diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs index bb3e5b6..52d0d8b 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs @@ -1158,6 +1158,24 @@ public static partial class Gen5SpirvTranslator case "DsReadB32": StoreV(instruction.Destinations[0].Value, LoadBlockWord(_globalDataShare, GlobalDataShareIndex(GetRawSource(instruction, 0), control.SingleOffsetBytes))); return true; + case "DsReadI8": + { + var address = GetRawSource(instruction, 0); + var byteAddress = control.SingleOffsetBytes == 0 + ? address + : IAdd(address, UInt(control.SingleOffsetBytes)); + var word = LoadBlockWord(_globalDataShare, GlobalDataShareIndex(address, control.SingleOffsetBytes)); + var shift = ShiftLeftLogical(BitwiseAnd(byteAddress, UInt(3)), UInt(3)); + var packed = ShiftRightLogical(word, shift); + var signedByte = _module.AddInstruction( + SpirvOp.BitFieldSExtract, + _intType, + Bitcast(_intType, packed), + UInt(0), + UInt(8)); + StoreV(instruction.Destinations[0].Value, Bitcast(_uintType, signedByte)); + return true; + } case "DsReadB64": { var index = GlobalDataShareIndex(GetRawSource(instruction, 0), control.SingleOffsetBytes); diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs index 4ee8e19..1529b9b 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs @@ -1610,6 +1610,30 @@ public static partial class Gen5SpirvTranslator StoreV(instruction.Destinations[0].Value, value); return true; } + case "DsReadI8": + { + if (instruction.Destinations.Count < 1 || instruction.Sources.Count < 1) + { + error = "missing LDS signed byte read operand"; + return false; + } + + var address = GetRawSource(instruction, 0); + var byteAddress = control.SingleOffsetBytes == 0 + ? address + : IAdd(address, UInt(control.SingleOffsetBytes)); + var word = Load(_uintType, LdsPointer(address, control.SingleOffsetBytes)); + var shift = ShiftLeftLogical(BitwiseAnd(byteAddress, UInt(3)), UInt(3)); + var packed = ShiftRightLogical(word, shift); + var signedByte = _module.AddInstruction( + SpirvOp.BitFieldSExtract, + _intType, + Bitcast(_intType, packed), + UInt(0), + UInt(8)); + StoreV(instruction.Destinations[0].Value, Bitcast(_uintType, signedByte)); + return true; + } case "DsReadB64": case "DsReadB96": case "DsReadB128": @@ -1971,6 +1995,65 @@ public static partial class Gen5SpirvTranslator _module.AddLabel(mergeLabel); } + private uint EmitBufferFloatAtomic( + uint pointer, + uint value, + 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 valueFloat = Bitcast(_floatType, value); + var replace = _module.AddInstruction( + maxValue ? SpirvOp.FOrdGreaterThan : SpirvOp.FOrdLessThan, + _boolType, + valueFloat, + observedFloat); + var next = _module.AddInstruction(SpirvOp.Select, _uintType, replace, value, 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); + return observed; + } + // 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. @@ -2145,7 +2228,70 @@ public static partial class Gen5SpirvTranslator if (instruction.Opcode.StartsWith("BufferAtomic", StringComparison.Ordinal)) { - if (!TryGetAtomicOp(instruction.Opcode["BufferAtomic".Length..], out var atomicOp)) + var atomicSuffix = instruction.Opcode["BufferAtomic".Length..]; + if (atomicSuffix == "OrX2") + { + EmitExecConditional(() => + { + var secondAddress = IAdd(dwordAddress, UInt(1)); + var inRange = _module.AddInstruction( + SpirvOp.LogicalAnd, + _boolType, + IsBufferWordInRange(bindingIndex, dwordAddress), + IsBufferWordInRange(bindingIndex, secondAddress)); + EmitConditional(inRange, () => + { + var originalLow = EmitAtomic( + SpirvOp.AtomicOr, + _uintType, + BufferWordPointer(bindingIndex, dwordAddress), + scope: 1, + semantics: 0x48, + value: () => LoadV(control.VectorData), + comparator: () => UInt(0)); + var originalHigh = EmitAtomic( + SpirvOp.AtomicOr, + _uintType, + BufferWordPointer(bindingIndex, secondAddress), + scope: 1, + semantics: 0x48, + value: () => LoadV(control.VectorData + 1), + comparator: () => UInt(0)); + if (control.Glc) + { + StoreV(control.VectorData, originalLow); + StoreV(control.VectorData + 1, originalHigh); + } + }); + }); + + return true; + } + + if (atomicSuffix is "Fmin" or "Fmax") + { + EmitExecConditional(() => + { + var inRange = IsBufferWordInRange(bindingIndex, dwordAddress); + EmitConditional(inRange, () => + { + var original = EmitBufferFloatAtomic( + BufferWordPointer(bindingIndex, dwordAddress), + LoadV(control.VectorData), + maxValue: atomicSuffix == "Fmax", + scope: 1, + semantics: 0x48); + if (control.Glc) + { + StoreV(control.VectorData, original); + } + }); + }); + + return true; + } + + if (!TryGetAtomicOp(atomicSuffix, out var atomicOp)) { error = $"unsupported buffer opcode {instruction.Opcode}"; return false; diff --git a/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs b/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs index 7cd6b1e..8929e2b 100644 --- a/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs +++ b/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs @@ -1216,6 +1216,7 @@ public static partial class Gen5ShaderTranslator 0x36 => "DsReadB32", 0x37 => "DsRead2B32", 0x38 => "DsRead2St64B32", + 0x39 => "DsReadI8", 0x3D => "DsConsume", 0x3E => "DsAppend", 0x4D => "DsWriteB64", @@ -1342,6 +1343,9 @@ public static partial class Gen5ShaderTranslator 0x3B => "BufferAtomicXor", 0x3C => "BufferAtomicInc", 0x3D => "BufferAtomicDec", + 0x3F => "BufferAtomicFmin", + 0x40 => "BufferAtomicFmax", + 0x5A => "BufferAtomicOrX2", _ => $"MubufRaw{opcode:X2}", }; sizeDwords = (extra >> 24) == 0xFF ? 3u : 2u; @@ -2181,7 +2185,7 @@ public static partial class Gen5ShaderTranslator "DsAppend" or "DsConsume" => [ Gen5Operand.Vector(vectorDestination), ], - "DsReadB32" or "DsReadAddtidB32" or "DsSwizzleB32" => [ + "DsReadB32" or "DsReadI8" or "DsReadAddtidB32" or "DsSwizzleB32" => [ Gen5Operand.Vector(vectorDestination), ], "DsReadB64" or "DsRead2B32" or "DsRead2St64B32" => [ @@ -2339,7 +2343,7 @@ public static partial class Gen5ShaderTranslator "BufferStoreDwordx2" => 2u, "BufferStoreDwordx3" => 3u, "BufferStoreDwordx4" => 4u, - "BufferAtomicCmpswap" => 2u, + "BufferAtomicCmpswap" or "BufferAtomicOrX2" => 2u, _ when opcode.StartsWith("BufferAtomic", StringComparison.Ordinal) => 1u, _ => 0u, };