diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs index 00e5303..bb3e5b6 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs @@ -1251,6 +1251,29 @@ public static partial class Gen5SpirvTranslator private bool TryEmitGlobalDataShareAtomic(Gen5ShaderInstruction instruction, Gen5DataShareControl control, out string error) { error = string.Empty; + if (instruction.Opcode is "DsMinF32" or "DsMaxF32") + { + if (instruction.Sources.Count < 3) + { + error = $"missing GDS operands for {instruction.Opcode}"; + return false; + } + + var floatIndex = GlobalDataShareIndex(GetRawSource(instruction, 0), control.SingleOffsetBytes); + EmitExecConditional(() => + { + EmitConditional(IsBlockWordInRange(_globalDataShare, floatIndex), () => + EmitDataShareFloatAtomic( + BlockWordPointer(_globalDataShare, floatIndex), + GetRawSource(instruction, 1), + GetRawSource(instruction, 2), + instruction.Opcode == "DsMaxF32", + scope: 1, + semantics: 0x48)); + }); + return true; + } + var atomicOp = instruction.Opcode switch { "DsAddU32" or "DsAddRtnU32" => SpirvOp.AtomicIAdd, diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs index 023c9db..4ee8e19 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs @@ -1844,6 +1844,27 @@ public static partial class Gen5SpirvTranslator out string error) { error = string.Empty; + if (instruction.Opcode is "DsMinF32" or "DsMaxF32") + { + if (instruction.Sources.Count < 3) + { + error = $"missing LDS operands for {instruction.Opcode}"; + return false; + } + + var floatAddress = GetRawSource(instruction, 0); + var floatPointer = LdsPointer(floatAddress, control.SingleOffsetBytes); + EmitExecConditional(() => + EmitDataShareFloatAtomic( + floatPointer, + GetRawSource(instruction, 1), + GetRawSource(instruction, 2), + instruction.Opcode == "DsMaxF32", + scope: 2, + semantics: 0x108)); + return true; + } + var atomicOp = instruction.Opcode switch { "DsAddU32" or "DsAddRtnU32" => SpirvOp.AtomicIAdd, @@ -1891,6 +1912,65 @@ public static partial class Gen5SpirvTranslator return true; } + private void EmitDataShareFloatAtomic( + uint pointer, + uint data, + uint compare, + bool maxValue, + uint scope, + uint semantics) + { + var preheader = _module.AllocateId(); + var header = _module.AllocateId(); + var continueLabel = _module.AllocateId(); + var mergeLabel = _module.AllocateId(); + var exchanged = _module.AllocateId(); + + _module.AddStatement(SpirvOp.Branch, preheader); + _module.AddLabel(preheader); + var initial = _module.AddInstruction( + SpirvOp.AtomicLoad, + _uintType, + pointer, + UInt(scope), + UInt(semantics)); + _module.AddStatement(SpirvOp.Branch, header); + + _module.AddLabel(header); + var observed = _module.AddInstruction( + SpirvOp.Phi, + _uintType, + initial, + preheader, + exchanged, + continueLabel); + var observedFloat = Bitcast(_floatType, observed); + var compareFloat = Bitcast(_floatType, compare); + var replace = _module.AddInstruction( + maxValue ? SpirvOp.FOrdGreaterThan : SpirvOp.FOrdLessThan, + _boolType, + maxValue ? observedFloat : compareFloat, + maxValue ? compareFloat : observedFloat); + var next = _module.AddInstruction(SpirvOp.Select, _uintType, replace, data, observed); + + _module.AddStatement( + SpirvOp.AtomicCompareExchange, + _uintType, + exchanged, + pointer, + UInt(scope), + UInt(semantics), + UInt((semantics & ~0x8u) | 0x2u), + next, + observed); + var success = _module.AddInstruction(SpirvOp.IEqual, _boolType, exchanged, observed); + _module.AddStatement(SpirvOp.LoopMerge, mergeLabel, continueLabel, 0); + _module.AddStatement(SpirvOp.BranchConditional, success, mergeLabel, continueLabel); + _module.AddLabel(continueLabel); + _module.AddStatement(SpirvOp.Branch, header); + _module.AddLabel(mergeLabel); + } + // Maps the AMD atomic-op name suffix shared by buffer/image atomics to a SPIR-V opcode. // Inc/Dec approximate the AMD wrap-clamp semantics (MEM = tmp >= DATA ? 0 : tmp + 1), // which is exact for the common 0xFFFFFFFF clamp operand. diff --git a/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs b/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs index cf5be67..818176d 100644 --- a/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs +++ b/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs @@ -1196,6 +1196,8 @@ public static partial class Gen5ShaderTranslator 0x0E => "DsWrite2B32", 0x0F => "DsWrite2St64B32", 0x10 => "DsCmpstB32", + 0x12 => "DsMinF32", + 0x13 => "DsMaxF32", 0x20 => "DsAddRtnU32", 0x21 => "DsSubRtnU32", 0x23 => "DsIncRtnU32", @@ -1683,6 +1685,7 @@ public static partial class Gen5ShaderTranslator { "DsAddU32" or "DsSubU32" or "DsIncU32" or "DsDecU32" or "DsMinI32" or "DsMaxI32" or "DsMinU32" or "DsMaxU32" or + "DsMinF32" or "DsMaxF32" or "DsAndB32" or "DsOrB32" or "DsXorB32" or "DsCmpstB32" or "DsAddRtnU32" or "DsSubRtnU32" or "DsIncRtnU32" or "DsDecRtnU32" or "DsMinRtnI32" or "DsMaxRtnI32" or "DsMinRtnU32" or "DsMaxRtnU32" or @@ -2159,6 +2162,13 @@ public static partial class Gen5ShaderTranslator Gen5Operand.Vector(vectorData0), Gen5Operand.Vector(vectorData1), ], + // GFX10 DS_MIN/MAX_F32 use DATA0 as the replacement value and + // DATA1 as the floating-point compare operand. + "DsMinF32" or "DsMaxF32" => [ + Gen5Operand.Vector(vectorAddress), + Gen5Operand.Vector(vectorData0), + Gen5Operand.Vector(vectorData1), + ], _ when IsDataShareAtomic(opcode) => [ Gen5Operand.Vector(vectorAddress), Gen5Operand.Vector(vectorData0), diff --git a/tests/SharpEmu.Libs.Tests/Agc/Gen5ShaderAtomicDecodeTests.cs b/tests/SharpEmu.Libs.Tests/Agc/Gen5ShaderAtomicDecodeTests.cs index 9bf8a6e..7a10c8d 100644 --- a/tests/SharpEmu.Libs.Tests/Agc/Gen5ShaderAtomicDecodeTests.cs +++ b/tests/SharpEmu.Libs.Tests/Agc/Gen5ShaderAtomicDecodeTests.cs @@ -84,6 +84,21 @@ public sealed class Gen5ShaderAtomicDecodeTests Assert.Equal(new[] { Gen5Operand.Vector(3) }, instruction.Destinations); } + [Theory] + [InlineData(0xD8480004u, "DsMinF32")] + [InlineData(0xD84C0008u, "DsMaxF32")] + public void DsFloatMinMax_KeepReplacementAndCompareOperands(uint word, string opcode) + { + // DATA0 is the replacement value and DATA1 is the float compare operand. + var instruction = DecodeSingle(word, 0x00010907); + + Assert.Equal(opcode, instruction.Opcode); + Assert.Equal( + new[] { Gen5Operand.Vector(7), Gen5Operand.Vector(9), Gen5Operand.Vector(1) }, + instruction.Sources); + Assert.Empty(instruction.Destinations); + } + [Fact] public void DsCmpstRtnB32_OrdersComparatorBeforeNewValue() {