From 5cac7c78120e6c7bf9b1e5a7274255c403fcf264 Mon Sep 17 00:00:00 2001 From: kuba Date: Sun, 4 Oct 2026 11:16:52 +0200 Subject: [PATCH] [GPU] Map half-wave shuffle lanes into the host subgroup --- .../Gen5SpirvTranslator.Alu.cs | 16 ++++++-------- .../Gen5SpirvTranslator.cs | 9 ++++++++ .../Gpu/Vulkan/WaveShuffleDeviceTests.cs | 22 +++++++++++-------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs index 7b0868a..6732552 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs @@ -3955,7 +3955,7 @@ public static partial class Gen5SpirvTranslator { targetLane = BitwiseAnd(targetLane, UInt(31)); } - var shuffled = ShuffleLane(value, targetLane); + var shuffled = ShuffleHalfWaveLane(value, targetLane); if (control.FetchInactive) { return shuffled; @@ -3968,7 +3968,7 @@ public static partial class Gen5SpirvTranslator UInt(1), UInt(0)); var sourceActive = IsNotZero( - ShuffleLane(activeWord, targetLane)); + ShuffleHalfWaveLane(activeWord, targetLane)); return _module.AddInstruction( SpirvOp.Select, _uintType, @@ -3991,7 +3991,7 @@ public static partial class Gen5SpirvTranslator // operations are limited to a single half-wave for some encodings, so we // must not clamp wave64 lanes to 31; use the full lane mask instead. safeTarget = BitwiseAnd(safeTarget, UInt(_waveLaneCount == 64 ? 63u : 31u)); - var shuffled = ShuffleLane(value, safeTarget); + var shuffled = ShuffleHalfWaveLane(value, safeTarget); var sourceAvailable = inRange; if (!control.FetchInactive) @@ -4002,7 +4002,7 @@ public static partial class Gen5SpirvTranslator Load(_boolType, _exec), UInt(1), UInt(0)); - var shuffledActive = ShuffleLane(activeWord, safeTarget); + var shuffledActive = ShuffleHalfWaveLane(activeWord, safeTarget); sourceAvailable = _module.AddInstruction( SpirvOp.LogicalAnd, _boolType, @@ -5124,11 +5124,9 @@ public static partial class Gen5SpirvTranslator } var targetLane = IAdd(rowBase, selector); - // Mask to guest wave size — on Radeon hardware DPP is limited to a - // single half-wave for some encodings, but we must not clamp wave64 - // lanes to 31; use the full lane mask instead. + // Select a guest lane first; ShuffleHalfWaveLane maps it to the host subgroup. targetLane = BitwiseAnd(targetLane, UInt(_waveLaneCount == 64 ? 63u : 31u)); - var shuffled = ShuffleLane(value, targetLane); + var shuffled = ShuffleHalfWaveLane(value, targetLane); var fetchInactive = (control.OperandSelect & 1) != 0; if (fetchInactive) { @@ -5142,7 +5140,7 @@ public static partial class Gen5SpirvTranslator UInt(1), UInt(0)); var sourceActive = IsNotZero( - ShuffleLane(activeWord, targetLane)); + ShuffleHalfWaveLane(activeWord, targetLane)); return _module.AddInstruction( SpirvOp.Select, _uintType, diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs index d0a48f5..a896618 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs @@ -8303,6 +8303,15 @@ public static partial class Gen5SpirvTranslator ? value : _module.AddInstruction(SpirvOp.GroupNonUniformShuffle, _uintType, UInt(3), value, lane); + // DPP row/quad operations and PERMLANE16 never cross a 32-lane half-wave. + // Guest lanes 32..63 belong to the second host subgroup on 32-lane devices; + // using those guest indices directly in OpGroupNonUniformShuffle is undefined. + // Preserve the current physical half on devices whose subgroup is wider than 32. + private uint ShuffleHalfWaveLane(uint value, uint guestLane) => + _subgroupInvocationIdInput == 0 ? value : ShuffleLane(value, + BitwiseOr(BitwiseAnd(Load(_uintType, _subgroupInvocationIdInput), UInt(0xFFFF_FFE0)), + BitwiseAnd(guestLane, UInt(31)))); + private uint CurrentLaneBit() { if (_subgroupInvocationIdInput == 0) diff --git a/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/WaveShuffleDeviceTests.cs b/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/WaveShuffleDeviceTests.cs index bd09267..2ebadd9 100644 --- a/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/WaveShuffleDeviceTests.cs +++ b/tests/SharpEmu.Libs.Tests/Gpu/Vulkan/WaveShuffleDeviceTests.cs @@ -17,12 +17,16 @@ public sealed class WaveShuffleDeviceTests(HeadlessVulkanFixture fixture, ITestO : IClassFixture { [Theory] - [InlineData("Dpp8")] - [InlineData("Dpp16")] - [InlineData("Permlane16")] - [InlineData("Permlanex16")] - [InlineData("Reduction")] - public void Wave64Shuffle_PreservesUpperHalf(string operation) + [InlineData("Dpp8", 32)] + [InlineData("Dpp8", 64)] + [InlineData("Dpp16", 32)] + [InlineData("Dpp16", 64)] + [InlineData("Permlane16", 32)] + [InlineData("Permlane16", 64)] + [InlineData("Permlanex16", 32)] + [InlineData("Permlanex16", 64)] + [InlineData("Reduction", 64)] + public void WaveShuffle_PreservesEachHalf(string operation, uint waveSize) { var vulkan = fixture.Vulkan; if (!GatePrerequisites.Ready(vulkan, shaderInt64: true)) return; @@ -79,7 +83,7 @@ public sealed class WaveShuffleDeviceTests(HeadlessVulkanFixture fixture, ITestO var (plan, resources, layout) = Prepare(Program([.. instructions])); var request = new ShaderCompileRequest(plan, resources, layout) { - LocalSizeX = 8, LocalSizeY = 8, ThreadCountX = 8, ThreadCountY = 8, WaveSize = 64, + LocalSizeX = 8, LocalSizeY = waveSize / 8, ThreadCountX = 8, ThreadCountY = waveSize / 8, WaveSize = waveSize, }; Assert.True(Gen5SpirvTranslator.TryCompileProgram(request, out var shader, out var error), error); using var harness = new ImageTestHarness(vulkan); @@ -90,7 +94,7 @@ public sealed class WaveShuffleDeviceTests(HeadlessVulkanFixture fixture, ITestO harness.Run(() => runner.Dispatch(registers, new Dictionary { [DescriptorBindingKind.Buffers] = [result] }, 1)); var actual = runner.ReadBack(result, 0, 256); - for (uint lane = 0; lane < 64; lane++) + for (uint lane = 0; lane < waveSize; lane++) { var expected = operation switch { @@ -104,6 +108,6 @@ public sealed class WaveShuffleDeviceTests(HeadlessVulkanFixture fixture, ITestO Assert.Equal(expected, BinaryPrimitives.ReadUInt32LittleEndian(actual.AsSpan((int)lane * 4))); } harness.AssertNoValidationMessages(); - output.WriteLine($"Verified {operation} on {vulkan.DeviceName}."); + output.WriteLine($"Verified wave{waveSize} {operation} on {vulkan.DeviceName}."); } }