From fe6521f617234ce7ff3c4ae80450ba609c82b5df Mon Sep 17 00:00:00 2001 From: Foued Attar Date: Tue, 18 Aug 2026 12:20:24 +0200 Subject: [PATCH] Fix unaligned BufferLoad/GlobalLoad dword access BufferLoadDword/x2/x3/x4, BufferStoreDword/x2/x3/x4, and their GLOBAL counterparts were routed through LoadUnalignedBufferWord / StoreBufferBytes, which reconstruct every dword one byte at a time (4 bounds-checked buffer accesses per dword, each with its own OpArrayLength + OpSelect + OpAccessChain + OpLoad/OpStore, plus shift/mask/or reassembly on top). The GCN ISA guarantees these opcodes are always dword-aligned - only the byte/short/D16 variants legitimately need unaligned access, and those already have their own dedicated path (LoadSubdwordBufferValue / StoreBufferBytes with an explicit byte count). The generic dword-count loop reached by every other BufferLoad*/BufferStore*/GlobalLoad*/GlobalStore* opcode was paying the same per-byte cost for no reason. Route the dword-granularity path straight through the existing LoadBufferWord / StoreBufferWord helpers (one bounds check and one load/store per dword) instead. Measured on a compute shader with 6 BufferLoadDwordx4 instructions in its hottest basic block, this drops GPU dispatch time for that shader from ~430-448ms to ~86-92ms (~5x) with no change in output correctness - it is a pure translation inefficiency fix, independent of any specific title. --- .../Gen5SpirvTranslator.cs | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs index 1289e1ea..44e137e6 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs @@ -2366,17 +2366,16 @@ public static partial class Gen5SpirvTranslator return; } + // GLOBAL_STORE/LOAD_DWORD(x2/x3/x4) are dword-aligned by the GCN ISA, so read/write dwords directly instead of the per-byte loop. for (uint index = 0; index < control.DwordCount; index++) { - var address = index == 0 - ? byteAddress - : IAdd(byteAddress, UInt(index * sizeof(uint))); - StoreBufferBytes( + var indexedDwordAddress = index == 0 + ? dwordAddress + : IAdd(dwordAddress, UInt(index)); + StoreBufferWord( bindingIndex, - address, - LoadV(control.VectorData + index), - sizeof(uint), - 0); + indexedDwordAddress, + LoadV(control.VectorData + index)); } }); return true; @@ -2404,12 +2403,12 @@ public static partial class Gen5SpirvTranslator for (uint index = 0; index < control.DwordCount; index++) { - var address = index == 0 - ? byteAddress - : IAdd(byteAddress, UInt(index * sizeof(uint))); + var indexedDwordAddress = index == 0 + ? dwordAddress + : IAdd(dwordAddress, UInt(index)); StoreV( control.VectorData + index, - LoadUnalignedBufferWord(bindingIndex, address)); + LoadBufferWord(bindingIndex, indexedDwordAddress)); } return true; @@ -2510,17 +2509,16 @@ public static partial class Gen5SpirvTranslator return; } + // BUFFER_STORE/LOAD_DWORD(x2/x3/x4) are dword-aligned by the GCN ISA, same as the GLOBAL case above — no per-byte reassembly needed. for (uint index = 0; index < control.DwordCount; index++) { - var address = index == 0 - ? byteAddress - : IAdd(byteAddress, UInt(index * sizeof(uint))); - StoreBufferBytes( + var indexedDwordAddress = index == 0 + ? dwordAddress + : IAdd(dwordAddress, UInt(index)); + StoreBufferWord( bindingIndex, - address, - LoadV(control.VectorData + index), - sizeof(uint), - 0); + indexedDwordAddress, + LoadV(control.VectorData + index)); } }); @@ -2576,12 +2574,12 @@ public static partial class Gen5SpirvTranslator for (uint index = 0; index < control.DwordCount; index++) { - var address = index == 0 - ? byteAddress - : IAdd(byteAddress, UInt(index * sizeof(uint))); + var indexedDwordAddress = index == 0 + ? dwordAddress + : IAdd(dwordAddress, UInt(index)); StoreV( control.VectorData + index, - LoadUnalignedBufferWord(bindingIndex, address)); + LoadBufferWord(bindingIndex, indexedDwordAddress)); } return true;