[SpirvTranslator] Add DsMinF32 and DsMaxF32 instructions

This commit is contained in:
ParantezTech 2026-09-20 19:35:08 +03:00
parent 776c97cebc
commit 537ecb83e0
4 changed files with 128 additions and 0 deletions

View File

@ -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,

View File

@ -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.

View File

@ -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),

View File

@ -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()
{