From 2bc466b10eecb0f07284a92c6d96ee1704c1095a Mon Sep 17 00:00:00 2001 From: Berk Date: Sat, 4 Jul 2026 15:19:32 +0300 Subject: [PATCH] [gpu-hotfix1] Cache improvements have been made (6x performance gain) (#18) --- .../Memory/PhysicalVirtualMemory.cs | 173 ++++--- src/SharpEmu.Libs/Agc/AgcExports.cs | 40 +- src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs | 54 +- src/SharpEmu.Libs/VideoOut/VideoOutExports.cs | 45 ++ .../VideoOut/VulkanVideoPresenter.cs | 460 +++++++++++++----- 5 files changed, 582 insertions(+), 190 deletions(-) diff --git a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs index 5619bee..7fa17e8 100644 --- a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs +++ b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs @@ -81,7 +81,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA _gate.EnterWriteLock(); try { - _regions.Add(new MemoryRegion + InsertRegionSorted(new MemoryRegion { VirtualAddress = actualAddress, Size = alignedSize, @@ -210,7 +210,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA _gate.EnterWriteLock(); try { - _regions.Add(new MemoryRegion + InsertRegionSorted(new MemoryRegion { VirtualAddress = actualAddress, Size = alignedSize, @@ -473,30 +473,34 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA _gate.EnterReadLock(); try { - foreach (var region in _regions) + var region = FindRegion(virtualAddress, (ulong)destination.Length); + if (region is not null && + TryResolveRegionOffset( + virtualAddress, + (ulong)destination.Length, + region, + out var offset)) { - if (TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset)) + var srcPtr = (void*)(region.VirtualAddress + offset); + if (destination.IsEmpty) { - var srcPtr = (void*)(region.VirtualAddress + offset); - if (destination.IsEmpty) - { - return true; - } + return true; + } - if (region.IsReservedOnly) + if (region.IsReservedOnly) + { + if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region)) { - if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region)) - { - return false; - } - } - - if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region)) - { - requiresExclusiveAccess = true; - break; + return false; } + } + if (!CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region)) + { + requiresExclusiveAccess = true; + } + else + { fixed (byte* destPtr = destination) { Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length); @@ -533,30 +537,34 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA _gate.EnterReadLock(); try { - foreach (var region in _regions) + var region = FindRegion(virtualAddress, (ulong)source.Length); + if (region is not null && + TryResolveRegionOffset( + virtualAddress, + (ulong)source.Length, + region, + out var offset)) { - if (TryResolveRegionOffset(virtualAddress, (ulong)source.Length, region, out var offset)) + var destPtr = (void*)(region.VirtualAddress + offset); + if (source.IsEmpty) { - var destPtr = (void*)(region.VirtualAddress + offset); - if (source.IsEmpty) - { - return true; - } + return true; + } - if (region.IsReservedOnly) + if (region.IsReservedOnly) + { + if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region)) { - if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region)) - { - return false; - } - } - - if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region)) - { - requiresExclusiveAccess = true; - break; + return false; } + } + if (!CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region)) + { + requiresExclusiveAccess = true; + } + else + { fixed (byte* srcPtr = source) { Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length); @@ -589,13 +597,14 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA private bool TryReadExclusive(ulong virtualAddress, Span destination) { - foreach (var region in _regions) + var region = FindRegion(virtualAddress, (ulong)destination.Length); + if (region is not null && + TryResolveRegionOffset( + virtualAddress, + (ulong)destination.Length, + region, + out var offset)) { - if (!TryResolveRegionOffset(virtualAddress, (ulong)destination.Length, region, out var offset)) - { - continue; - } - var srcPtr = (void*)(region.VirtualAddress + offset); if (!EnsureRangeCommitted((ulong)srcPtr, (ulong)destination.Length, region)) { @@ -637,13 +646,14 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA private bool TryWriteExclusive(ulong virtualAddress, ReadOnlySpan source) { - foreach (var region in _regions) + var region = FindRegion(virtualAddress, (ulong)source.Length); + if (region is not null && + TryResolveRegionOffset( + virtualAddress, + (ulong)source.Length, + region, + out var offset)) { - if (!TryResolveRegionOffset(virtualAddress, (ulong)source.Length, region, out var offset)) - { - continue; - } - var destPtr = (void*)(region.VirtualAddress + offset); if (!EnsureRangeCommitted((ulong)destPtr, (ulong)source.Length, region)) { @@ -699,15 +709,9 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA _gate.EnterReadLock(); try { - foreach (var region in _regions) - { - if (virtualAddress >= region.VirtualAddress && - virtualAddress < region.VirtualAddress + region.Size) - { - return (void*)virtualAddress; - } - } - return null; + return FindRegion(virtualAddress, 1) is not null + ? (void*)virtualAddress + : null; } finally { @@ -720,14 +724,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA _gate.EnterReadLock(); try { - foreach (var region in _regions) - { - if (TryResolveRegionOffset(virtualAddress, size, region, out _)) - { - return true; - } - } - return false; + return FindRegion(virtualAddress, size) is not null; } finally { @@ -737,14 +734,48 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA private MemoryRegion? FindRegion(ulong address, ulong size) { - foreach (var region in _regions) + var low = 0; + var high = _regions.Count - 1; + MemoryRegion? candidate = null; + while (low <= high) { - if (TryResolveRegionOffset(address, size, region, out _)) + var middle = low + ((high - low) >> 1); + var region = _regions[middle]; + if (region.VirtualAddress <= address) { - return region; + candidate = region; + low = middle + 1; + } + else + { + high = middle - 1; } } - return null; + + return candidate is not null && + TryResolveRegionOffset(address, size, candidate, out _) + ? candidate + : null; + } + + private void InsertRegionSorted(MemoryRegion region) + { + var low = 0; + var high = _regions.Count; + while (low < high) + { + var middle = low + ((high - low) >> 1); + if (_regions[middle].VirtualAddress < region.VirtualAddress) + { + low = middle + 1; + } + else + { + high = middle; + } + } + + _regions.Insert(low, region); } private bool TryGetOverlappingRegionEnd(ulong address, ulong size, out ulong overlapEnd) diff --git a/src/SharpEmu.Libs/Agc/AgcExports.cs b/src/SharpEmu.Libs/Agc/AgcExports.cs index 7f7cc83..1dfc9aa 100644 --- a/src/SharpEmu.Libs/Agc/AgcExports.cs +++ b/src/SharpEmu.Libs/Agc/AgcExports.cs @@ -155,6 +155,16 @@ public static class AgcExports (ulong Cs, ulong State, uint LocalX, uint LocalY, uint LocalZ), byte[]> _computeSpirvCache = new(); private static readonly Dictionary _shaderHeadersByCode = new(); + private static readonly bool _traceAgc = string.Equals( + Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), + "1", + StringComparison.Ordinal); + private static readonly bool _traceAgcShader = + _traceAgc || + string.Equals( + Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"), + "1", + StringComparison.Ordinal); private static long _dcbWriteDataTraceCount; private static long _dcbWaitRegMemTraceCount; private static long _createShaderTraceCount; @@ -3629,6 +3639,31 @@ public static class AgcExports return true; } + if (!isStorage && + descriptor.Address != 0 && + VulkanVideoPresenter.IsGuestImageAvailable( + descriptor.Address, + descriptor.Format, + descriptor.NumberType)) + { + texture = new VulkanGuestDrawTexture( + descriptor.Address, + descriptor.Width, + descriptor.Height, + descriptor.Format, + descriptor.NumberType, + [], + IsFallback: false, + IsStorage: false, + MipLevels: descriptor.MipLevels, + MipLevel: mipLevel, + Pitch: sourceWidth, + TileMode: descriptor.TileMode, + DstSelect: descriptor.DstSelect, + Sampler: ToVulkanSampler(samplerDescriptor)); + return true; + } + if (isStorage) { var initialPixels = Array.Empty(); @@ -5405,7 +5440,7 @@ public static class AgcExports private static void TraceAgc(string message) { - if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal)) + if (!_traceAgc) { return; } @@ -5415,8 +5450,7 @@ public static class AgcExports private static void TraceAgcShader(string message) { - if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC"), "1", StringComparison.Ordinal) && - !string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"), "1", StringComparison.Ordinal)) + if (!_traceAgcShader) { return; } diff --git a/src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs b/src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs index 71ec844..5111814 100644 --- a/src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs +++ b/src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs @@ -4,6 +4,7 @@ using SharpEmu.HLE; using SharpEmu.Libs.VideoOut; using System.Buffers.Binary; +using System.Runtime.CompilerServices; using System.Text; namespace SharpEmu.Libs.Agc; @@ -13,6 +14,14 @@ internal static class Gen5ShaderTranslator private const int MaxInstructions = 4096; private const int MinimumUserDataDwords = 16; private const int MaximumUserDataDwords = 256; + private static readonly ConditionalWeakTable _decodeCaches = new(); + + private sealed class ShaderDecodeCache + { + public object Gate { get; } = new(); + public Dictionary Programs { get; } = new(); + public Dictionary Metadata { get; } = new(); + } private static readonly uint[] FullscreenBarycentricEs = [ @@ -117,16 +126,51 @@ internal static class Gen5ShaderTranslator uint userDataScalarRegisterBase = 0) { state = default!; - if (!TryDecodeProgram(ctx, shaderAddress, out var program, out error)) + error = string.Empty; + var cache = _decodeCaches.GetValue(ctx.Memory, static _ => new ShaderDecodeCache()); + Gen5ShaderProgram? program; + lock (cache.Gate) { - return false; + cache.Programs.TryGetValue(shaderAddress, out program); + } + + if (program is null) + { + if (!TryDecodeProgram(ctx, shaderAddress, out program, out error)) + { + return false; + } + + lock (cache.Gate) + { + cache.Programs.TryAdd(shaderAddress, program); + } } Gen5ShaderMetadata? metadata = null; - if (shaderHeaderAddress != 0 && - Gen5ShaderMetadataReader.TryRead(ctx, shaderHeaderAddress, out var decodedMetadata)) + if (shaderHeaderAddress != 0) { - metadata = decodedMetadata; + var metadataCached = false; + lock (cache.Gate) + { + metadataCached = cache.Metadata.TryGetValue(shaderHeaderAddress, out metadata); + } + + if (!metadataCached) + { + if (Gen5ShaderMetadataReader.TryRead( + ctx, + shaderHeaderAddress, + out var decodedMetadata)) + { + metadata = decodedMetadata; + } + + lock (cache.Gate) + { + cache.Metadata.TryAdd(shaderHeaderAddress, metadata); + } + } } var userData = new uint[GetUserDataDwordCount(metadata)]; diff --git a/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs b/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs index de82410..dcd984c 100644 --- a/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs +++ b/src/SharpEmu.Libs/VideoOut/VideoOutExports.cs @@ -4,6 +4,7 @@ using SharpEmu.HLE; using SharpEmu.Libs.Kernel; using System.Buffers.Binary; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; @@ -48,6 +49,13 @@ public static class VideoOutExports private static int _frameDumpCount; private static long _nextFrameDumpIndex; private static string _windowTitle = "SharpEmu VideoOut"; + private static readonly bool _logFrameRate = string.Equals( + Environment.GetEnvironmentVariable("SHARPEMU_LOG_VIDEOOUT_FPS"), + "1", + StringComparison.Ordinal); + private static long _frameRateWindowStart = Stopwatch.GetTimestamp(); + private static long _submittedFrameCount; + private static long _presentedFrameCount; public static void ConfigureApplicationInfo(string? title, string? titleId, string? version) { @@ -812,9 +820,46 @@ public static class VideoOutExports } TraceVideoOut($"videoout.submit_flip handle={handle} index={bufferIndex} mode={flipMode} arg={flipArg} events={flipEvents.Count}"); + ReportFrameRate(presented: false); return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + internal static void ReportPresentedFrame() => + ReportFrameRate(presented: true); + + private static void ReportFrameRate(bool presented) + { + if (!_logFrameRate) + { + return; + } + + if (presented) + { + Interlocked.Increment(ref _presentedFrameCount); + } + else + { + Interlocked.Increment(ref _submittedFrameCount); + } + + var started = Volatile.Read(ref _frameRateWindowStart); + var now = Stopwatch.GetTimestamp(); + var elapsedTicks = now - started; + if (elapsedTicks < Stopwatch.Frequency || + Interlocked.CompareExchange(ref _frameRateWindowStart, now, started) != started) + { + return; + } + + var elapsedSeconds = (double)elapsedTicks / Stopwatch.Frequency; + var submitted = Interlocked.Exchange(ref _submittedFrameCount, 0); + var presentedCount = Interlocked.Exchange(ref _presentedFrameCount, 0); + Console.Error.WriteLine( + $"[LOADER][PERF] videoout submitted_fps={submitted / elapsedSeconds:F1} " + + $"presented_fps={presentedCount / elapsedSeconds:F1}"); + } + private static int RegisterBufferRange(VideoOutPortState port, int startIndex, ReadOnlySpan addresses, BufferAttribute attribute, int requestedGroupIndex = -1) { lock (_stateGate) diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs index dc7da05..4ae1177 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs @@ -8,7 +8,9 @@ using SharpEmu.Libs.Agc; using Silk.NET.Vulkan; using Silk.NET.Vulkan.Extensions.KHR; using Silk.NET.Windowing; +using System.Numerics; using System.Runtime.CompilerServices; +using System.Security.Cryptography; using System.Text; using VkBuffer = Silk.NET.Vulkan.Buffer; using VkSemaphore = Silk.NET.Vulkan.Semaphore; @@ -597,6 +599,24 @@ internal static unsafe class VulkanVideoPresenter return true; } + internal static bool IsGuestImageAvailable( + ulong address, + uint format, + uint numberType) + { + var guestFormat = GetGuestTextureFormat(format, numberType); + if (address == 0 || guestFormat == 0) + { + return false; + } + + lock (_gate) + { + return _availableGuestImages.TryGetValue(address, out var availableFormat) && + availableFormat == guestFormat; + } + } + public static bool TrySubmitGuestImageBlit( ulong sourceAddress, uint sourceWidth, @@ -867,14 +887,50 @@ internal static unsafe class VulkanVideoPresenter private int _tracedVertexBufferCount; private readonly Dictionary _computePipelines = new(ReferenceEqualityComparer.Instance); + private readonly Dictionary _graphicsPipelines = new(); + private readonly Dictionary _samplers = new(); + private readonly Dictionary _shaderDigests = + new(ReferenceEqualityComparer.Instance); + private readonly Dictionary + _descriptorLayouts = new(); + private readonly Dictionary> + _hostBufferPool = new(); + private readonly Dictionary _hostBufferAllocations = new(); private readonly Queue _pendingGuestSubmissions = new(); + private readonly record struct GraphicsPipelineKey( + string VertexShader, + string FragmentShader, + ulong RenderPass, + PrimitiveTopology Topology, + VulkanGuestBlendState Blend, + string ResourceLayout, + string VertexLayout); + + private readonly record struct HostBufferPoolKey( + BufferUsageFlags Usage, + ulong Capacity); + + private readonly record struct DescriptorLayoutKey( + ShaderStageFlags Stages, + string Resources); + + private sealed record DescriptorLayoutBundle( + DescriptorSetLayout DescriptorSetLayout, + PipelineLayout PipelineLayout); + + private sealed record HostBufferAllocation( + VkBuffer Buffer, + DeviceMemory Memory, + HostBufferPoolKey Key); + private sealed class TranslatedDrawResources { public string DebugName = "SharpEmu translated"; public PipelineLayout PipelineLayout; public Pipeline Pipeline; public bool PipelineCached; + public bool DescriptorLayoutCached; public DescriptorSetLayout DescriptorSetLayout; public DescriptorPool DescriptorPool; public DescriptorSet DescriptorSet; @@ -1978,81 +2034,16 @@ internal static unsafe class VulkanVideoPresenter var storageImageCount = textureCount - sampledImageCount; var globalBufferCount = resources.GlobalMemoryBuffers.Length; var bindingCount = textureCount + (globalBufferCount == 0 ? 0 : 1); + var layout = GetOrCreateDescriptorLayout(resources, stageFlags, bindingCount); + resources.DescriptorSetLayout = layout.DescriptorSetLayout; + resources.PipelineLayout = layout.PipelineLayout; + resources.DescriptorLayoutCached = true; if (bindingCount == 0) { - var layoutInfo = new PipelineLayoutCreateInfo - { - SType = StructureType.PipelineLayoutCreateInfo, - }; - PipelineLayout pipelineLayout; - Check( - _vk.CreatePipelineLayout(_device, &layoutInfo, null, out pipelineLayout), - "vkCreatePipelineLayout"); - resources.PipelineLayout = pipelineLayout; return; } - var bindings = new DescriptorSetLayoutBinding[bindingCount]; - var bindingOffset = 0; - if (globalBufferCount != 0) - { - bindings[bindingOffset++] = new DescriptorSetLayoutBinding - { - Binding = 0, - DescriptorType = DescriptorType.StorageBuffer, - DescriptorCount = (uint)globalBufferCount, - StageFlags = stageFlags, - }; - } - - for (var index = 0; index < textureCount; index++) - { - bindings[bindingOffset + index] = new DescriptorSetLayoutBinding - { - Binding = (uint)(index + 1), - DescriptorType = resources.Textures[index].IsStorage - ? DescriptorType.StorageImage - : DescriptorType.CombinedImageSampler, - DescriptorCount = 1, - StageFlags = stageFlags, - }; - } - - fixed (DescriptorSetLayoutBinding* bindingPointer = bindings) - { - var layoutInfo = new DescriptorSetLayoutCreateInfo - { - SType = StructureType.DescriptorSetLayoutCreateInfo, - BindingCount = (uint)bindings.Length, - PBindings = bindingPointer, - }; - DescriptorSetLayout descriptorSetLayout; - Check( - _vk.CreateDescriptorSetLayout( - _device, - &layoutInfo, - null, - out descriptorSetLayout), - "vkCreateDescriptorSetLayout"); - resources.DescriptorSetLayout = descriptorSetLayout; - } - - var setLayout = resources.DescriptorSetLayout; - var pipelineLayoutInfo = new PipelineLayoutCreateInfo - { - SType = StructureType.PipelineLayoutCreateInfo, - SetLayoutCount = 1, - PSetLayouts = &setLayout, - }; - PipelineLayout translatedPipelineLayout; - Check( - _vk.CreatePipelineLayout( - _device, - &pipelineLayoutInfo, - null, - out translatedPipelineLayout), - "vkCreatePipelineLayout"); - resources.PipelineLayout = translatedPipelineLayout; + var setLayout = layout.DescriptorSetLayout; var poolSizes = new DescriptorPoolSize[ (sampledImageCount == 0 ? 0 : 1) + @@ -2202,6 +2193,21 @@ internal static unsafe class VulkanVideoPresenter RenderPass renderPass, Extent2D extent) { + var pipelineKey = new GraphicsPipelineKey( + GetShaderDigest(vertexSpirv), + GetShaderDigest(fragmentSpirv), + renderPass.Handle, + resources.Topology, + resources.Blend, + GetResourceLayoutKey(resources), + GetVertexLayoutKey(resources)); + if (_graphicsPipelines.TryGetValue(pipelineKey, out var cachedPipeline)) + { + resources.Pipeline = cachedPipeline; + resources.PipelineCached = true; + return; + } + var vertexModule = CreateShaderModule(vertexSpirv); var fragmentModule = CreateShaderModule(fragmentSpirv); var entryPoint = (byte*)SilkMarshal.StringToPtr("main"); @@ -2353,6 +2359,8 @@ internal static unsafe class VulkanVideoPresenter out pipeline), "vkCreateGraphicsPipelines(translated)"); resources.Pipeline = pipeline; + resources.PipelineCached = true; + _graphicsPipelines.Add(pipelineKey, pipeline); SetDebugName( ObjectType.Pipeline, pipeline.Handle, @@ -2367,6 +2375,129 @@ internal static unsafe class VulkanVideoPresenter } } + private DescriptorLayoutBundle GetOrCreateDescriptorLayout( + TranslatedDrawResources resources, + ShaderStageFlags stageFlags, + int bindingCount) + { + var key = new DescriptorLayoutKey(stageFlags, GetResourceLayoutKey(resources)); + if (_descriptorLayouts.TryGetValue(key, out var cached)) + { + return cached; + } + + DescriptorSetLayout descriptorSetLayout = default; + if (bindingCount != 0) + { + var bindings = new DescriptorSetLayoutBinding[bindingCount]; + var bindingOffset = 0; + if (resources.GlobalMemoryBuffers.Length != 0) + { + bindings[bindingOffset++] = new DescriptorSetLayoutBinding + { + Binding = 0, + DescriptorType = DescriptorType.StorageBuffer, + DescriptorCount = (uint)resources.GlobalMemoryBuffers.Length, + StageFlags = stageFlags, + }; + } + + for (var index = 0; index < resources.Textures.Length; index++) + { + bindings[bindingOffset + index] = new DescriptorSetLayoutBinding + { + Binding = (uint)(index + 1), + DescriptorType = resources.Textures[index].IsStorage + ? DescriptorType.StorageImage + : DescriptorType.CombinedImageSampler, + DescriptorCount = 1, + StageFlags = stageFlags, + }; + } + + fixed (DescriptorSetLayoutBinding* bindingPointer = bindings) + { + var descriptorInfo = new DescriptorSetLayoutCreateInfo + { + SType = StructureType.DescriptorSetLayoutCreateInfo, + BindingCount = (uint)bindings.Length, + PBindings = bindingPointer, + }; + Check( + _vk.CreateDescriptorSetLayout( + _device, + &descriptorInfo, + null, + out descriptorSetLayout), + "vkCreateDescriptorSetLayout"); + } + } + + var pipelineInfo = new PipelineLayoutCreateInfo + { + SType = StructureType.PipelineLayoutCreateInfo, + }; + if (descriptorSetLayout.Handle != 0) + { + pipelineInfo.SetLayoutCount = 1; + pipelineInfo.PSetLayouts = &descriptorSetLayout; + } + + PipelineLayout pipelineLayout; + Check( + _vk.CreatePipelineLayout( + _device, + &pipelineInfo, + null, + out pipelineLayout), + "vkCreatePipelineLayout"); + var created = new DescriptorLayoutBundle(descriptorSetLayout, pipelineLayout); + _descriptorLayouts.Add(key, created); + return created; + } + + private string GetShaderDigest(byte[] spirv) + { + if (_shaderDigests.TryGetValue(spirv, out var digest)) + { + return digest; + } + + digest = Convert.ToHexString(SHA256.HashData(spirv)); + _shaderDigests.Add(spirv, digest); + return digest; + } + + private static string GetResourceLayoutKey(TranslatedDrawResources resources) + { + var key = new StringBuilder(); + key.Append(resources.GlobalMemoryBuffers.Length).Append(':'); + foreach (var texture in resources.Textures) + { + key.Append(texture.IsStorage ? 'S' : 'T'); + } + + return key.ToString(); + } + + private static string GetVertexLayoutKey(TranslatedDrawResources resources) + { + var key = new StringBuilder(); + foreach (var buffer in resources.VertexBuffers) + { + key.Append(buffer.Location).Append(',') + .Append(buffer.ComponentCount).Append(',') + .Append(buffer.DataFormat).Append(',') + .Append(buffer.NumberFormat).Append(',') + .Append(buffer.Stride == 0 + ? Math.Max(buffer.ComponentCount, 1) * sizeof(float) + : buffer.Stride) + .Append(';'); + } + + return key.ToString(); + } + [MethodImpl(MethodImplOptions.NoInlining)] private void CreateComputePipeline( TranslatedDrawResources resources, @@ -2779,7 +2910,7 @@ internal static unsafe class VulkanVideoPresenter SetDebugName(ObjectType.Buffer, stagingBuffer.Handle, $"{debugName} staging"); SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image"); SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view"); - return new TextureResource + var resource = new TextureResource { Address = texture.Address, StagingBuffer = stagingBuffer, @@ -2795,6 +2926,38 @@ internal static unsafe class VulkanVideoPresenter OwnsStorage = true, SamplerState = texture.Sampler, }; + + if (texture.Address != 0 && + !_guestImages.ContainsKey(texture.Address)) + { + var guestImage = new GuestImageResource + { + Address = texture.Address, + Width = width, + Height = height, + MipLevels = 1, + Format = vkFormat, + Image = image, + Memory = imageMemory, + View = view, + InitialUploadPending = true, + }; + _guestImages.Add(texture.Address, guestImage); + resource.OwnsStorage = false; + resource.GuestImage = guestImage; + lock (_gate) + { + var guestFormat = VulkanVideoPresenter.GetGuestTextureFormat( + texture.Format, + texture.NumberType); + if (guestFormat != 0) + { + _availableGuestImages[texture.Address] = guestFormat; + } + } + } + + return resource; } private void DumpTextureUpload( @@ -2895,6 +3058,11 @@ internal static unsafe class VulkanVideoPresenter private Sampler CreateSampler(VulkanGuestSampler sampler) { + if (_samplers.TryGetValue(sampler, out var cachedSampler)) + { + return cachedSampler; + } + var minLod = DecodeSamplerMipFilter(sampler) == 0 ? 0f : DecodeSamplerMinLod(sampler); @@ -2921,6 +3089,7 @@ internal static unsafe class VulkanVideoPresenter Check( _vk.CreateSampler(_device, &samplerInfo, null, out vkSampler), "vkCreateSampler(texture)"); + _samplers.Add(sampler, vkSampler); return vkSampler; } @@ -3035,11 +3204,31 @@ internal static unsafe class VulkanVideoPresenter out DeviceMemory memory) { var size = (ulong)Math.Max(data.Length, sizeof(uint)); - var buffer = CreateBuffer( - size, - usage, - MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, - out memory); + var capacity = BitOperations.RoundUpToPowerOf2(size); + var key = new HostBufferPoolKey(usage, capacity); + if (!_hostBufferPool.TryGetValue(key, out var available)) + { + available = new Stack(); + _hostBufferPool.Add(key, available); + } + + HostBufferAllocation allocation; + if (available.TryPop(out var pooled)) + { + allocation = pooled; + } + else + { + var buffer = CreateBuffer( + capacity, + usage, + MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, + out var allocatedMemory); + allocation = new HostBufferAllocation(buffer, allocatedMemory, key); + _hostBufferAllocations.Add(buffer.Handle, allocation); + } + + memory = allocation.Memory; void* mapped; Check(_vk.MapMemory(_device, memory, 0, size, 0, &mapped), "vkMapMemory(host)"); try @@ -3058,7 +3247,28 @@ internal static unsafe class VulkanVideoPresenter _vk.UnmapMemory(_device, memory); } - return buffer; + return allocation.Buffer; + } + + private void RecycleHostBuffer(VkBuffer buffer, DeviceMemory memory) + { + if (buffer.Handle == 0) + { + return; + } + + if (_hostBufferAllocations.TryGetValue(buffer.Handle, out var allocation) && + allocation.Memory.Handle == memory.Handle) + { + _hostBufferPool[allocation.Key].Push(allocation); + return; + } + + _vk.DestroyBuffer(_device, buffer, null); + if (memory.Handle != 0) + { + _vk.FreeMemory(_device, memory, null); + } } private static PrimitiveTopology GetPrimitiveTopology(uint primitiveType) => @@ -3608,6 +3818,7 @@ internal static unsafe class VulkanVideoPresenter resources, GetTraceImages(resources)); submitted = true; + MarkSampledImagesInitialized(resources); MarkStorageImagesInitialized(resources, traceContents: false); TraceVulkanShader( $"vk.compute_dispatch groups={work.GroupCountX}x" + @@ -3796,6 +4007,7 @@ internal static unsafe class VulkanVideoPresenter GetTraceImages(resources, target)); submitted = true; target.Initialized = true; + MarkSampledImagesInitialized(resources); MarkStorageImagesInitialized(resources, traceContents: false); var guestTextureFormat = GetGuestTextureFormat(target.Format); @@ -4498,6 +4710,7 @@ internal static unsafe class VulkanVideoPresenter CheckSwapchainResult(presentResult, "vkQueuePresentKHR"); recreateAfterPresent |= presentResult == Result.SuboptimalKhr; Check(_vk.QueueWaitIdle(_queue), "vkQueueWaitIdle"); + VideoOutExports.ReportPresentedFrame(); if (_swapchainReadbackPending) { TraceSwapchainReadback(); @@ -4505,6 +4718,7 @@ internal static unsafe class VulkanVideoPresenter CollectCompletedGuestSubmissions(waitForOldest: false); if (translatedResources is not null) { + MarkSampledImagesInitialized(translatedResources); MarkStorageImagesInitialized(translatedResources); DestroyTranslatedDrawResources(translatedResources); } @@ -5010,6 +5224,27 @@ internal static unsafe class VulkanVideoPresenter } } + private static void MarkSampledImagesInitialized( + TranslatedDrawResources resources) + { + lock (_gate) + { + foreach (var texture in resources.Textures) + { + if (!texture.NeedsUpload || + texture.IsStorage || + texture.Address == 0 || + texture.GuestImage is not { } guestImage) + { + continue; + } + + guestImage.Initialized = true; + guestImage.InitialUploadPending = false; + } + } + } + private bool ShouldTraceGuestImageContents(GuestImageResource image) { if (image.Address == 0) @@ -5248,11 +5483,6 @@ internal static unsafe class VulkanVideoPresenter _vk.FreeMemory(_device, texture.StagingMemory, null); } - if (texture.Sampler.Handle != 0) - { - _vk.DestroySampler(_device, texture.Sampler, null); - } - if (texture.NeedsUpload && texture.GuestImage is { Initialized: false } guestImage) { @@ -5267,15 +5497,7 @@ internal static unsafe class VulkanVideoPresenter continue; } - if (globalBuffer.Buffer.Handle != 0) - { - _vk.DestroyBuffer(_device, globalBuffer.Buffer, null); - } - - if (globalBuffer.Memory.Handle != 0) - { - _vk.FreeMemory(_device, globalBuffer.Memory, null); - } + RecycleHostBuffer(globalBuffer.Buffer, globalBuffer.Memory); } foreach (var vertexBuffer in resources.VertexBuffers) @@ -5285,26 +5507,10 @@ internal static unsafe class VulkanVideoPresenter continue; } - if (vertexBuffer.Buffer.Handle != 0) - { - _vk.DestroyBuffer(_device, vertexBuffer.Buffer, null); - } - - if (vertexBuffer.Memory.Handle != 0) - { - _vk.FreeMemory(_device, vertexBuffer.Memory, null); - } + RecycleHostBuffer(vertexBuffer.Buffer, vertexBuffer.Memory); } - if (resources.IndexBuffer.Handle != 0) - { - _vk.DestroyBuffer(_device, resources.IndexBuffer, null); - } - - if (resources.IndexMemory.Handle != 0) - { - _vk.FreeMemory(_device, resources.IndexMemory, null); - } + RecycleHostBuffer(resources.IndexBuffer, resources.IndexMemory); if (!resources.PipelineCached && resources.Pipeline.Handle != 0) { @@ -5316,12 +5522,14 @@ internal static unsafe class VulkanVideoPresenter _vk.DestroyDescriptorPool(_device, resources.DescriptorPool, null); } - if (resources.PipelineLayout.Handle != 0) + if (!resources.DescriptorLayoutCached && + resources.PipelineLayout.Handle != 0) { _vk.DestroyPipelineLayout(_device, resources.PipelineLayout, null); } - if (resources.DescriptorSetLayout.Handle != 0) + if (!resources.DescriptorLayoutCached && + resources.DescriptorSetLayout.Handle != 0) { _vk.DestroyDescriptorSetLayout(_device, resources.DescriptorSetLayout, null); } @@ -5756,6 +5964,36 @@ internal static unsafe class VulkanVideoPresenter _vk.DestroyPipeline(_device, pipeline, null); } _computePipelines.Clear(); + foreach (var pipeline in _graphicsPipelines.Values) + { + _vk.DestroyPipeline(_device, pipeline, null); + } + _graphicsPipelines.Clear(); + foreach (var layout in _descriptorLayouts.Values) + { + _vk.DestroyPipelineLayout(_device, layout.PipelineLayout, null); + if (layout.DescriptorSetLayout.Handle != 0) + { + _vk.DestroyDescriptorSetLayout( + _device, + layout.DescriptorSetLayout, + null); + } + } + _descriptorLayouts.Clear(); + foreach (var sampler in _samplers.Values) + { + _vk.DestroySampler(_device, sampler, null); + } + _samplers.Clear(); + _shaderDigests.Clear(); + foreach (var allocation in _hostBufferAllocations.Values) + { + _vk.DestroyBuffer(_device, allocation.Buffer, null); + _vk.FreeMemory(_device, allocation.Memory, null); + } + _hostBufferAllocations.Clear(); + _hostBufferPool.Clear(); foreach (var guestImage in _guestImages.Values) { DestroyGuestImage(guestImage);