diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs index 004289b..7f3b796 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs @@ -1309,7 +1309,7 @@ public static partial class Gen5SpirvTranslator StoreS(register.Value, value); } - private enum SharedMemoryPhase { None, Read, Write } + private enum SharedMemoryPhase { None, Read, Write, Unknown } private bool TryEmitBlock( IReadOnlyList blocks, @@ -1318,10 +1318,13 @@ public static partial class Gen5SpirvTranslator { error = string.Empty; var block = blocks[blockIndex]; - // One guest wave can span two host subgroups. Keep its shared-memory phases ordered. - // Restrict added barriers to a single block, where all invocations follow the same path. - var synchronizeSharedMemory = _emulateWave64 && blocks.Count == 1; - var sharedMemoryPhase = SharedMemoryPhase.None; + // One guest wave can span two host subgroups; the guest orders its lanes' LDS accesses + // by executing in lockstep, so a change between reading and writing needs a barrier + // between the halves. Every guest branch condition is wave-uniform (EXEC and VCC tests + // go through the wave mask, SCC is scalar), so every block is entered by the whole wave + // and may hold a barrier. Another block may have left either phase behind. + var synchronizeSharedMemory = _emulateWave64; + var sharedMemoryPhase = blocks.Count == 1 ? SharedMemoryPhase.None : SharedMemoryPhase.Unknown; for (var index = block.StartIndex; index < block.EndIndex; index++) { var instruction = _request.Program.Instructions[index]; @@ -6917,14 +6920,35 @@ public static partial class Gen5SpirvTranslator Store(ScalarPointer(register), value); if (register is 106 or 107) { - Store(_vcc, IsWaveMaskActive(LoadS64(106))); + if (_waveLaneCount != 32 || register == 106) + { + Store(_vcc, IsLaneSetInMaskRegisters(106)); + } } else if (register is 126 or 127) { - Store(_exec, IsWaveMaskActive(LoadS64(126))); + if (_waveLaneCount != 32 || register == 126) + { + Store(_exec, IsLaneSetInMaskRegisters(126)); + } } } + // In wave32 a lane mask is its low register alone (VCC_HI and EXEC_HI are ordinary SGPRs), + // so the lane's bit is tested on one dword instead of a composed 64-bit mask. + private uint IsLaneSetInMaskRegisters(uint lowRegister) + { + if (_waveLaneCount != 32) + { + return IsWaveMaskActive(LoadS64(lowRegister)); + } + + var laneBit = _subgroupInvocationIdInput == 0 + ? UInt(1) + : ShiftLeftLogical(UInt(1), GuestWaveLane()); + return IsNotZero(BitwiseAnd(LoadS(lowRegister), laneBit)); + } + private void StoreV(uint register, uint value, bool guardWithExec = true) { if (guardWithExec) diff --git a/tests/SharpEmu.Libs.Tests/Agc/Gen5WaveMaskSpirvTests.cs b/tests/SharpEmu.Libs.Tests/Agc/Gen5WaveMaskSpirvTests.cs index 45e7e6c..88b8f5f 100644 --- a/tests/SharpEmu.Libs.Tests/Agc/Gen5WaveMaskSpirvTests.cs +++ b/tests/SharpEmu.Libs.Tests/Agc/Gen5WaveMaskSpirvTests.cs @@ -22,7 +22,7 @@ public sealed class Gen5WaveMaskSpirvTests // the per-lane _vcc predicate from the wave mask via IsWaveMaskActive. var spirv = Compile([0x7C04_0300u]); - // The lane's bit in single-lane emulation is the 64-bit constant 1, so the + // The lane's bit in single-lane emulation is the constant 1, so the // predicate is `(mask & 1) != 0`. The whole-word bug emitted `mask != 0` // with no such mask. Require the lane-bit AND to be present. Assert.True( @@ -47,26 +47,27 @@ public sealed class Gen5WaveMaskSpirvTests Assert.NotEmpty(spirv); } - // True when the module contains an OpBitwiseAnd whose operand is a 64-bit - // constant of value 1 — the current-lane bit that IsCurrentLaneSet masks the - // wave mask with before the non-zero test. + // True when the module contains an OpBitwiseAnd whose operand is an integer + // constant of value 1 — the current-lane bit that the lane test masks the + // wave mask with before the non-zero test. A wave64 mask is a 64-bit value + // and a wave32 mask is a single dword, so either width counts. private static bool ContainsLaneBitMaskedWaveTest(byte[] spirv) { var laneBitConstIds = new HashSet(); - // Pass 1: collect 64-bit OpConstant result-ids whose value is 1. + // Pass 1: collect OpConstant result-ids whose integer value is 1. foreach (var (op, wordCount, offset) in EnumerateInstructions(spirv)) { - // OpConstant = 43; a 64-bit constant occupies 5 words - // (opcode, resultType, resultId, valueLow, valueHigh). - if (op != 43 || wordCount != 5) + // OpConstant = 43; a 32-bit constant occupies 4 words and a 64-bit one + // 5 (opcode, resultType, resultId, valueLow[, valueHigh]). + if (op != 43 || wordCount is not (4 or 5)) { continue; } var resultId = ReadWord(spirv, offset + 8); var low = ReadWord(spirv, offset + 12); - var high = ReadWord(spirv, offset + 16); + var high = wordCount == 5 ? ReadWord(spirv, offset + 16) : 0u; if (low == 1 && high == 0) { laneBitConstIds.Add(resultId);