diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs index de756513..b9853c72 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs @@ -2775,6 +2775,11 @@ internal static unsafe class VulkanVideoPresenter private void LoadDebugUtilsCommands() { + if (!_vulkanDebugUtilsEnabled) + { + return; + } + var setObjectName = _vk.GetDeviceProcAddr(_device, "vkSetDebugUtilsObjectNameEXT"); var beginLabel = _vk.GetDeviceProcAddr(_device, "vkCmdBeginDebugUtilsLabelEXT"); var endLabel = _vk.GetDeviceProcAddr(_device, "vkCmdEndDebugUtilsLabelEXT"); @@ -2877,10 +2882,48 @@ internal static unsafe class VulkanVideoPresenter $"SharpEmu texture 0x{texture.Address:X16} {texture.Width}x{texture.Height} " + $"fmt{texture.Format}/{format}"; + private static bool IsTitleDraw(IReadOnlyList vertexBuffers) + { + foreach (var buffer in vertexBuffers) + { + if (buffer.Location == 0 && + buffer.ComponentCount == 4 && + buffer.DataFormat == 10 && + buffer.NumberFormat == 0 && + buffer.Stride == 16 && + buffer.OffsetBytes == 12 && + buffer.Length == 67568) + { + return true; + } + } + + return false; + } + + private static bool AnyTargetAddressMatches( + IReadOnlyList? targets, + string environmentVariable) + { + if (targets is null) + { + return false; + } + + foreach (var target in targets) + { + if (AddressListContains(environmentVariable, target.Address)) + { + return true; + } + } + + return false; + } + private void CreateInstance() { var applicationName = (byte*)SilkMarshal.StringToPtr("SharpEmu"); - var enableValidation = Environment.GetEnvironmentVariable("SHARPEMU_VK_VALIDATION") == "1"; byte* validationLayerName = null; try @@ -2906,7 +2949,8 @@ internal static unsafe class VulkanVideoPresenter enabledExtensions[index] = extensions[index]; } - if (IsInstanceExtensionAvailable(DebugUtilsExtensionName)) + if (_vulkanDebugUtilsEnabled && + IsInstanceExtensionAvailable(DebugUtilsExtensionName)) { debugUtilsExtension = (byte*)SilkMarshal.StringToPtr(DebugUtilsExtensionName); enabledExtensions[enabledExtensionCount++] = debugUtilsExtension; @@ -2921,11 +2965,12 @@ internal static unsafe class VulkanVideoPresenter instanceCreateFlags |= InstanceCreateFlags.EnumeratePortabilityBitKhr; } - if (enableValidation && IsInstanceLayerAvailable("VK_LAYER_KHRONOS_validation")) + if (_vulkanValidationEnabled && + IsInstanceLayerAvailable("VK_LAYER_KHRONOS_validation")) { validationLayerName = (byte*)SilkMarshal.StringToPtr("VK_LAYER_KHRONOS_validation"); } - else if (enableValidation) + else if (_vulkanValidationEnabled) { Console.Error.WriteLine("[LOADER][WARN] SHARPEMU_VK_VALIDATION=1 but VK_LAYER_KHRONOS_validation not found (Vulkan SDK installed?)."); } @@ -4898,6 +4943,13 @@ internal static unsafe class VulkanVideoPresenter IReadOnlyList? renderTargets = null, ulong shaderAddress = 0) { + if (!_traceGuestImagesEnabled && + !_traceGuestImageAddressFilterEnabled && + GuestImageTraceInterval() is null) + { + return Array.Empty(); + } + var candidates = new HashSet(); foreach (var renderTarget in renderTargets ?? []) { @@ -5140,47 +5192,35 @@ internal static unsafe class VulkanVideoPresenter bool hasDepthAttachment = false, GuestDepthResource? feedbackDepth = null) { - var isTitleDraw = draw.VertexBuffers.Any(buffer => - buffer.Location == 0 && - buffer.ComponentCount == 4 && - buffer.DataFormat == 10 && - buffer.NumberFormat == 0 && - buffer.Stride == 16 && - buffer.OffsetBytes == 12 && - buffer.Length == 67568); - var forceFullscreenPipeline = Environment.GetEnvironmentVariable( - "SHARPEMU_FORCE_FULLSCREEN_PIPELINE") == "1"; - var forceFullscreenVertex = forceFullscreenPipeline || - Environment.GetEnvironmentVariable( - "SHARPEMU_FORCE_FULLSCREEN_VERTEX") == "1" || - isTitleDraw && Environment.GetEnvironmentVariable( - "SHARPEMU_FORCE_TITLE_FULLSCREEN_VERTEX") == "1" || - feedbackTargets?.Any(target => AddressListContains( - "SHARPEMU_FORCE_FULLSCREEN_VERTEX_TARGETS", - target.Address)) == true; - var forceRasterState = forceFullscreenPipeline || - Environment.GetEnvironmentVariable( - "SHARPEMU_FORCE_DEFAULT_RASTER_STATE") == "1" || - isTitleDraw && Environment.GetEnvironmentVariable( - "SHARPEMU_FORCE_TITLE_DEFAULT_RASTER_STATE") == "1" || - feedbackTargets?.Any(target => AddressListContains( - "SHARPEMU_FORCE_DEFAULT_RASTER_STATE_TARGETS", - target.Address)) == true; + var isTitleDraw = IsTitleDraw(draw.VertexBuffers); + var forceFullscreenVertex = _forceFullscreenPipeline || + _forceFullscreenVertex || + isTitleDraw && _forceTitleFullscreenVertex || + AnyTargetAddressMatches( + feedbackTargets, + "SHARPEMU_FORCE_FULLSCREEN_VERTEX_TARGETS"); + var forceRasterState = _forceFullscreenPipeline || + _forceDefaultRasterState || + isTitleDraw && _forceTitleDefaultRasterState || + AnyTargetAddressMatches( + feedbackTargets, + "SHARPEMU_FORCE_DEFAULT_RASTER_STATE_TARGETS"); var forceTitleSolidFragment = - Environment.GetEnvironmentVariable( - "SHARPEMU_FORCE_TITLE_SOLID_FRAGMENT") == "1" && + _forceTitleSolidFragment && isTitleDraw; - var forceSolidFragment = forceTitleSolidFragment || forceFullscreenPipeline || - Environment.GetEnvironmentVariable("SHARPEMU_FORCE_SOLID_FRAGMENT") == "1" || - feedbackTargets?.Any(target => AddressListContains( - "SHARPEMU_FORCE_SOLID_FRAGMENT_TARGETS", - target.Address)) == true; - var forceAttributeFragment = uint.TryParse( - Environment.GetEnvironmentVariable("SHARPEMU_FORCE_ATTRIBUTE_FRAGMENT"), - out var attributeFragmentLocation) && - feedbackTargets?.Any(target => AddressListContains( - "SHARPEMU_FORCE_ATTRIBUTE_FRAGMENT_TARGETS", - target.Address)) == true; + var forceSolidFragment = forceTitleSolidFragment || + _forceFullscreenPipeline || + _forceSolidFragment || + AnyTargetAddressMatches( + feedbackTargets, + "SHARPEMU_FORCE_SOLID_FRAGMENT_TARGETS"); + var attributeFragmentLocation = + _forceAttributeFragmentLocation.GetValueOrDefault(); + var forceAttributeFragment = + _forceAttributeFragmentLocation.HasValue && + AnyTargetAddressMatches( + feedbackTargets, + "SHARPEMU_FORCE_ATTRIBUTE_FRAGMENT_TARGETS"); var vertexSpirv = forceFullscreenVertex ? SpirvFixedShaders.CreateFullscreenVertex(0) : draw.VertexSpirv; @@ -5189,11 +5229,9 @@ internal static unsafe class VulkanVideoPresenter : forceAttributeFragment ? SpirvFixedShaders.CreateAttributeFragment(attributeFragmentLocation) : draw.PixelSpirv; - var fixedFragmentDumpPath = Environment.GetEnvironmentVariable( - "SHARPEMU_DUMP_FIXED_SOLID_FRAGMENT"); - if (forceSolidFragment && !string.IsNullOrWhiteSpace(fixedFragmentDumpPath)) + if (forceSolidFragment && !string.IsNullOrWhiteSpace(_fixedFragmentDumpPath)) { - File.WriteAllBytes(fixedFragmentDumpPath, fragmentSpirv); + File.WriteAllBytes(_fixedFragmentDumpPath, fragmentSpirv); } if (draw.RenderState.Blends.Count != renderTargetFormats.Count) { @@ -5243,21 +5281,18 @@ internal static unsafe class VulkanVideoPresenter resources.Raster = GuestRasterState.Default; resources.Depth = GuestDepthState.Default; } - if (isTitleDraw && Environment.GetEnvironmentVariable( - "SHARPEMU_FORCE_TITLE_DEFAULT_BLEND") == "1") + if (isTitleDraw && _forceTitleDefaultBlend) { resources.Blends = Enumerable.Repeat( GuestBlendState.Default, renderTargetFormats.Count).ToArray(); } - if (isTitleDraw && Environment.GetEnvironmentVariable( - "SHARPEMU_FORCE_TITLE_DEFAULT_VIEWPORT_SCISSOR") == "1") + if (isTitleDraw && _forceTitleDefaultViewportScissor) { resources.Scissor = null; resources.Viewport = null; } - if (isTitleDraw && Environment.GetEnvironmentVariable( - "SHARPEMU_FORCE_TITLE_DISABLE_CULL") == "1") + if (isTitleDraw && _forceTitleDisableCull) { resources.Raster = resources.Raster with { @@ -5265,13 +5300,11 @@ internal static unsafe class VulkanVideoPresenter CullBack = false, }; } - if (isTitleDraw && Environment.GetEnvironmentVariable( - "SHARPEMU_FORCE_TITLE_DISABLE_DEPTH") == "1") + if (isTitleDraw && _forceTitleDisableDepth) { resources.Depth = GuestDepthState.Default; } - if (isTitleDraw && Environment.GetEnvironmentVariable( - "SHARPEMU_TRACE_TITLE_STATE") == "1") + if (isTitleDraw && _traceTitleState) { Console.Error.WriteLine( $"[LOADER][TRACE] vk.title_state " + @@ -7874,7 +7907,7 @@ internal static unsafe class VulkanVideoPresenter { ReadOnlySpan source = guestBuffer.Data.AsSpan(0, guestBuffer.Length); byte[]? forcedVertexColors = null; - if (Environment.GetEnvironmentVariable("SHARPEMU_FORCE_TITLE_VERTEX_COLOR_WHITE") == "1" && + if (_forceTitleVertexColorWhite && guestBuffer.Location == 0 && guestBuffer.ComponentCount == 4 && guestBuffer.DataFormat == 10 && @@ -9400,37 +9433,67 @@ internal static unsafe class VulkanVideoPresenter return; } - if (targetFormats - .Select((format, index) => format.IsInteger && work.Draw.RenderState.Blends[index].Enable) - .Any(invalid => invalid)) + for (var index = 0; index < targetFormats.Length; index++) { - Console.Error.WriteLine( - "[LOADER][WARN] Vulkan skipped MRT draw with blending enabled for an integer attachment."); - ReturnPooledGuestData(work.Draw); - return; + if (targetFormats[index].IsInteger && + work.Draw.RenderState.Blends[index].Enable) + { + Console.Error.WriteLine( + "[LOADER][WARN] Vulkan skipped MRT draw with blending enabled for an integer attachment."); + ReturnPooledGuestData(work.Draw); + return; + } } - if (!_supportsIndependentBlend && - work.Draw.RenderState.Blends.Skip(1).Any(blend => blend != work.Draw.RenderState.Blends[0])) + if (!_supportsIndependentBlend) { - Console.Error.WriteLine( - "[LOADER][WARN] Vulkan skipped MRT draw requiring unsupported independentBlend."); - ReturnPooledGuestData(work.Draw); - return; + for (var index = 1; index < work.Draw.RenderState.Blends.Count; index++) + { + if (work.Draw.RenderState.Blends[index] != + work.Draw.RenderState.Blends[0]) + { + Console.Error.WriteLine( + "[LOADER][WARN] Vulkan skipped MRT draw requiring unsupported independentBlend."); + ReturnPooledGuestData(work.Draw); + return; + } + } } - var formats = targetFormats.Select(target => target.Format).ToArray(); + var formats = new Format[targetFormats.Length]; + for (var index = 0; index < targetFormats.Length; index++) + { + formats[index] = targetFormats[index].Format; + } - var targetAddresses = work.Targets - .Where(target => target.Address != 0) - .Select(target => target.Address) - .ToHashSet(); - if (work.Draw.Textures.Any(texture => - texture.IsStorage && targetAddresses.Contains(texture.Address))) + var hasStorageFeedback = false; + foreach (var texture in work.Draw.Textures) + { + if (!texture.IsStorage || texture.Address == 0) + { + continue; + } + + foreach (var target in work.Targets) + { + if (target.Address != 0 && target.Address == texture.Address) + { + hasStorageFeedback = true; + break; + } + } + + if (hasStorageFeedback) + { + break; + } + } + + if (hasStorageFeedback) { Console.Error.WriteLine( $"[LOADER][WARN] Vulkan skipped storage render-target feedback loop " + - $"targets={string.Join(',', targetAddresses.Select(address => $"0x{address:X16}"))}; " + + $"targets={string.Join(',', work.Targets.Where(target => target.Address != 0).Select(target => $"0x{target.Address:X16}"))}; " + "sampled aliases use ordered snapshots"); ReturnPooledGuestData(work.Draw); return; @@ -9802,64 +9865,34 @@ internal static unsafe class VulkanVideoPresenter } } - var guestWritesMode = Environment.GetEnvironmentVariable( - "SHARPEMU_TRACE_GUEST_WRITES"); - var traceAddressWriteOrdinal = 0L; - _ = long.TryParse( - Environment.GetEnvironmentVariable("SHARPEMU_TRACE_GUEST_WRITE_ORDINAL"), - out traceAddressWriteOrdinal); - var traceLargeWriteOrdinal = 0L; - if (guestWritesMode is not null && - guestWritesMode.StartsWith("large@", StringComparison.Ordinal) && - long.TryParse( - guestWritesMode.AsSpan("large@".Length), - out var parsedTraceLargeWriteOrdinal) && - parsedTraceLargeWriteOrdinal > 0) - { - traceLargeWriteOrdinal = parsedTraceLargeWriteOrdinal; - } - var tracePixelSpirv = false; - if (int.TryParse( - Environment.GetEnvironmentVariable("SHARPEMU_TRACE_PIXEL_SPIRV_BYTES"), - out var tracePixelSpirvBytes) && - tracePixelSpirvBytes == work.Draw.PixelSpirv.Length) + if (_tracePixelSpirvBytes > 0 && + _tracePixelSpirvBytes == work.Draw.PixelSpirv.Length) { var pixelWriteCount = _pixelSpirvWriteCounts.TryGetValue( - tracePixelSpirvBytes, + _tracePixelSpirvBytes, out var previousPixelWriteCount) ? previousPixelWriteCount + 1 : 1; - _pixelSpirvWriteCounts[tracePixelSpirvBytes] = pixelWriteCount; - var tracePixelSpirvOccurrence = 1; - _ = int.TryParse( - Environment.GetEnvironmentVariable( - "SHARPEMU_TRACE_PIXEL_SPIRV_OCCURRENCE"), - out tracePixelSpirvOccurrence); + _pixelSpirvWriteCounts[_tracePixelSpirvBytes] = pixelWriteCount; tracePixelSpirv = - pixelWriteCount == Math.Max(tracePixelSpirvOccurrence, 1); + pixelWriteCount == _tracePixelSpirvOccurrence; } var traceTitleDraw = !_tracedTitleDraw && - Environment.GetEnvironmentVariable("SHARPEMU_TRACE_TITLE_DRAW") == "1" && - work.Draw.VertexBuffers.Any(buffer => - buffer.Location == 0 && - buffer.ComponentCount == 4 && - buffer.DataFormat == 10 && - buffer.NumberFormat == 0 && - buffer.Stride == 16 && - buffer.OffsetBytes == 12 && - buffer.Length == 67568); + _traceTitleDrawEnabled && + IsTitleDraw(work.Draw.VertexBuffers); _tracedTitleDraw |= traceTitleDraw; foreach (var target in targets) { var traceAddressWrite = ShouldTraceGuestImageWriteForDiagnostics(target.Address); - var traceSmallWrites = guestWritesMode == "small" && + var traceSmallWrites = _traceGuestWritesMode == "small" && target.Width <= 512 && target.Height <= 256; var traceLargeWrites = - (guestWritesMode == "large" || traceLargeWriteOrdinal != 0) && + (_traceGuestWritesMode == "large" || + _traceLargeGuestWriteOrdinal != 0) && target.Width >= 2560 && target.Height >= 1440; if (traceAddressWrite || traceSmallWrites || traceLargeWrites || tracePixelSpirv || traceTitleDraw) @@ -9872,10 +9905,10 @@ internal static unsafe class VulkanVideoPresenter _tracedGuestWriteCounts[target.Address] = writeCount; var shouldTraceWrite = tracePixelSpirv || traceTitleDraw ? true - : traceAddressWrite && traceAddressWriteOrdinal > 0 - ? writeCount == traceAddressWriteOrdinal - : traceLargeWriteOrdinal != 0 - ? writeCount == traceLargeWriteOrdinal + : traceAddressWrite && _traceGuestWriteOrdinal > 0 + ? writeCount == _traceGuestWriteOrdinal + : _traceLargeGuestWriteOrdinal != 0 + ? writeCount == _traceLargeGuestWriteOrdinal : writeCount <= (traceLargeWrites ? 2 : traceSmallWrites ? 48 : 3); if (traceAddressWrite || shouldTraceWrite) @@ -12849,6 +12882,20 @@ internal static unsafe class VulkanVideoPresenter // Diagnostics toggles are read once: these run per draw / per cached // texture hit, and env lookups plus string parsing are far too // expensive there (and non-trivially so under Rosetta 2). + private static readonly bool _vulkanValidationEnabled = + string.Equals( + Environment.GetEnvironmentVariable("SHARPEMU_VK_VALIDATION"), + "1", + StringComparison.Ordinal); + // Object names and command labels are useful in RenderDoc and validation + // captures, but formatting them and calling the debug-utils driver hooks + // for every draw is measurable overhead in normal gameplay. + private static readonly bool _vulkanDebugUtilsEnabled = + _vulkanValidationEnabled || + string.Equals( + Environment.GetEnvironmentVariable("SHARPEMU_VK_DEBUG_LABELS"), + "1", + StringComparison.Ordinal); private static readonly string? _traceGuestImagesMode = Environment.GetEnvironmentVariable("SHARPEMU_TRACE_GUEST_IMAGES"); private static readonly bool _traceGuestImagesEnabled = @@ -12912,10 +12959,80 @@ internal static unsafe class VulkanVideoPresenter !string.IsNullOrWhiteSpace( Environment.GetEnvironmentVariable( "SHARPEMU_TRACE_GUEST_IMAGE_ADDRS")); + private static readonly bool _forceFullscreenPipeline = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_FULLSCREEN_PIPELINE") == "1"; + private static readonly bool _forceFullscreenVertex = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_FULLSCREEN_VERTEX") == "1"; + private static readonly bool _forceTitleFullscreenVertex = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_TITLE_FULLSCREEN_VERTEX") == "1"; + private static readonly bool _forceDefaultRasterState = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_DEFAULT_RASTER_STATE") == "1"; + private static readonly bool _forceTitleDefaultRasterState = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_TITLE_DEFAULT_RASTER_STATE") == "1"; + private static readonly bool _forceTitleSolidFragment = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_TITLE_SOLID_FRAGMENT") == "1"; + private static readonly bool _forceSolidFragment = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_SOLID_FRAGMENT") == "1"; + private static readonly uint? _forceAttributeFragmentLocation = + uint.TryParse( + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_ATTRIBUTE_FRAGMENT"), + out var forceAttributeFragmentLocation) + ? forceAttributeFragmentLocation + : null; + private static readonly string? _fixedFragmentDumpPath = + Environment.GetEnvironmentVariable("SHARPEMU_DUMP_FIXED_SOLID_FRAGMENT"); + private static readonly bool _forceTitleDefaultBlend = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_TITLE_DEFAULT_BLEND") == "1"; + private static readonly bool _forceTitleDefaultViewportScissor = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_TITLE_DEFAULT_VIEWPORT_SCISSOR") == "1"; + private static readonly bool _forceTitleDisableCull = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_TITLE_DISABLE_CULL") == "1"; + private static readonly bool _forceTitleDisableDepth = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_TITLE_DISABLE_DEPTH") == "1"; + private static readonly bool _traceTitleState = + Environment.GetEnvironmentVariable("SHARPEMU_TRACE_TITLE_STATE") == "1"; + private static readonly bool _forceTitleVertexColorWhite = + Environment.GetEnvironmentVariable("SHARPEMU_FORCE_TITLE_VERTEX_COLOR_WHITE") == "1"; + private static readonly bool _chunkedDrawsEnabled = + Environment.GetEnvironmentVariable("SHARPEMU_ENABLE_CHUNKED_DRAWS") == "1"; + private static readonly string? _traceGuestWritesMode = + Environment.GetEnvironmentVariable("SHARPEMU_TRACE_GUEST_WRITES"); + private static readonly long _traceGuestWriteOrdinal = + long.TryParse( + Environment.GetEnvironmentVariable("SHARPEMU_TRACE_GUEST_WRITE_ORDINAL"), + out var traceGuestWriteOrdinal) + ? traceGuestWriteOrdinal + : 0; + private static readonly long _traceLargeGuestWriteOrdinal = + ParseTraceLargeGuestWriteOrdinal(_traceGuestWritesMode); + private static readonly int _tracePixelSpirvBytes = + int.TryParse( + Environment.GetEnvironmentVariable("SHARPEMU_TRACE_PIXEL_SPIRV_BYTES"), + out var tracePixelSpirvBytes) + ? tracePixelSpirvBytes + : 0; + private static readonly int _tracePixelSpirvOccurrence = + int.TryParse( + Environment.GetEnvironmentVariable("SHARPEMU_TRACE_PIXEL_SPIRV_OCCURRENCE"), + out var tracePixelSpirvOccurrence) + ? Math.Max(tracePixelSpirvOccurrence, 1) + : 1; + private static readonly bool _traceTitleDrawEnabled = + Environment.GetEnvironmentVariable("SHARPEMU_TRACE_TITLE_DRAW") == "1"; private static readonly System.Collections.Concurrent.ConcurrentDictionary< string, (bool Wildcard, ulong[] Addresses)> _cachedAddressLists = new(); + private static long ParseTraceLargeGuestWriteOrdinal(string? mode) + { + return mode is not null && + mode.StartsWith("large@", StringComparison.Ordinal) && + long.TryParse(mode.AsSpan("large@".Length), out var ordinal) && + ordinal > 0 + ? ordinal + : 0; + } + private static bool ShouldTraceGuestImageContentsForDiagnostics() => _traceGuestImagesEnabled; @@ -13113,8 +13230,7 @@ internal static unsafe class VulkanVideoPresenter // MoltenVK this starves the render thread and makes the guest fall // behind its own flip queue. Vulkan clips a normal fullscreen draw // efficiently; keep tiling only as an explicit driver diagnostic. - var maxPixelsPerDraw = Environment.GetEnvironmentVariable( - "SHARPEMU_ENABLE_CHUNKED_DRAWS") == "1" + var maxPixelsPerDraw = _chunkedDrawsEnabled ? 512u * 512u : uint.MaxValue; var rowsPerDraw = Math.Max(