mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-01 07:29:43 +08:00
[AGC] Implement RDNA2 buffer/image/DS 32-bit atomic instructions (#222)
Adds decode and SPIR-V translation for the missing MUBUF, MIMG and DS atomic instructions in the Gen5 shader translator, generalizing the existing BufferAtomicAdd path. Covers swap, cmpswap, add, sub, smin/smax, umin/umax, and/or/xor, inc and dec, plus the DS RTN variants. Image atomics go through OpImageTexelPointer on the storage image binding. Notable: DS_CMPST operand order (DATA0 = comparator, DATA1 = new value) is reversed relative to buffer/image cmpswap, which a dedicated test locks in. ATOMIC_INC/DEC are approximated with OpAtomicIIncrement/IDecrement, exact for the common 0xFFFFFFFF clamp. Verified with 9 new synthetic decoder and end-to-end SPIR-V tests (part of the #36 test corpus effort); full suite passes 35/35. Signed-off-by: missatjuhvdk1 <177474143+missatjuhvdk1@users.noreply.github.com> Co-authored-by: missatjuhvdk1 <177474143+missatjuhvdk1@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
f2d9051358
commit
9883a9445d
@@ -1802,24 +1802,6 @@ public static partial class Gen5SpirvTranslator
|
||||
|
||||
switch (instruction.Opcode)
|
||||
{
|
||||
case "DsAddU32":
|
||||
{
|
||||
if (instruction.Sources.Count < 2)
|
||||
{
|
||||
error = "missing LDS atomic-add source";
|
||||
return false;
|
||||
}
|
||||
|
||||
var address = GetRawSource(instruction, 0);
|
||||
_module.AddInstruction(
|
||||
SpirvOp.AtomicIAdd,
|
||||
_uintType,
|
||||
LdsPointer(address, control.Offset0),
|
||||
UInt(2), // Workgroup scope.
|
||||
UInt(0x108), // AcquireRelease | WorkgroupMemory.
|
||||
GetRawSource(instruction, 1));
|
||||
return true;
|
||||
}
|
||||
case "DsWriteB32":
|
||||
{
|
||||
if (instruction.Sources.Count < 2)
|
||||
@@ -1964,6 +1946,11 @@ public static partial class Gen5SpirvTranslator
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
if (Gen5ShaderTranslator.IsDataShareAtomic(instruction.Opcode))
|
||||
{
|
||||
return TryEmitDataShareAtomic(instruction, control, out error);
|
||||
}
|
||||
|
||||
error = $"unsupported LDS opcode {instruction.Opcode}";
|
||||
return false;
|
||||
}
|
||||
@@ -2004,6 +1991,126 @@ public static partial class Gen5SpirvTranslator
|
||||
Store(pointer, selected);
|
||||
}
|
||||
|
||||
private bool TryEmitDataShareAtomic(
|
||||
Gen5ShaderInstruction instruction,
|
||||
Gen5DataShareControl control,
|
||||
out string error)
|
||||
{
|
||||
error = string.Empty;
|
||||
var atomicOp = instruction.Opcode switch
|
||||
{
|
||||
"DsAddU32" or "DsAddRtnU32" => SpirvOp.AtomicIAdd,
|
||||
"DsSubU32" or "DsSubRtnU32" => SpirvOp.AtomicISub,
|
||||
"DsIncU32" or "DsIncRtnU32" => SpirvOp.AtomicIIncrement,
|
||||
"DsDecU32" or "DsDecRtnU32" => SpirvOp.AtomicIDecrement,
|
||||
"DsMinI32" or "DsMinRtnI32" => SpirvOp.AtomicSMin,
|
||||
"DsMaxI32" or "DsMaxRtnI32" => SpirvOp.AtomicSMax,
|
||||
"DsMinU32" or "DsMinRtnU32" => SpirvOp.AtomicUMin,
|
||||
"DsMaxU32" or "DsMaxRtnU32" => SpirvOp.AtomicUMax,
|
||||
"DsAndB32" or "DsAndRtnB32" => SpirvOp.AtomicAnd,
|
||||
"DsOrB32" or "DsOrRtnB32" => SpirvOp.AtomicOr,
|
||||
"DsXorB32" or "DsXorRtnB32" => SpirvOp.AtomicXor,
|
||||
"DsWrxchgRtnB32" => SpirvOp.AtomicExchange,
|
||||
"DsCmpstB32" or "DsCmpstRtnB32" => SpirvOp.AtomicCompareExchange,
|
||||
_ => SpirvOp.Nop,
|
||||
};
|
||||
if (atomicOp == SpirvOp.Nop)
|
||||
{
|
||||
error = $"unsupported LDS opcode {instruction.Opcode}";
|
||||
return false;
|
||||
}
|
||||
|
||||
var address = GetRawSource(instruction, 0);
|
||||
var pointer = LdsPointer(address, control.Offset0);
|
||||
EmitExecConditional(() =>
|
||||
{
|
||||
var original = EmitAtomic(
|
||||
atomicOp,
|
||||
_uintType,
|
||||
pointer,
|
||||
scope: 2,
|
||||
semantics: 0x108,
|
||||
// DS_CMPST sources: DATA0 is the comparator, DATA1 the new value.
|
||||
value: () => GetRawSource(
|
||||
instruction,
|
||||
atomicOp == SpirvOp.AtomicCompareExchange ? 2 : 1),
|
||||
comparator: () => GetRawSource(instruction, 1));
|
||||
if (instruction.Destinations.Count > 0)
|
||||
{
|
||||
StoreV(instruction.Destinations[0].Value, original);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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.
|
||||
private static bool TryGetAtomicOp(string name, out SpirvOp op)
|
||||
{
|
||||
op = name switch
|
||||
{
|
||||
"Swap" => SpirvOp.AtomicExchange,
|
||||
"Cmpswap" => SpirvOp.AtomicCompareExchange,
|
||||
"Add" => SpirvOp.AtomicIAdd,
|
||||
"Sub" => SpirvOp.AtomicISub,
|
||||
"Smin" => SpirvOp.AtomicSMin,
|
||||
"Umin" => SpirvOp.AtomicUMin,
|
||||
"Smax" => SpirvOp.AtomicSMax,
|
||||
"Umax" => SpirvOp.AtomicUMax,
|
||||
"And" => SpirvOp.AtomicAnd,
|
||||
"Or" => SpirvOp.AtomicOr,
|
||||
"Xor" => SpirvOp.AtomicXor,
|
||||
"Inc" => SpirvOp.AtomicIIncrement,
|
||||
"Dec" => SpirvOp.AtomicIDecrement,
|
||||
_ => SpirvOp.Nop,
|
||||
};
|
||||
return op != SpirvOp.Nop;
|
||||
}
|
||||
|
||||
private uint EmitAtomic(
|
||||
SpirvOp op,
|
||||
uint type,
|
||||
uint pointer,
|
||||
uint scope,
|
||||
uint semantics,
|
||||
Func<uint> value,
|
||||
Func<uint> comparator)
|
||||
{
|
||||
if (op is SpirvOp.AtomicIIncrement or SpirvOp.AtomicIDecrement)
|
||||
{
|
||||
return _module.AddInstruction(
|
||||
op,
|
||||
type,
|
||||
pointer,
|
||||
UInt(scope),
|
||||
UInt(semantics));
|
||||
}
|
||||
|
||||
if (op == SpirvOp.AtomicCompareExchange)
|
||||
{
|
||||
// The unequal semantics must not contain Release; downgrade it to Acquire.
|
||||
return _module.AddInstruction(
|
||||
op,
|
||||
type,
|
||||
pointer,
|
||||
UInt(scope),
|
||||
UInt(semantics),
|
||||
UInt((semantics & ~0x8u) | 0x2u),
|
||||
value(),
|
||||
comparator());
|
||||
}
|
||||
|
||||
return _module.AddInstruction(
|
||||
op,
|
||||
type,
|
||||
pointer,
|
||||
UInt(scope),
|
||||
UInt(semantics),
|
||||
value());
|
||||
}
|
||||
|
||||
private bool TryEmitInterpolation(
|
||||
Gen5ShaderInstruction instruction,
|
||||
Gen5InterpolationControl interpolation,
|
||||
@@ -2239,22 +2346,27 @@ public static partial class Gen5SpirvTranslator
|
||||
byteAddress = ApplyGuestBufferByteBias(bindingIndex, byteAddress);
|
||||
var dwordAddress = ShiftRightLogical(byteAddress, UInt(2));
|
||||
|
||||
if (instruction.Opcode is "BufferAtomicAdd" or "BufferAtomicUMax")
|
||||
if (instruction.Opcode.StartsWith("BufferAtomic", StringComparison.Ordinal))
|
||||
{
|
||||
if (!TryGetAtomicOp(instruction.Opcode["BufferAtomic".Length..], out var atomicOp))
|
||||
{
|
||||
error = $"unsupported buffer opcode {instruction.Opcode}";
|
||||
return false;
|
||||
}
|
||||
|
||||
EmitExecConditional(() =>
|
||||
{
|
||||
var inRange = IsBufferWordInRange(bindingIndex, dwordAddress);
|
||||
EmitConditional(inRange, () =>
|
||||
{
|
||||
var original = _module.AddInstruction(
|
||||
instruction.Opcode == "BufferAtomicAdd"
|
||||
? SpirvOp.AtomicIAdd
|
||||
: SpirvOp.AtomicUMax,
|
||||
var original = EmitAtomic(
|
||||
atomicOp,
|
||||
_uintType,
|
||||
BufferWordPointer(bindingIndex, dwordAddress),
|
||||
UInt(1),
|
||||
UInt(0x48),
|
||||
LoadV(control.VectorData));
|
||||
scope: 1,
|
||||
semantics: 0x48,
|
||||
value: () => LoadV(control.VectorData),
|
||||
comparator: () => LoadV(control.VectorData + 1));
|
||||
if (control.Glc)
|
||||
{
|
||||
StoreV(control.VectorData, original);
|
||||
@@ -3196,6 +3308,60 @@ public static partial class Gen5SpirvTranslator
|
||||
return true;
|
||||
}
|
||||
|
||||
if (instruction.Opcode.StartsWith("ImageAtomic", StringComparison.Ordinal))
|
||||
{
|
||||
if (!resource.IsStorage)
|
||||
{
|
||||
error = "image atomic is not bound as storage";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resource.ComponentKind == ImageComponentKind.Float ||
|
||||
!TryGetAtomicOp(instruction.Opcode["ImageAtomic".Length..], out var atomicOp))
|
||||
{
|
||||
error = $"unsupported storage image opcode {instruction.Opcode}";
|
||||
return false;
|
||||
}
|
||||
|
||||
var signed = resource.ComponentKind == ImageComponentKind.Sint;
|
||||
var atomicImageSize = _module.AddInstruction(
|
||||
SpirvOp.ImageQuerySize,
|
||||
_module.TypeVector(_intType, 2),
|
||||
imageObject);
|
||||
var coordinates = BuildClampedIntegerCoordinates(
|
||||
image,
|
||||
0,
|
||||
atomicImageSize);
|
||||
EmitExecConditional(() =>
|
||||
{
|
||||
var pointer = _module.AddInstruction(
|
||||
SpirvOp.ImageTexelPointer,
|
||||
_module.TypePointer(SpirvStorageClass.Image, resource.ComponentType),
|
||||
resource.Variable,
|
||||
coordinates,
|
||||
UInt(0));
|
||||
uint LoadData(uint register) => signed
|
||||
? Bitcast(_intType, LoadV(register))
|
||||
: LoadV(register);
|
||||
var original = EmitAtomic(
|
||||
atomicOp,
|
||||
resource.ComponentType,
|
||||
pointer,
|
||||
scope: 1,
|
||||
semantics: 0x808,
|
||||
value: () => LoadData(image.VectorData),
|
||||
comparator: () => LoadData(image.VectorData + 1));
|
||||
if (image.Glc)
|
||||
{
|
||||
StoreV(
|
||||
image.VectorData,
|
||||
signed ? Bitcast(_uintType, original) : original);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (resource.IsStorage &&
|
||||
instruction.Opcode is not ("ImageLoad" or "ImageLoadMip"))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user