From 8c09b170b6a17b74024a807742c9509db2d91d4e Mon Sep 17 00:00:00 2001 From: ParantezTech Date: Sat, 11 Jul 2026 05:07:29 +0300 Subject: [PATCH] [shader-decoder] Fix address calculation for SW linear textures --- src/SharpEmu.Libs/Agc/AgcExports.cs | 65 +++++-- .../VideoOut/VulkanVideoPresenter.cs | 178 +++++++++++++++--- 2 files changed, 200 insertions(+), 43 deletions(-) diff --git a/src/SharpEmu.Libs/Agc/AgcExports.cs b/src/SharpEmu.Libs/Agc/AgcExports.cs index d08c262..223c1d4 100644 --- a/src/SharpEmu.Libs/Agc/AgcExports.cs +++ b/src/SharpEmu.Libs/Agc/AgcExports.cs @@ -142,6 +142,7 @@ public static class AgcExports private const ulong ShaderSpecialGeUserVgprEnOffset = 0x28; private const uint CbSetShRegisterRangeMarker = 0x6875000D; private static readonly object _submitTraceGate = new(); + private static readonly object _textureHashTraceGate = new(); private static readonly HashSet _tracedDcbSizes = new(); private static readonly HashSet<(ulong Es, ulong Ps, GuestDrawKind Kind)> _tracedShaderTranslations = new(); private static readonly HashSet<(ulong Es, ulong Ps)> _tracedShaderDecodePairs = new(); @@ -149,6 +150,7 @@ public static class AgcExports private static readonly HashSet<(ulong Ps, string Error)> _tracedShaderFailures = new(); private static readonly HashSet<(int Handle, int Index, ulong Address, string Path)> _tracedDisplayBuffers = new(); private static readonly HashSet _tracedComputeShaders = new(); + private static readonly Dictionary<(ulong Address, uint Width, uint Height), ulong> _tracedTextureHashes = []; private static readonly HashSet _tracedSubmittedDrawOpcodes = new(); private static readonly Dictionary<(ulong Ps, ulong State, Gen5PixelOutputKind Output), byte[]> _pixelSpirvCache = new(); private static readonly Dictionary< @@ -168,6 +170,10 @@ public static class AgcExports Environment.GetEnvironmentVariable("SHARPEMU_LOG_AGC_SHADER"), "1", StringComparison.Ordinal); + private static readonly bool _traceTextureHashes = string.Equals( + Environment.GetEnvironmentVariable("SHARPEMU_TRACE_TEXTURE_HASHES"), + "1", + StringComparison.Ordinal); private static long _dcbWriteDataTraceCount; private static long _dcbWaitRegMemTraceCount; private static long _createShaderTraceCount; @@ -3747,7 +3753,6 @@ public static class AgcExports var sourceWidth = descriptor.TileMode == 0 ? GetLinearTexturePitch( Math.Max(descriptor.Width, descriptor.Pitch), - descriptor.Height, descriptor.Format) : descriptor.Width; var sourceByteCount = GetTextureByteCount( @@ -3764,7 +3769,7 @@ public static class AgcExports if (!isStorage && descriptor.Address != 0 && - VulkanVideoPresenter.IsGuestImageAvailable( + VulkanVideoPresenter.IsGpuGuestImageAvailable( descriptor.Address, descriptor.Format, descriptor.NumberType)) @@ -3825,6 +3830,8 @@ public static class AgcExports return true; } + TraceTextureHash(descriptor, source); + var nonZero = 0; for (var i = 0; i < source.Length; i++) { @@ -3884,6 +3891,34 @@ public static class AgcExports MipLevel: 0); } + private static void TraceTextureHash(TextureDescriptor descriptor, ReadOnlySpan source) + { + if (!_traceTextureHashes || + descriptor.Address == 0 || + descriptor.Width > 256 || + descriptor.Height > 256) + { + return; + } + + var hash = ComputeFingerprint(source); + var key = (descriptor.Address, descriptor.Width, descriptor.Height); + lock (_textureHashTraceGate) + { + if (_tracedTextureHashes.TryGetValue(key, out var previousHash) && + previousHash == hash) + { + return; + } + + _tracedTextureHashes[key] = hash; + } + + Console.Error.WriteLine( + $"[LOADER][TRACE] agc.texture_hash addr=0x{descriptor.Address:X16} " + + $"size={descriptor.Width}x{descriptor.Height} bytes={source.Length} hash=0x{hash:X16}"); + } + private static VulkanGuestSampler ToVulkanSampler(IReadOnlyList descriptor) => descriptor.Count >= 4 ? new VulkanGuestSampler( @@ -4527,23 +4562,17 @@ public static class AgcExports : checked(((ulong)width + 3) / 4 * (((ulong)height + 3) / 4) * blockBytes); } - private static uint GetLinearTexturePitch(uint pitch, uint height, uint format) + private static uint GetLinearTexturePitch(uint pitch, uint format) { var bytesPerTexel = GetTextureBytesPerTexel(format); - if (bytesPerTexel == 0 || height == 0) + if (bytesPerTexel == 0) { return pitch; } - var pitchAlignment = Math.Max(8UL, 64UL / bytesPerTexel); - var alignedPitch = AlignUp(pitch, pitchAlignment); - var sliceAlignment = Math.Max(64UL, 256UL / bytesPerTexel); - while ((alignedPitch * height) % sliceAlignment != 0) - { - alignedPitch += pitchAlignment; - } - - return checked((uint)alignedPitch); + // GFX10 ADDR_SW_LINEAR aligns each row to 256 bytes. + var pitchAlignment = Math.Max(1UL, 256UL / bytesPerTexel); + return checked((uint)AlignUp(pitch, pitchAlignment)); } private static ulong AlignUp(ulong value, ulong alignment) => @@ -4833,9 +4862,13 @@ public static class AgcExports var type = (fields[3] >> 28) & 0xFu; var baseLevel = (fields[3] >> 12) & 0xFu; var lastLevel = (fields[3] >> 16) & 0xFu; - var pitch = fields.Count >= 5 - ? ((fields[4] >> 13) & 0x3FFFu) + 1 - : width; + // The 128-bit RDNA2 2D resource derives pitch[12:0] from width; + // the optional extension word only supplies pitch[13]. + var pitch = width; + if (fields.Count >= 5) + { + pitch = ((((width - 1) & 0x1FFFu) | (((fields[4] >> 13) & 1u) << 13)) + 1); + } var dstSelect = fields[3] & 0xFFFu; if (address == 0 || width == 0 || height == 0) { diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs index e2a2742..0d5f130 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs @@ -168,6 +168,7 @@ internal static unsafe class VulkanVideoPresenter private static readonly object _gate = new(); private static readonly Queue _pendingGuestWork = new(); private static readonly Dictionary _availableGuestImages = new(); + private static readonly Dictionary _gpuGuestImages = new(); private static readonly HashSet<(ulong Address, uint Width, uint Height)> _tracedGuestImageSubmissions = []; private static Thread? _thread; @@ -600,7 +601,7 @@ internal static unsafe class VulkanVideoPresenter return true; } - internal static bool IsGuestImageAvailable( + internal static bool IsGpuGuestImageAvailable( ulong address, uint format, uint numberType) @@ -613,7 +614,7 @@ internal static unsafe class VulkanVideoPresenter lock (_gate) { - return _availableGuestImages.TryGetValue(address, out var availableFormat) && + return _gpuGuestImages.TryGetValue(address, out var availableFormat) && availableFormat == guestFormat; } } @@ -971,6 +972,8 @@ internal static unsafe class VulkanVideoPresenter public VulkanGuestSampler SamplerState; public Sampler Sampler; public GuestImageResource? GuestImage; + public ulong CpuContentFingerprint; + public bool UpdatesCpuContent; } private sealed class GlobalBufferResource @@ -1009,6 +1012,8 @@ internal static unsafe class VulkanVideoPresenter public Framebuffer Framebuffer; public bool Initialized; public bool InitialUploadPending; + public bool IsCpuBacked; + public ulong CpuContentFingerprint; } private sealed record PendingGuestSubmission( @@ -2838,6 +2843,11 @@ internal static unsafe class VulkanVideoPresenter $"tile={texture.TileMode} format={vkFormat}"); } + if (TryCreateCpuTextureRefreshResource(texture, guestImage, view, out var refresh)) + { + return refresh; + } + return new TextureResource { Address = texture.Address, @@ -2855,6 +2865,71 @@ internal static unsafe class VulkanVideoPresenter return CreateTextureResource(texture); } + private bool TryCreateCpuTextureRefreshResource( + VulkanGuestDrawTexture texture, + GuestImageResource guestImage, + ImageView view, + out TextureResource resource) + { + resource = default!; + if (!guestImage.IsCpuBacked || + guestImage.Width != texture.Width || + guestImage.Height != texture.Height || + guestImage.MipLevels != 1 || + texture.RgbaPixels.Length == 0) + { + return false; + } + + var rowLength = texture.TileMode == 0 + ? Math.Max(texture.Pitch, texture.Width) + : texture.Width; + var expectedSize = GetTextureByteCount(texture.Format, rowLength, texture.Height); + if (expectedSize == 0 || expectedSize > int.MaxValue) + { + return false; + } + + var pixels = texture.RgbaPixels.Length == (int)expectedSize + ? texture.RgbaPixels + : CreateFallbackTexturePixels(texture.Format, rowLength, texture.Height, expectedSize); + var fingerprint = ComputeTextureContentFingerprint(pixels); + if ((guestImage.Initialized || guestImage.InitialUploadPending) && + guestImage.CpuContentFingerprint == fingerprint) + { + return false; + } + + var uploadPixels = texture.Format == 13 + ? ExpandRgb32Pixels(pixels) + : pixels; + var debugName = TextureDebugName(texture, guestImage.Format); + var (stagingBuffer, stagingMemory) = CreateTextureStagingBuffer( + uploadPixels, + $"{debugName} refresh staging"); + TraceVulkanShader( + $"vk.texture_refresh addr=0x{texture.Address:X16} " + + $"size={texture.Width}x{texture.Height} bytes={uploadPixels.Length}"); + resource = new TextureResource + { + Address = texture.Address, + StagingBuffer = stagingBuffer, + StagingMemory = stagingMemory, + Image = guestImage.Image, + View = view, + Width = guestImage.Width, + Height = guestImage.Height, + RowLength = rowLength, + DstSelect = texture.DstSelect, + NeedsUpload = true, + SamplerState = texture.Sampler, + GuestImage = guestImage, + CpuContentFingerprint = fingerprint, + UpdatesCpuContent = true, + }; + return true; + } + private static bool IsCompatibleGuestImageAlias( VulkanGuestDrawTexture texture, GuestImageResource guestImage) @@ -3096,24 +3171,11 @@ internal static unsafe class VulkanVideoPresenter var uploadPixels = texture.Format == 13 ? ExpandRgb32Pixels(pixels) : pixels; - var uploadSize = (ulong)uploadPixels.Length; + var contentFingerprint = ComputeTextureContentFingerprint(pixels); - var stagingBuffer = CreateBuffer( - uploadSize, - BufferUsageFlags.TransferSrcBit, - MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, - out var stagingMemory); - void* mapped; - Check(_vk.MapMemory(_device, stagingMemory, 0, uploadSize, 0, &mapped), "vkMapMemory(texture)"); - fixed (byte* source = uploadPixels) - { - System.Buffer.MemoryCopy( - source, - mapped, - uploadPixels.Length, - uploadPixels.Length); - } - _vk.UnmapMemory(_device, stagingMemory); + var (stagingBuffer, stagingMemory) = CreateTextureStagingBuffer( + uploadPixels, + $"{TextureDebugName(texture, vkFormat)} staging"); var supportsAttachmentUsage = !IsBlockCompressedFormat(vkFormat); var imageInfo = new ImageCreateInfo @@ -3163,7 +3225,6 @@ internal static unsafe class VulkanVideoPresenter }; Check(_vk.CreateImageView(_device, &viewInfo, null, out var view), "vkCreateImageView(texture)"); var debugName = TextureDebugName(texture, vkFormat); - SetDebugName(ObjectType.Buffer, stagingBuffer.Handle, $"{debugName} staging"); SetDebugName(ObjectType.Image, image.Handle, $"{debugName} image"); SetDebugName(ObjectType.ImageView, view.Handle, $"{debugName} view"); var resource = new TextureResource @@ -3181,6 +3242,8 @@ internal static unsafe class VulkanVideoPresenter NeedsUpload = true, OwnsStorage = true, SamplerState = texture.Sampler, + CpuContentFingerprint = contentFingerprint, + UpdatesCpuContent = texture.Address != 0, }; if (texture.Address != 0 && @@ -3197,6 +3260,8 @@ internal static unsafe class VulkanVideoPresenter Memory = imageMemory, View = view, InitialUploadPending = true, + IsCpuBacked = true, + CpuContentFingerprint = contentFingerprint, }; _guestImages.Add(texture.Address, guestImage); resource.OwnsStorage = false; @@ -3209,6 +3274,7 @@ internal static unsafe class VulkanVideoPresenter if (guestFormat != 0) { _availableGuestImages[texture.Address] = guestFormat; + _gpuGuestImages.Remove(texture.Address); } } } @@ -3216,6 +3282,41 @@ internal static unsafe class VulkanVideoPresenter return resource; } + private (VkBuffer Buffer, DeviceMemory Memory) CreateTextureStagingBuffer( + byte[] pixels, + string debugName) + { + var size = (ulong)pixels.Length; + var buffer = CreateBuffer( + size, + BufferUsageFlags.TransferSrcBit, + MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit, + out var memory); + void* mapped; + Check(_vk.MapMemory(_device, memory, 0, size, 0, &mapped), "vkMapMemory(texture)"); + fixed (byte* source = pixels) + { + System.Buffer.MemoryCopy(source, mapped, pixels.Length, pixels.Length); + } + _vk.UnmapMemory(_device, memory); + SetDebugName(ObjectType.Buffer, buffer.Handle, debugName); + return (buffer, memory); + } + + private static ulong ComputeTextureContentFingerprint(ReadOnlySpan pixels) + { + const ulong offsetBasis = 14695981039346656037; + const ulong prime = 1099511628211; + var hash = offsetBasis; + foreach (var value in pixels) + { + hash ^= value; + hash *= prime; + } + + return hash; + } + private void DumpTextureUpload( VulkanGuestDrawTexture texture, byte[] pixels, @@ -4267,12 +4368,13 @@ internal static unsafe class VulkanVideoPresenter RecordTextureUploads(resources, PipelineStageFlags.FragmentShaderBit); RecordStorageImagesForWrite(resources, PipelineStageFlags.FragmentShaderBit); + var targetHasPriorContents = target.Initialized || target.InitialUploadPending; var toColorAttachment = new ImageMemoryBarrier { SType = StructureType.ImageMemoryBarrier, - SrcAccessMask = target.Initialized ? AccessFlags.ShaderReadBit : 0, + SrcAccessMask = targetHasPriorContents ? AccessFlags.ShaderReadBit : 0, DstAccessMask = AccessFlags.ColorAttachmentWriteBit, - OldLayout = target.Initialized + OldLayout = targetHasPriorContents ? ImageLayout.ShaderReadOnlyOptimal : ImageLayout.Undefined, NewLayout = ImageLayout.ColorAttachmentOptimal, @@ -4283,8 +4385,8 @@ internal static unsafe class VulkanVideoPresenter }; _vk.CmdPipelineBarrier( _commandBuffer, - target.Initialized - ? PipelineStageFlags.FragmentShaderBit + targetHasPriorContents + ? PipelineStageFlags.AllCommandsBit : PipelineStageFlags.TopOfPipeBit, PipelineStageFlags.ColorAttachmentOutputBit, 0, @@ -4337,12 +4439,15 @@ internal static unsafe class VulkanVideoPresenter MarkSampledImagesInitialized(resources); MarkStorageImagesInitialized(resources, traceContents: false); - var guestTextureFormat = GetGuestTextureFormat(target.Format); + var guestTextureFormat = VulkanVideoPresenter.GetGuestTextureFormat( + work.Target.Format, + work.Target.NumberType); if (work.PublishTarget && guestTextureFormat != 0) { lock (_gate) { _availableGuestImages[target.Address] = guestTextureFormat; + _gpuGuestImages[target.Address] = guestTextureFormat; } } if (ShouldTraceGuestImageWriteForDiagnostics(target.Address)) @@ -4384,6 +4489,7 @@ internal static unsafe class VulkanVideoPresenter lock (_gate) { _availableGuestImages.Remove(work.Target.Address); + _gpuGuestImages.Remove(work.Target.Address); } } @@ -4423,6 +4529,8 @@ internal static unsafe class VulkanVideoPresenter existing.MipLevels == mipLevels && existing.Format == format) { + existing.IsCpuBacked = false; + existing.CpuContentFingerprint = 0; if (existing.RenderPass.Handle == 0) { var attachmentView = existing.MipViews.Length > 0 @@ -4448,6 +4556,7 @@ internal static unsafe class VulkanVideoPresenter lock (_gate) { _availableGuestImages.Remove(target.Address); + _gpuGuestImages.Remove(target.Address); } } @@ -5386,11 +5495,18 @@ internal static unsafe class VulkanVideoPresenter continue; } + var hasPriorContents = texture.GuestImage is { } guestImage && + (guestImage.Initialized || guestImage.InitialUploadPending); var toTransfer = new ImageMemoryBarrier { SType = StructureType.ImageMemoryBarrier, + SrcAccessMask = hasPriorContents + ? AccessFlags.ShaderReadBit + : 0, DstAccessMask = AccessFlags.TransferWriteBit, - OldLayout = ImageLayout.Undefined, + OldLayout = hasPriorContents + ? ImageLayout.ShaderReadOnlyOptimal + : ImageLayout.Undefined, NewLayout = ImageLayout.TransferDstOptimal, SrcQueueFamilyIndex = Vk.QueueFamilyIgnored, DstQueueFamilyIndex = Vk.QueueFamilyIgnored, @@ -5399,7 +5515,9 @@ internal static unsafe class VulkanVideoPresenter }; _vk.CmdPipelineBarrier( _commandBuffer, - PipelineStageFlags.TopOfPipeBit, + hasPriorContents + ? PipelineStageFlags.AllCommandsBit + : PipelineStageFlags.TopOfPipeBit, PipelineStageFlags.TransferBit, 0, 0, @@ -5569,6 +5687,7 @@ internal static unsafe class VulkanVideoPresenter if (format != 0) { _availableGuestImages[texture.Address] = format; + _gpuGuestImages[texture.Address] = format; } if (traceContents && @@ -5608,6 +5727,10 @@ internal static unsafe class VulkanVideoPresenter guestImage.Initialized = true; guestImage.InitialUploadPending = false; + if (texture.UpdatesCpuContent) + { + guestImage.CpuContentFingerprint = texture.CpuContentFingerprint; + } } } } @@ -6379,6 +6502,7 @@ internal static unsafe class VulkanVideoPresenter lock (_gate) { _availableGuestImages.Clear(); + _gpuGuestImages.Clear(); } DestroySwapchainResources(); if (_device.Handle != 0)