From 6d6a5a1a0dfa6daf872781610ae078a0cbb5a699 Mon Sep 17 00:00:00 2001 From: Berk <12572227+par274@users.noreply.github.com> Date: Wed, 23 Sep 2026 14:05:19 +0300 Subject: [PATCH] [Spirvtranslator] Added many instruction set for SPIR-V translator (#894) --- .../Gen5MslTranslator.Alu.cs | 26 +++- .../Gen5MslTranslator.DataShareSwizzle.cs | 27 ++++ .../Gen5MslTranslator.Pixel.cs | 13 ++ .../Gen5MslTranslator.cs | 6 +- .../Gen5SpirvTranslator.Alu.cs | 98 +++++++++++- .../Gen5SpirvTranslator.DataShareSwizzle.cs | 36 +++++ .../Gen5SpirvTranslator.Resources.cs | 140 ++++++++++++++++++ .../Gen5SpirvTranslator.cs | 69 ++++++++- .../Gen5ShaderTranslator.cs | 63 +++++++- .../Resources/MemoryAccessInfo.cs | 26 +++- .../Resources/ResourceMaterializer.cs | 121 +++++++++++++++ .../Resources/ResourceTracker.cs | 4 +- .../Agc/Gen5ShaderAtomicDecodeTests.cs | 55 +++++++ .../Agc/Gen5SpirvAtomicTranslationTests.cs | 10 ++ .../Gen5ShaderTranslatorTests.cs | 29 ++++ 15 files changed, 700 insertions(+), 23 deletions(-) diff --git a/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Alu.cs b/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Alu.cs index 2a63752..58b0b2f 100644 --- a/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Alu.cs +++ b/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Alu.cs @@ -336,6 +336,7 @@ public static partial class Gen5MslTranslator "VLshlrevB32" => $"(({RawSource(instruction, 1)}) << (({RawSource(instruction, 0)}) & 31u))", "VLshrB32" => $"(({RawSource(instruction, 0)}) >> (({RawSource(instruction, 1)}) & 31u))", "VLshrrevB32" => $"(({RawSource(instruction, 1)}) >> (({RawSource(instruction, 0)}) & 31u))", + "VLshrrevB64" => EmitLshrrevB64(instruction, destination), "VAshrI32" => AsUInt($"(as_type({RawSource(instruction, 0)}) >> (({RawSource(instruction, 1)}) & 31u))"), "VAshrrevI32" => @@ -471,6 +472,14 @@ public static partial class Gen5MslTranslator return $"({AsUInt(signedLeft)} * {AsUInt(signedRight)})"; } + private string EmitLshrrevB64(Gen5ShaderInstruction instruction, uint destination) + { + var shift = Temp("uint", $"({RawSource(instruction, 0)}) & 63u"); + var shifted = Temp("ulong", $"({RawSource64(instruction, 1)}) >> {shift}"); + StoreVector(destination + 1, $"(uint)({shifted} >> 32)"); + return $"(uint){shifted}"; + } + private string EmitCvtPkU8F32(Gen5ShaderInstruction instruction) { var converted = Temp("uint", $"(uint)({F(instruction, 0)})"); @@ -732,7 +741,12 @@ public static partial class Gen5MslTranslator } else { - var signed = opcode.EndsWith("I32", StringComparison.Ordinal); + var signed16 = opcode.EndsWith("I16", StringComparison.Ordinal); + var signed = signed16 || + opcode.EndsWith("I32", StringComparison.Ordinal) || + opcode.EndsWith("I64", StringComparison.Ordinal); + var wide = opcode.EndsWith("I64", StringComparison.Ordinal) || + opcode.EndsWith("U64", StringComparison.Ordinal); var op = TrimCompare(opcode) switch { "Eq" => "==", @@ -749,9 +763,15 @@ public static partial class Gen5MslTranslator return false; } + var left = wide ? RawSource64(instruction, 0) : RawSource(instruction, 0); + var right = wide ? RawSource64(instruction, 1) : RawSource(instruction, 1); condition = signed - ? $"(as_type({RawSource(instruction, 0)}) {op} as_type({RawSource(instruction, 1)}))" - : $"(({RawSource(instruction, 0)}) {op} ({RawSource(instruction, 1)}))"; + ? signed16 + ? $"(int(short(({left}) & 0xFFFFu)) {op} int(short(({right}) & 0xFFFFu)))" + : wide + ? $"(as_type({left}) {op} as_type({right}))" + : $"(as_type({left}) {op} as_type({right}))" + : $"(({left}) {op} ({right}))"; } // Only EXEC-enabled lanes can pass; balloting the raw condition diff --git a/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.DataShareSwizzle.cs b/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.DataShareSwizzle.cs index 423ec5a..7fcec58 100644 --- a/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.DataShareSwizzle.cs +++ b/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.DataShareSwizzle.cs @@ -45,5 +45,32 @@ public static partial class Gen5MslTranslator StoreVector(instruction.Destinations[0].Value, $"{sourceActive} ? {value} : 0u"); return true; } + + private bool TryEmitDataShareBpermute(Gen5ShaderInstruction instruction, + Gen5DataShareControl control, out string error) + { + error = string.Empty; + if (instruction.Sources.Count != 2 || instruction.Destinations.Count != 1) + { + error = "invalid data-share bpermute operands"; + return false; + } + if (IsSingleLaneStage) + { + error = "data-share bpermute requires subgroup lane access"; + return false; + } + + var lane = Temp("uint", "sharpemu_lane"); + var address = Temp("uint", $"{RawSource(instruction, 0)} + {control.SingleOffsetBytes}u"); + var index = Temp("uint", $"({address} >> 2u) & 31u"); + var sourceLane = Temp("uint", $"({lane} & ~31u) | {index}"); + var activeLanes = Temp("uint", "sharpemu_ballot(exec)"); + var sourceActive = Temp("bool", $"(({activeLanes} >> ({sourceLane} & 31u)) & 1u) != 0u"); + var safeSourceLane = Temp("uint", $"{sourceActive} ? {sourceLane} : {lane}"); + var value = Temp("uint", $"simd_shuffle({RawSource(instruction, 1)}, (ushort){safeSourceLane})"); + StoreVector(instruction.Destinations[0].Value, $"{sourceActive} ? {value} : 0u"); + return true; + } } } diff --git a/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Pixel.cs b/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Pixel.cs index b8af129..7aad2cd 100644 --- a/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Pixel.cs +++ b/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.Pixel.cs @@ -23,6 +23,19 @@ public static partial class Gen5MslTranslator out string error) { error = string.Empty; + if (instruction.Opcode is "ImageBvhIntersectRay" or "ImageBvh64IntersectRay") + { + // Metal has no bound representation of GFX10's raw BVH + // descriptor in this backend. Preserve the hardware miss + // fallback used by the Vulkan path and keep the shader valid. + for (uint component = 0; component < 4; component++) + { + StoreVector(image.VectorData + component, "0u"); + } + + return true; + } + string texture; string samplerName; string kind; diff --git a/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.cs b/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.cs index 481078b..167e3b9 100644 --- a/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.cs +++ b/src/SharpEmu.ShaderCompiler.Metal/Gen5MslTranslator.cs @@ -134,7 +134,8 @@ public static partial class Gen5MslTranslator foreach (var instruction in _request.Program.Instructions) { - _usesLds |= instruction.Control is Gen5DataShareControl { Gds: false }; + _usesLds |= instruction.Control is Gen5DataShareControl { Gds: false } && + instruction.Opcode is not ("DsSwizzleB32" or "DsBpermuteB32"); _usesFormatLoads |= IsFormatBufferLoad(instruction.Opcode) || instruction.Opcode.StartsWith("TBufferStoreFormat", StringComparison.Ordinal); if (instruction.Control is Gen5InterpolationControl interpolationControl) @@ -907,6 +908,7 @@ public static partial class Gen5MslTranslator case "STrap": return true; case "SNop": + case "SSetregB32": case "SWaitcnt": case "SInstPrefetch": case "STtraceData": @@ -1332,6 +1334,8 @@ public static partial class Gen5MslTranslator { case "DsSwizzleB32": return TryEmitDataShareSwizzle(instruction, control, out error); + case "DsBpermuteB32": + return TryEmitDataShareBpermute(instruction, control, out error); case "DsAppend": case "DsConsume": { diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs index 0f2ba19..525a104 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Alu.cs @@ -453,6 +453,30 @@ public static partial class Gen5SpirvTranslator _uintType, GetRawSource(instruction, 0)); break; + case "VFfbhU32": + { + var source = GetRawSource(instruction, 0); + var msb = Bitcast( + _uintType, + Ext(75, _intType, source)); + var position = _module.AddInstruction( + SpirvOp.ISub, + _uintType, + UInt(31), + msb); + var nonzero = _module.AddInstruction( + SpirvOp.INotEqual, + _boolType, + source, + UInt(0)); + result = _module.AddInstruction( + SpirvOp.Select, + _uintType, + nonzero, + position, + UInt(uint.MaxValue)); + break; + } case "VFfblB32": result = Bitcast( _uintType, @@ -680,6 +704,31 @@ public static partial class Gen5SpirvTranslator SpirvOp.ShiftRightLogical, reverse: true); break; + case "VLshrrevB64": + { + // V_LSHRREV_B64 writes a VGPR pair. Source 0 supplies the + // shift count; source 1 is the 64-bit value to shift. + var shift = _module.AddInstruction( + SpirvOp.UConvert, + _ulongType, + BitwiseAnd(GetRawSource(instruction, 0), UInt(63))); + var shifted = ShiftRightLogical64( + GetRawSource64(instruction, 1), + shift); + result = _module.AddInstruction( + SpirvOp.UConvert, + _uintType, + shifted); + StoreV( + destination + 1, + _module.AddInstruction( + SpirvOp.UConvert, + _uintType, + ShiftRightLogical64( + shifted, + _module.Constant64(_ulongType, 32)))); + break; + } case "VLshlB32": result = EmitIntegerBinary(instruction, SpirvOp.ShiftLeftLogical); break; @@ -1011,6 +1060,18 @@ public static partial class Gen5SpirvTranslator source)); break; } + case "VAlignbitB32": + { + var high = GetRawSource(instruction, 0); + var low = GetRawSource(instruction, 1); + var shift = BitwiseAnd(GetRawSource(instruction, 2), UInt(31)); + var lowPart = ShiftRightLogical(low, shift); + var inverse = BitwiseAnd(ISubU(UInt(32), shift), UInt(31)); + var highPartRaw = ShiftLeftLogical(high, inverse); + var highPart = SelectU(IsNotZero(shift), highPartRaw, UInt(0)); + result = BitwiseOr(lowPart, highPart); + break; + } case "VCvtPkrtzF16F32": { var first = TruncateFloat32ForPack(GetFloatSource(instruction, 0)); @@ -1791,11 +1852,33 @@ public static partial class Gen5SpirvTranslator var right = compare64 ? GetRawSource64(instruction, 1) : GetRawSource(instruction, 1); - var signed = opcode.EndsWith("I32", StringComparison.Ordinal); + var signed16 = opcode.EndsWith("I16", StringComparison.Ordinal); + var signed = signed16 || + opcode.EndsWith("I32", StringComparison.Ordinal) || + opcode.EndsWith("I64", StringComparison.Ordinal); + if (signed16) + { + left = _module.AddInstruction( + SpirvOp.BitFieldSExtract, + _intType, + Bitcast(_intType, left), + UInt(0), + UInt(16)); + right = _module.AddInstruction( + SpirvOp.BitFieldSExtract, + _intType, + Bitcast(_intType, right), + UInt(0), + UInt(16)); + } if (signed) { - left = Bitcast(_intType, left); - right = Bitcast(_intType, right); + if (!signed16) + { + var signedType = compare64 ? _longType : _intType; + left = Bitcast(signedType, left); + right = Bitcast(signedType, right); + } } var operation = opcode switch @@ -1804,7 +1887,14 @@ public static partial class Gen5SpirvTranslator "VCmpEqU32" or "VCmpxEqU32" => SpirvOp.IEqual, "VCmpNeI32" or "VCmpxNeI32" or "VCmpNeU32" or "VCmpxNeU32" => SpirvOp.INotEqual, - "VCmpxNeU64" => SpirvOp.INotEqual, + "VCmpEqI16" or "VCmpxEqI16" => SpirvOp.IEqual, + "VCmpNeI16" or "VCmpxNeI16" => SpirvOp.INotEqual, + "VCmpNeU64" or "VCmpxNeU64" or + "VCmpNeI64" or "VCmpxNeI64" => SpirvOp.INotEqual, + "VCmpLtI16" or "VCmpxLtI16" => SpirvOp.SLessThan, + "VCmpLeI16" or "VCmpxLeI16" => SpirvOp.SLessThanEqual, + "VCmpGtI16" or "VCmpxGtI16" => SpirvOp.SGreaterThan, + "VCmpGeI16" or "VCmpxGeI16" => SpirvOp.SGreaterThanEqual, "VCmpLtI32" or "VCmpxLtI32" => SpirvOp.SLessThan, "VCmpLeI32" or "VCmpxLeI32" => SpirvOp.SLessThanEqual, "VCmpGtI32" or "VCmpxGtI32" => SpirvOp.SGreaterThan, diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.DataShareSwizzle.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.DataShareSwizzle.cs index 72fae00..51b3e55 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.DataShareSwizzle.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.DataShareSwizzle.cs @@ -61,5 +61,41 @@ public static partial class Gen5SpirvTranslator _module.AddInstruction(SpirvOp.Select, _uintType, sourceActive, value, UInt(0))); return true; } + + private bool TryEmitDataShareBpermute(Gen5ShaderInstruction instruction, + Gen5DataShareControl control, out string error) + { + error = string.Empty; + if (instruction.Sources.Count != 2 || instruction.Destinations.Count != 1) + { + error = "invalid data-share bpermute operands"; + return false; + } + if (_subgroupInvocationIdInput == 0) + { + error = "data-share bpermute requires subgroup lane access"; + return false; + } + + var lane = Load(_uintType, _subgroupInvocationIdInput); + var address = IAdd(GetRawSource(instruction, 0), UInt(control.SingleOffsetBytes)); + var index = BitwiseAnd(ShiftRightLogical(address, UInt(2)), UInt(31)); + var sourceLane = BitwiseOr(BitwiseAnd(lane, UInt(~31u)), index); + + // DS_BPERMUTE reads only from active lanes in the current 32-lane half-wave. + var ballot = _module.AddInstruction(SpirvOp.GroupNonUniformBallot, _uvec4Type, + UInt(3), Load(_boolType, _exec)); + var sourceWord = _module.AddInstruction(SpirvOp.VectorExtractDynamic, _uintType, + ballot, ShiftRightLogical(sourceLane, UInt(5))); + var sourceActive = IsNotZero(BitwiseAnd(ShiftRightLogical(sourceWord, + BitwiseAnd(sourceLane, UInt(31))), UInt(1))); + var safeSourceLane = _module.AddInstruction(SpirvOp.Select, _uintType, + sourceActive, sourceLane, lane); + var value = _module.AddInstruction(SpirvOp.GroupNonUniformShuffle, _uintType, + UInt(3), GetRawSource(instruction, 1), safeSourceLane); + StoreV(instruction.Destinations[0].Value, + _module.AddInstruction(SpirvOp.Select, _uintType, sourceActive, value, UInt(0))); + return true; + } } } diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs index 52d0d8b..0fafe2a 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.Resources.cs @@ -741,6 +741,11 @@ public static partial class Gen5SpirvTranslator { _deviceAddressInstructionPc = instruction.Pc; error = string.Empty; + if (instruction.Opcode.StartsWith("Scratch", StringComparison.Ordinal)) + { + return TryEmitScratchMemory(instruction, control, out error); + } + var request = _request; if (!request.Memory.TryGetIndex(instruction.Pc, 0, out var memoryIndex)) { @@ -842,6 +847,141 @@ public static partial class Gen5SpirvTranslator return true; } + private bool TryEmitScratchMemory( + Gen5ShaderInstruction instruction, + Gen5GlobalMemoryControl control, + out string error) + { + error = string.Empty; + if (_scratch == 0 || _scratchDwordCount == 0) + { + error = "scratch storage was not declared"; + return false; + } + + var address = control.ScalarAddress < 125 + ? LoadS(control.ScalarAddress) + : LoadV(control.VectorAddress); + if (control.OffsetBytes != 0) + { + address = IAdd(address, UInt(unchecked((uint)control.OffsetBytes))); + } + + if (instruction.Opcode.StartsWith("ScratchStore", StringComparison.Ordinal)) + { + EmitExecConditional(() => + { + if (TryGetSubdwordStoreInfo(instruction.Opcode, out var byteCount, out var sourceShift)) + { + StoreScratchBytes(address, LoadV(control.SourceVectorRegister), byteCount, sourceShift); + return; + } + + for (uint index = 0; index < control.DwordCount; index++) + { + Store( + ScratchPointer(address, index * sizeof(uint)), + LoadV(control.SourceVectorRegister + index)); + } + }); + return true; + } + + if (!instruction.Opcode.StartsWith("ScratchLoad", StringComparison.Ordinal)) + { + error = $"unsupported scratch opcode {instruction.Opcode}"; + return false; + } + + EmitExecConditional(() => + { + if (TryGetSubdwordLoadInfo(instruction.Opcode, out var byteCount, out var signExtend, out var d16, out var d16High)) + { + var destination = control.DestinationVectorRegister; + var value = LoadScratchBytes(address, byteCount, signExtend); + if (d16) + { + value = d16High + ? BitwiseOr(BitwiseAnd(LoadV(destination), UInt(0x0000_FFFF)), ShiftLeftLogical(BitwiseAnd(value, UInt(0xFFFF)), UInt(16))) + : BitwiseOr(BitwiseAnd(LoadV(destination), UInt(0xFFFF_0000)), BitwiseAnd(value, UInt(0xFFFF))); + } + + StoreV(destination, value); + return; + } + + for (uint index = 0; index < control.DwordCount; index++) + { + StoreV( + control.DestinationVectorRegister + index, + Load(_uintType, ScratchPointer(address, index * sizeof(uint)))); + } + }); + return true; + } + + private uint ScratchPointer(uint byteAddress, uint offsetBytes = 0) + { + var address = offsetBytes == 0 + ? byteAddress + : IAdd(byteAddress, UInt(offsetBytes)); + var dwordIndex = ShiftRightLogical(address, UInt(2)); + if (_scratchDwordCount > 1) + { + dwordIndex = _module.AddInstruction( + SpirvOp.UMod, + _uintType, + dwordIndex, + UInt(_scratchDwordCount)); + } + + return _module.AddInstruction( + SpirvOp.AccessChain, + _scratchElementPointer, + _scratch, + dwordIndex); + } + + private uint LoadScratchBytes(uint byteAddress, uint byteCount, bool signExtend) + { + var shift = ShiftLeftLogical(BitwiseAnd(byteAddress, UInt(3)), UInt(3)); + var value = BitwiseAnd( + ShiftRightLogical(Load(_uintType, ScratchPointer(byteAddress)), shift), + UInt(byteCount == 1 ? 0xFFu : 0xFFFFu)); + if (signExtend) + { + value = Bitcast( + _uintType, + _module.AddInstruction( + SpirvOp.BitFieldSExtract, + _intType, + Bitcast(_intType, value), + UInt(0), + UInt(byteCount * 8))); + } + + return value; + } + + private void StoreScratchBytes(uint byteAddress, uint value, uint byteCount, uint sourceShift) + { + if (sourceShift != 0) + { + value = ShiftRightLogical(value, UInt(sourceShift)); + } + + var pointer = ScratchPointer(byteAddress); + var shift = ShiftLeftLogical(BitwiseAnd(byteAddress, UInt(3)), UInt(3)); + var elementMask = UInt(byteCount == 1 ? 0xFFu : 0xFFFFu); + var shiftedMask = ShiftLeftLogical(elementMask, shift); + var merged = BitwiseOr( + BitwiseAnd( + Load(_uintType, pointer), + _module.AddInstruction(SpirvOp.Not, _uintType, shiftedMask)), + ShiftLeftLogical(BitwiseAnd(value, elementMask), shift)); + Store(pointer, merged); + } + // ---- buffers ---- private bool TryResolveLayoutBuffer(uint pc, out int bindingIndex, out BufferResource resource) diff --git a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs index 719609c..1459dad 100644 --- a/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs +++ b/src/SharpEmu.ShaderCompiler.Vulkan/Gen5SpirvTranslator.cs @@ -123,6 +123,9 @@ public static partial class Gen5SpirvTranslator private uint _lds; private uint _ldsElementPointer; private uint _ldsDwordMask; + private uint _scratch; + private uint _scratchElementPointer; + private uint _scratchDwordCount; private uint _positionOutput; private uint _pointSizeOutput; private uint _clipDistanceOutput; @@ -570,6 +573,7 @@ public static partial class Gen5SpirvTranslator { DeclareLayoutBindings(); + DeclareScratch(); DeclareLds(); DeclareWave64Scratch(); DeclareStageInterface(); @@ -577,6 +581,26 @@ public static partial class Gen5SpirvTranslator } } + private void DeclareScratch() + { + if (!_request.Program.Instructions.Any(static instruction => + instruction.Opcode.StartsWith("Scratch", StringComparison.Ordinal))) + { + return; + } + + _scratchDwordCount = Math.Max(_request.ScratchDwords, 1u); + var arrayType = _module.TypeArray(_uintType, _scratchDwordCount); + var arrayPointer = _module.TypePointer(SpirvStorageClass.Private, arrayType); + _scratchElementPointer = _module.TypePointer(SpirvStorageClass.Private, _uintType); + _scratch = _module.AddGlobalVariable( + arrayPointer, + SpirvStorageClass.Private, + _module.ConstantNull(arrayType)); + _interfaces.Add(_scratch); + _module.AddName(_scratch, "scratch"); + } + private void DeclareWave64Scratch() { if (!_emulateWave64 || !UsesSubgroupOperations()) @@ -1399,6 +1423,7 @@ public static partial class Gen5SpirvTranslator } if (instruction.Opcode is "SNop" or + "SSetregB32" or "SWaitcnt" or "SInstPrefetch" or "STtraceData" or @@ -1490,9 +1515,7 @@ public static partial class Gen5SpirvTranslator return TryEmitGlobalDataShare(instruction, globalShare, out error); } - if (_lds == 0 || - _ldsElementPointer == 0 || - instruction.Control is not Gen5DataShareControl control) + if (instruction.Control is not Gen5DataShareControl control) { error = "invalid LDS instruction"; return false; @@ -1502,6 +1525,18 @@ public static partial class Gen5SpirvTranslator { case "DsSwizzleB32": return TryEmitDataShareSwizzle(instruction, control, out error); + case "DsBpermuteB32": + return TryEmitDataShareBpermute(instruction, control, out error); + } + + if (_lds == 0 || _ldsElementPointer == 0) + { + error = "invalid LDS instruction"; + return false; + } + + switch (instruction.Opcode) + { case "DsAppend": case "DsConsume": return TryEmitDataShareWaveCounter(instruction, control, out error); @@ -1957,7 +1992,7 @@ public static partial class Gen5SpirvTranslator _uintType, pointer, UInt(scope), - UInt(semantics)); + UInt((semantics & ~0xCu) | 0x2u)); _module.AddStatement(SpirvOp.Branch, header); _module.AddLabel(header); @@ -2015,7 +2050,7 @@ public static partial class Gen5SpirvTranslator _uintType, pointer, UInt(scope), - UInt(semantics)); + UInt((semantics & ~0xCu) | 0x2u)); _module.AddStatement(SpirvOp.Branch, header); _module.AddLabel(header); @@ -2229,8 +2264,11 @@ public static partial class Gen5SpirvTranslator if (instruction.Opcode.StartsWith("BufferAtomic", StringComparison.Ordinal)) { var atomicSuffix = instruction.Opcode["BufferAtomic".Length..]; - if (atomicSuffix == "OrX2") + if (atomicSuffix is "SwapX2" or "OrX2") { + var wideAtomicOp = atomicSuffix == "SwapX2" + ? SpirvOp.AtomicExchange + : SpirvOp.AtomicOr; EmitExecConditional(() => { var secondAddress = IAdd(dwordAddress, UInt(1)); @@ -2242,7 +2280,7 @@ public static partial class Gen5SpirvTranslator EmitConditional(inRange, () => { var originalLow = EmitAtomic( - SpirvOp.AtomicOr, + wideAtomicOp, _uintType, BufferWordPointer(bindingIndex, dwordAddress), scope: 1, @@ -2250,7 +2288,7 @@ public static partial class Gen5SpirvTranslator value: () => LoadV(control.VectorData), comparator: () => UInt(0)); var originalHigh = EmitAtomic( - SpirvOp.AtomicOr, + wideAtomicOp, _uintType, BufferWordPointer(bindingIndex, secondAddress), scope: 1, @@ -3456,6 +3494,21 @@ public static partial class Gen5SpirvTranslator out string error) { error = string.Empty; + if (instruction.Opcode is "ImageBvhIntersectRay" or "ImageBvh64IntersectRay") + { + // The host path does not expose Vulkan ray-query or an + // acceleration-structure descriptor for GFX10's raw BVH + // texture. Return a deterministic miss instead of rejecting + // the complete compute shader. The instruction always writes + // four DWORD result registers. + for (uint component = 0; component < 4; component++) + { + StoreV(image.VectorData + component, UInt(0)); + } + + return true; + } + SpirvImageResource resource; uint imageObject; uint dstSelect; diff --git a/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs b/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs index 8929e2b..8c25b75 100644 --- a/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs +++ b/src/SharpEmu.ShaderCompiler/Gen5ShaderTranslator.cs @@ -776,6 +776,7 @@ public static partial class Gen5ShaderTranslator 0x0E => "SCmpkLeU32", 0x0F => "SAddkI32", 0x10 => "SMulkI32", + 0x13 => "SSetregB32", // RDNA2 uses four SOPK forms to wait for one counter. // The selected counter does not change the translated operation. 0x17 or 0x18 or 0x19 or 0x1A => "SWaitcnt", @@ -824,6 +825,7 @@ public static partial class Gen5ShaderTranslator 0x36 => "VCosF32", 0x37 => "VNotB32", 0x38 => "VBfrevB32", + 0x39 => "VFfbhU32", 0x3A => "VFfblB32", 0x42 => "VMovreldB32", 0x43 => "VMovrelsB32", @@ -962,6 +964,13 @@ public static partial class Gen5ShaderTranslator 0x86 => "VCmpGeI32", 0x87 => "VCmpTI32", 0x88 => "VCmpClassF32", + 0x89 => "VCmpLtI16", + 0x8A => "VCmpEqI16", + 0x8B => "VCmpLeI16", + 0x8C => "VCmpGtI16", + 0x8D => "VCmpNeI16", + 0x8E => "VCmpGeI16", + 0x98 => "VCmpxClassF32", 0x90 => "VCmpxFI32", 0x91 => "VCmpxLtI32", 0x92 => "VCmpxEqI32", @@ -970,6 +979,12 @@ public static partial class Gen5ShaderTranslator 0x95 => "VCmpxNeI32", 0x96 => "VCmpxGeI32", 0x97 => "VCmpxTI32", + 0x99 => "VCmpxLtI16", + 0x9A => "VCmpxEqI16", + 0x9B => "VCmpxLeI16", + 0x9C => "VCmpxGtI16", + 0x9D => "VCmpxNeI16", + 0x9E => "VCmpxGeI16", 0xC0 => "VCmpFU32", 0xC1 => "VCmpLtU32", 0xC2 => "VCmpEqU32", @@ -1002,6 +1017,7 @@ public static partial class Gen5ShaderTranslator 0xDD => "VCmpxLgF16", 0xDE => "VCmpxGeF16", 0xDF => "VCmpxOF16", + 0xE5 => "VCmpNeU64", 0xE8 => "VCmpUF16", 0xE9 => "VCmpNgeF16", 0xEA => "VCmpNlgF16", @@ -1011,6 +1027,7 @@ public static partial class Gen5ShaderTranslator 0xEE => "VCmpNltF16", 0xEF => "VCmpTruF16", 0xF5 => "VCmpxNeU64", + 0xB5 => "VCmpxNeI64", 0xF8 => "VCmpxUF16", 0xF9 => "VCmpxNgeF16", 0xFA => "VCmpxNlgF16", @@ -1051,6 +1068,7 @@ public static partial class Gen5ShaderTranslator } : opcode switch { + 0x0B5 => "VCmpxNeI64", 0x101 => "VCndmaskB32", 0x103 => "VAddF32", 0x104 => "VSubF32", @@ -1068,6 +1086,7 @@ public static partial class Gen5ShaderTranslator 0x146 => "VCubetcF32", 0x147 => "VCubemaF32", 0x14A => "VBfiB32", + 0x14E => "VAlignbitB32", 0x14B => "VFmaF32", 0x151 => "VMin3F32", 0x152 => "VMin3I32", @@ -1109,6 +1128,7 @@ public static partial class Gen5ShaderTranslator 0x36D => "VAdd3U32", 0x36F => "VLshlOrU32", 0x178 => "VXor3B32", + 0x300 => "VLshrrevB64", 0x371 => "VAndOrB32", 0x372 => "VOr3U32", 0x377 => "VPermlane16B32", @@ -1226,6 +1246,7 @@ public static partial class Gen5ShaderTranslator 0x77 => "DsRead2B64", 0xB0 => "DsWriteAddtidB32", 0xB1 => "DsReadAddtidB32", + 0xB3 => "DsBpermuteB32", 0xDE => "DsWriteB96", 0xDF => "DsWriteB128", 0xFE => "DsReadB96", @@ -1345,6 +1366,7 @@ public static partial class Gen5ShaderTranslator 0x3D => "BufferAtomicDec", 0x3F => "BufferAtomicFmin", 0x40 => "BufferAtomicFmax", + 0x50 => "BufferAtomicSwapX2", 0x5A => "BufferAtomicOrX2", _ => $"MubufRaw{opcode:X2}", }; @@ -1366,6 +1388,7 @@ public static partial class Gen5ShaderTranslator var prefix = segment switch { 0x0 => "Flat", + 0x1 => "Scratch", 0x2 => "Global", _ => string.Empty, }; @@ -1457,7 +1480,9 @@ public static partial class Gen5ShaderTranslator private static bool DecodeMimg(uint word, out string name, out uint sizeDwords, out string error) { - var opcode = (word >> 18) & 0x7F; + // RDNA2 MIMG OP[7] is bit 0 while OP[6:0] occupies bits 24:18. + // Ignoring bit 0 aliases 0xE6/0xE7 BVH operations to 0x66/0x67. + var opcode = ((word >> 18) & 0x7F) | ((word & 1) << 7); sizeDwords = 2 + ((word >> 1) & 0x3); error = string.Empty; name = opcode switch @@ -1545,6 +1570,8 @@ public static partial class Gen5ShaderTranslator 0xB9 => "ImageSampleCClAO", 0xBD => "ImageSampleCBAO", 0xBE => "ImageSampleCBClAO", + 0xE6 => "ImageBvhIntersectRay", + 0xE7 => "ImageBvh64IntersectRay", _ => string.Empty, }; @@ -1833,6 +1860,14 @@ public static partial class Gen5ShaderTranslator sources = [.. sources, Gen5Operand.Scalar(scalarSource)]; } } + else if (opcode == "SSetregB32") + { + sources = + [ + Gen5Operand.Scalar((word >> 16) & 0x7F), + new Gen5Operand(Gen5OperandKind.EncodedConstant, word & 0xFFFF), + ]; + } else { destinations = [Gen5Operand.Scalar((word >> 16) & 0x7F)]; @@ -2160,6 +2195,10 @@ public static partial class Gen5ShaderTranslator Gen5Operand.Vector(vectorData1), ], "DsSwizzleB32" => [Gen5Operand.Vector(vectorData0)], + "DsBpermuteB32" => [ + Gen5Operand.Vector(vectorAddress), + Gen5Operand.Vector(vectorData0), + ], // DS_CMPST operand order is reversed vs buffer/image cmpswap: // DATA0 holds the comparator, DATA1 holds the new value. "DsCmpstB32" or "DsCmpstRtnB32" => [ @@ -2185,7 +2224,8 @@ public static partial class Gen5ShaderTranslator "DsAppend" or "DsConsume" => [ Gen5Operand.Vector(vectorDestination), ], - "DsReadB32" or "DsReadI8" or "DsReadAddtidB32" or "DsSwizzleB32" => [ + "DsReadB32" or "DsReadI8" or "DsReadAddtidB32" or + "DsSwizzleB32" or "DsBpermuteB32" => [ Gen5Operand.Vector(vectorDestination), ], "DsReadB64" or "DsRead2B32" or "DsRead2St64B32" => [ @@ -2228,9 +2268,14 @@ public static partial class Gen5ShaderTranslator var usesFlatAddress = opcode.StartsWith( "Flat", StringComparison.Ordinal); + var usesScratchAddress = opcode.StartsWith( + "Scratch", + StringComparison.Ordinal); var memoryOpcode = usesFlatAddress ? "Global" + opcode["Flat".Length..] - : opcode; + : usesScratchAddress + ? "Global" + opcode["Scratch".Length..] + : opcode; var dwordCount = memoryOpcode switch { "GlobalLoadUbyte" or @@ -2265,6 +2310,10 @@ public static partial class Gen5ShaderTranslator Gen5Operand.Vector(vectorAddress), Gen5Operand.Vector(vectorAddress + 1), ] + : usesScratchAddress && scalarAddress < 125 + ? [Gen5Operand.Scalar(scalarAddress)] + : usesScratchAddress + ? [Gen5Operand.Vector(vectorAddress)] : [ Gen5Operand.Vector(vectorAddress), @@ -2343,7 +2392,7 @@ public static partial class Gen5ShaderTranslator "BufferStoreDwordx2" => 2u, "BufferStoreDwordx3" => 3u, "BufferStoreDwordx4" => 4u, - "BufferAtomicCmpswap" or "BufferAtomicOrX2" => 2u, + "BufferAtomicCmpswap" or "BufferAtomicSwapX2" or "BufferAtomicOrX2" => 2u, _ when opcode.StartsWith("BufferAtomic", StringComparison.Ordinal) => 1u, _ => 0u, }; @@ -2442,7 +2491,11 @@ public static partial class Gen5ShaderTranslator sources = imageSources; destinations = opcode.StartsWith("ImageStore", StringComparison.Ordinal) ? [] - : [Gen5Operand.Vector(vectorData)]; + : opcode is "ImageBvhIntersectRay" or "ImageBvh64IntersectRay" + ? Enumerable.Range((int)vectorData, 4) + .Select(index => Gen5Operand.Vector((uint)index)) + .ToArray() + : [Gen5Operand.Vector(vectorData)]; var dimension = (word >> 3) & 0x7; control = new Gen5ImageControl( (word >> 8) & 0xF, diff --git a/src/SharpEmu.ShaderCompiler/Resources/MemoryAccessInfo.cs b/src/SharpEmu.ShaderCompiler/Resources/MemoryAccessInfo.cs index 2ddec13..32148b6 100644 --- a/src/SharpEmu.ShaderCompiler/Resources/MemoryAccessInfo.cs +++ b/src/SharpEmu.ShaderCompiler/Resources/MemoryAccessInfo.cs @@ -158,6 +158,10 @@ public sealed class MemoryAccessTable table.Add(FromImage(instruction, image)); break; case Gen5DataShareControl share: + if (instruction.Opcode is "DsSwizzleB32" or "DsBpermuteB32") + { + break; + } table.Add(new MemoryAccessInfo { Pc = instruction.Pc, @@ -241,7 +245,11 @@ public sealed class MemoryAccessTable { Pc = instruction.Pc, Opcode = opcode, - Kind = control.UsesFlatAddress ? MemoryResourceKind.Flat : MemoryResourceKind.Global, + Kind = opcode.StartsWith("Scratch", StringComparison.Ordinal) + ? MemoryResourceKind.Scratch + : control.UsesFlatAddress + ? MemoryResourceKind.Flat + : MemoryResourceKind.Global, Access = access, Offset = unchecked((uint)control.OffsetBytes), DataDwords = Math.Max(control.DwordCount, 1u), @@ -257,6 +265,22 @@ public sealed class MemoryAccessTable private static MemoryAccessInfo FromImage(Gen5ShaderInstruction instruction, Gen5ImageControl control) { var opcode = instruction.Opcode; + if (opcode is "ImageBvhIntersectRay" or "ImageBvh64IntersectRay") + { + // The current backends return a deterministic miss for raw GFX10 + // BVH traversal. Do not materialize its descriptor as a texture; + // BVH descriptors do not use the regular image descriptor layout. + return new MemoryAccessInfo + { + Pc = instruction.Pc, + Opcode = opcode, + Kind = MemoryResourceKind.None, + Access = MemoryAccess.None, + DataDwords = 4, + ComponentCount = 4, + }; + } + var atomic = opcode.StartsWith("ImageAtomic", StringComparison.Ordinal); var store = opcode.StartsWith("ImageStore", StringComparison.Ordinal); var sampled = opcode.StartsWith("ImageSample", StringComparison.Ordinal) || diff --git a/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializer.cs b/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializer.cs index 13736ad..dd23a5d 100644 --- a/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializer.cs +++ b/src/SharpEmu.ShaderCompiler/Resources/ResourceMaterializer.cs @@ -117,6 +117,7 @@ public static class ResourceMaterializer evaluateTable: true, out var values, out var table, out var activeSources, additionalTableWords: checked(plan.WrittenRangeCount * ShaderResourcePlan.WrittenRangeDwordCount))) { + SpecializationFailed(DiagnoseSnapshotEvaluationFailure(plan, inputs, activeSources)); return false; } @@ -214,6 +215,126 @@ public static class ResourceMaterializer return true; } + private static string DiagnoseSnapshotEvaluationFailure( + ShaderResourcePlan plan, + ResourceRuntimeInputs inputs, + IReadOnlyList activeSources) + { + var cleanEvaluator = new RuntimeValueEvaluator( + plan, + inputs.WithReader(inputs.ReadCleanMemory)); + var evaluator = new RuntimeValueEvaluator( + plan, + inputs, + plan.CleanFlatSlots, + cleanEvaluator); + + foreach (var sourceIndex in plan.MaterializationSources) + { + if (sourceIndex >= plan.DescriptorSources.Count) + { + return $"descriptor source {sourceIndex} is outside the source table"; + } + + if (activeSources.Count != 0 && !activeSources[(int)sourceIndex]) + { + continue; + } + + var source = plan.DescriptorSources[(int)sourceIndex]; + for (var dword = 0; dword < source.Dwords.Length; dword++) + { + if (!evaluator.Evaluate(source.Dwords[dword], out _)) + { + return $"descriptor source {sourceIndex} dword {dword} cannot be evaluated: " + + DescribeEvaluationValue(plan, inputs, evaluator, source.Dwords[dword]); + } + } + } + + foreach (var read in plan.TableReads) + { + var clean = read.FlatOffset < plan.CleanFlatSlots.Count && + plan.CleanFlatSlots[(int)read.FlatOffset] != 0; + var selected = clean ? cleanEvaluator : evaluator; + if (read.FlatOffset >= plan.TableReads.Count || !selected.Evaluate(read.Value, out _)) + { + return $"resource table read {read.FlatOffset} cannot be evaluated: " + + DescribeEvaluationValue(plan, inputs, selected, read.Value); + } + } + + return "descriptor snapshot evaluation failed without an isolated source"; + } + + private static string DescribeEvaluationValue( + ShaderResourcePlan plan, + ResourceRuntimeInputs inputs, + RuntimeValueEvaluator evaluator, + ScalarValue value, + int depth = 0) + { + if (depth >= 6) + { + return value.ToString(); + } + + if (value.Kind == ScalarValueKind.ResourceTableWord) + { + var slot = (int)value.Payload; + if ((uint)slot < plan.TableReads.Count) + { + var read = plan.TableReads[slot]; + return $"table-slot={slot} flat={read.FlatOffset} value=" + + DescribeEvaluationValue(plan, inputs, evaluator, read.Value, depth + 1); + } + + return $"table-slot={slot} (outside table)"; + } + + if (value.Kind is ScalarValueKind.ScalarAddressWord or ScalarValueKind.ScalarBufferWord && + value.MemoryIndex >= 0 && value.MemoryIndex < plan.Memory.Count) + { + var memory = plan.Memory[value.MemoryIndex]; + var detail = $"{value.Kind} memory={value.MemoryIndex} pc=0x{memory.Pc:X8} " + + $"opcode={memory.Opcode} offset={memory.Offset} operands=[{string.Join(", ", value.Operands.Select(operand => + DescribeEvaluationValue(plan, inputs, evaluator, operand, depth + 1)))}]"; + if (value.Operands.Length >= 2 && value.Operands[0].Operands.Length >= 2 && + evaluator.EvaluateWide(value.Operands[0].Operands[0], out var low) && + evaluator.EvaluateWide(value.Operands[0].Operands[1], out var high) && + evaluator.EvaluateWide(value.Operands[1], out var dynamicOffset)) + { + var baseAddress = ((high << 32) | (uint)low) & AddressMask; + var relative = (long)(int)memory.Offset + (uint)dynamicOffset; + if (relative >= 0 && baseAddress <= AddressMask - (ulong)relative) + { + var address = (baseAddress + (ulong)relative) & ~3ul; + var regular = inputs.ReadMemory is not null && inputs.ReadMemory(address, out _); + var clean = inputs.ReadCleanMemory is not null && inputs.ReadCleanMemory(address, out _); + detail += $" base=0x{baseAddress:X16} dynamic=0x{dynamicOffset:X} " + + $"address=0x{address:X16} readable={regular} clean={clean}"; + } + } + + return detail; + } + + if (value.Kind is ScalarValueKind.AddressHandle or ScalarValueKind.BufferHandle or + ScalarValueKind.ImageHandle or ScalarValueKind.SamplerHandle) + { + return $"{value.Kind}[{string.Join(", ", value.Operands.Select(operand => + DescribeEvaluationValue(plan, inputs, evaluator, operand, depth + 1)))}]"; + } + + if (value.Kind is ScalarValueKind.Phi or ScalarValueKind.Select or ScalarValueKind.Operation) + { + return $"{value} operands=[{string.Join(", ", value.Operands.Select(operand => + DescribeEvaluationValue(plan, inputs, evaluator, operand, depth + 1)))}]"; + } + + return value.ToString(); + } + private static bool NullImageDescriptor(ReadOnlySpan descriptor) => descriptor[0] == 0 && (descriptor[1] & 0xFF) == 0; diff --git a/src/SharpEmu.ShaderCompiler/Resources/ResourceTracker.cs b/src/SharpEmu.ShaderCompiler/Resources/ResourceTracker.cs index 894ea6e..0395c28 100644 --- a/src/SharpEmu.ShaderCompiler/Resources/ResourceTracker.cs +++ b/src/SharpEmu.ShaderCompiler/Resources/ResourceTracker.cs @@ -485,7 +485,9 @@ public sealed partial class ResourceTracker { if (memory.Kind == MemoryResourceKind.Scratch) { - throw Failure(memory.Pc, "scratch operations are not supported"); + // Scratch is invocation-private shader storage. It has no host + // resource handle or descriptor-table binding to materialize. + return; } if (access.Handle is null || access.Handle.Kind != ScalarValueKind.AddressHandle) diff --git a/tests/SharpEmu.Libs.Tests/Agc/Gen5ShaderAtomicDecodeTests.cs b/tests/SharpEmu.Libs.Tests/Agc/Gen5ShaderAtomicDecodeTests.cs index b1928a7..ff18975 100644 --- a/tests/SharpEmu.Libs.Tests/Agc/Gen5ShaderAtomicDecodeTests.cs +++ b/tests/SharpEmu.Libs.Tests/Agc/Gen5ShaderAtomicDecodeTests.cs @@ -73,6 +73,19 @@ public sealed class Gen5ShaderAtomicDecodeTests instruction.Destinations); } + [Fact] + public void BufferAtomicSwapX2_UsesTwoDataRegisters() + { + // BUFFER_ATOMIC_SWAP_X2 v[1:2], off, s[0:3], 128 glc + var instruction = DecodeSingle(0xE1404000, 0x80000100); + + Assert.Equal("BufferAtomicSwapX2", instruction.Opcode); + var control = Assert.IsType(instruction.Control); + Assert.Equal(2u, control.DwordCount); + Assert.Equal(1u, control.VectorData); + Assert.Equal(new[] { Gen5Operand.Vector(1), Gen5Operand.Vector(2) }, instruction.Destinations); + } + [Fact] public void ImageAtomicAdd_KeepsDataRegisterAsDestination() { @@ -199,6 +212,48 @@ public sealed class Gen5ShaderAtomicDecodeTests Assert.Empty(instruction.Destinations); } + [Fact] + public void VCmpNeU64_DecodesVectorRegisterPairs() + { + // V_CMP_NE_U64 v[0:1], v[3:4]. + var instruction = DecodeSingle(0x7DCA0700); + + Assert.Equal("VCmpNeU64", instruction.Opcode); + Assert.Equal( + new[] { Gen5Operand.Vector(0), Gen5Operand.Vector(3) }, + instruction.Sources); + Assert.Empty(instruction.Destinations); + } + + [Fact] + public void VFfbhU32_DecodesVop1Opcode39() + { + // V_FFBH_U32 v1, v2. + var instruction = DecodeSingle(0x7E027302); + + Assert.Equal("VFfbhU32", instruction.Opcode); + Assert.Equal(new[] { Gen5Operand.Vector(2) }, instruction.Sources); + Assert.Equal(new[] { Gen5Operand.Vector(1) }, instruction.Destinations); + } + + [Fact] + public void VCmpxClassF32_DecodesVopcOpcode98() + { + var instruction = DecodeSingle(0x7D300702); + + Assert.Equal("VCmpxClassF32", instruction.Opcode); + } + + [Fact] + public void VAlignbitB32_DecodesVop3Opcode14E() + { + var instruction = DecodeSingle(0xD14E0001, 0x040E0502); + + Assert.Equal("VAlignbitB32", instruction.Opcode); + Assert.Equal(new[] { Gen5Operand.Vector(2), Gen5Operand.Vector(2), Gen5Operand.Vector(3) }, instruction.Sources); + Assert.Equal(new[] { Gen5Operand.Vector(1) }, instruction.Destinations); + } + private static Gen5ShaderInstruction DecodeSingle(params uint[] words) { var memory = new FakeCpuMemory(ShaderAddress, 0x1000); diff --git a/tests/SharpEmu.Libs.Tests/Agc/Gen5SpirvAtomicTranslationTests.cs b/tests/SharpEmu.Libs.Tests/Agc/Gen5SpirvAtomicTranslationTests.cs index d7851a1..a6b66b3 100644 --- a/tests/SharpEmu.Libs.Tests/Agc/Gen5SpirvAtomicTranslationTests.cs +++ b/tests/SharpEmu.Libs.Tests/Agc/Gen5SpirvAtomicTranslationTests.cs @@ -59,6 +59,16 @@ public sealed class Gen5SpirvAtomicTranslationTests Assert.Contains((ushort)SpirvOp.AtomicOr, opcodes); } + [Fact] + public void BufferAtomicSwapX2_EmitsAtomicExchange() + { + var opcodes = CompileComputeOpcodes( + [0xE1404000, 0x80000100], + BufferDescriptorRegisters()); + + Assert.Contains((ushort)SpirvOp.AtomicExchange, opcodes); + } + [Fact] public void DataShareAtomics_EmitAtomicOpcodes() { diff --git a/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs b/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs index 45bb695..5c1b218 100644 --- a/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs +++ b/tests/SharpEmu.Libs.Tests/ShaderCompiler/Gen5ShaderTranslatorTests.cs @@ -106,6 +106,35 @@ public sealed class Gen5ShaderTranslatorTests Assert.Equal(Gen5OperandKind.EncodedConstant, instruction.Sources[0].Kind); } + [Fact] + public void SopkSetregB32DecodesScalarSourceWithoutDestination() + { + var memory = new FakeCpuMemory(ProgramAddress, 0x100); + Span code = stackalloc byte[2 * sizeof(uint)]; + BinaryPrimitives.WriteUInt32LittleEndian(code, 0xB9851234u); + BinaryPrimitives.WriteUInt32LittleEndian(code[sizeof(uint)..], 0xBF810000u); + Assert.True(memory.TryWrite(ProgramAddress, code)); + + var context = new CpuContext(memory, Generation.Gen5); + Assert.True( + Gen5ShaderTranslator.TryDecodeProgram( + context, + ProgramAddress, + out var program, + out var error), + error); + + var instruction = program.Instructions[0]; + Assert.Equal(Gen5ShaderEncoding.Sopk, instruction.Encoding); + Assert.Equal("SSetregB32", instruction.Opcode); + Assert.Empty(instruction.Destinations); + Assert.Equal(2, instruction.Sources.Count); + Assert.Equal(Gen5OperandKind.ScalarRegister, instruction.Sources[0].Kind); + Assert.Equal(5u, instruction.Sources[0].Value); + Assert.Equal(Gen5OperandKind.EncodedConstant, instruction.Sources[1].Kind); + Assert.Equal(0x1234u, instruction.Sources[1].Value); + } + [Fact] public void FusedProgramContinuesAfterSetProgramCounter() {