[shader-decoder] Fix address calculation for SW linear textures

This commit is contained in:
ParantezTech
2026-07-11 05:07:29 +03:00
parent 29021b5a71
commit 8c09b170b6
2 changed files with 200 additions and 43 deletions

View File

@@ -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<uint> _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<ulong> _tracedComputeShaders = new();
private static readonly Dictionary<(ulong Address, uint Width, uint Height), ulong> _tracedTextureHashes = [];
private static readonly HashSet<uint> _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<byte> 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<uint> 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)
{

View File

@@ -168,6 +168,7 @@ internal static unsafe class VulkanVideoPresenter
private static readonly object _gate = new();
private static readonly Queue<object> _pendingGuestWork = new();
private static readonly Dictionary<ulong, uint> _availableGuestImages = new();
private static readonly Dictionary<ulong, uint> _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<byte> 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)