From e11d8a15c3cdeae15a4efced137c545a7f7cb6a5 Mon Sep 17 00:00:00 2001 From: ParantezTech Date: Sat, 4 Jul 2026 13:41:54 +0300 Subject: [PATCH] [shader-decoder] improve RDNA2 shader decoder to support more instructions and fix some issues with the previous implementation. --- src/SharpEmu.Libs/Agc/AgcExports.cs | 24 ++ .../Agc/Gen5ShaderScalarEvaluator.cs | 73 +++-- src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs | 1 + .../Agc/Gen5SpirvTranslator.Alu.cs | 260 ++++++++++++++++-- 4 files changed, 319 insertions(+), 39 deletions(-) diff --git a/src/SharpEmu.Libs/Agc/AgcExports.cs b/src/SharpEmu.Libs/Agc/AgcExports.cs index bd077a7..7f7cc83 100644 --- a/src/SharpEmu.Libs/Agc/AgcExports.cs +++ b/src/SharpEmu.Libs/Agc/AgcExports.cs @@ -3493,6 +3493,29 @@ public static class AgcExports $"{drawViewport.Width:0.###}x{drawViewport.Height:0.###}:" + $"{drawViewport.MinDepth:0.###}-{drawViewport.MaxDepth:0.###}" : "full"; + var rasterRegisters = new (string Name, uint Offset)[] + { + ("screen_tl", PaScScreenScissorTl), + ("screen_br", PaScScreenScissorBr), + ("window_off", PaScWindowOffset), + ("window_tl", PaScWindowScissorTl), + ("window_br", PaScWindowScissorBr), + ("generic_tl", PaScGenericScissorTl), + ("generic_br", PaScGenericScissorBr), + ("vport_tl", PaScVportScissor0Tl), + ("vport_br", PaScVportScissor0Br), + ("mode", PaScModeCntl0), + ("xscale", PaClVportXScale), + ("xoffset", PaClVportXOffset), + ("yscale", PaClVportYScale), + ("yoffset", PaClVportYOffset), + }; + var raster = string.Join( + ',', + rasterRegisters.Select(entry => + state.CxRegisters.TryGetValue(entry.Offset, out var value) + ? $"{entry.Name}=0x{value:X8}" + : $"{entry.Name}=missing")); var blend = draw.RenderState.Blend; TraceAgcShader( $"agc.shader_draw es=0x{draw.ExportShaderAddress:X16} " + @@ -3500,6 +3523,7 @@ public static class AgcExports $"primitive=0x{draw.PrimitiveType:X} " + $"blend={(blend.Enable ? 1 : 0)}:{blend.ColorSrcFactor}/{blend.ColorDstFactor}/{blend.ColorFunc} " + $"write_mask=0x{blend.WriteMask:X} scissor={scissor} viewport={viewport} " + + $"raster=[{raster}] " + $"ps_ena=0x{psInputEna:X8} ps_addr=0x{psInputAddr:X8} " + $"targets=[{targets}] textures=[{textures}] " + $"buffers=[{buffers}] vertex=[{vertexInputs}] indices=[{indices}]"); diff --git a/src/SharpEmu.Libs/Agc/Gen5ShaderScalarEvaluator.cs b/src/SharpEmu.Libs/Agc/Gen5ShaderScalarEvaluator.cs index 67d7965..990591e 100644 --- a/src/SharpEmu.Libs/Agc/Gen5ShaderScalarEvaluator.cs +++ b/src/SharpEmu.Libs/Agc/Gen5ShaderScalarEvaluator.cs @@ -812,7 +812,11 @@ internal static class Gen5ShaderScalarEvaluator "SFF1I32B32" => left == 0 ? uint.MaxValue : (uint)BitOperations.TrailingZeroCount(left), _ => registers[destination.Value] | (1u << ((int)left & 31)), }; - scalarConditionCode = registers[destination.Value] != 0; + if (instruction.Opcode != "SBitset1B32") + { + scalarConditionCode = registers[destination.Value] != 0; + } + return true; } @@ -838,13 +842,15 @@ internal static class Gen5ShaderScalarEvaluator } case "SSubU32": result = left - right; - scalarConditionCode = left >= right; + scalarConditionCode = right > left; break; case "SAddI32": result = unchecked((uint)((int)left + (int)right)); + scalarConditionCode = SignedAddOverflow(left, right, result); break; case "SSubI32": result = unchecked((uint)((int)left - (int)right)); + scalarConditionCode = SignedSubOverflow(left, right, result); break; case "SAddcU32": { @@ -855,23 +861,27 @@ internal static class Gen5ShaderScalarEvaluator } case "SSubbU32": { - var borrow = scalarConditionCode ? 0UL : 1UL; + var borrow = scalarConditionCode ? 1UL : 0UL; var subtrahend = (ulong)right + borrow; result = unchecked(left - (uint)subtrahend); - scalarConditionCode = left >= subtrahend; + scalarConditionCode = subtrahend > left; break; } case "SMinI32": result = unchecked((uint)Math.Min((int)left, (int)right)); + scalarConditionCode = (int)left < (int)right; break; case "SMinU32": result = Math.Min(left, right); + scalarConditionCode = left < right; break; case "SMaxI32": result = unchecked((uint)Math.Max((int)left, (int)right)); + scalarConditionCode = (int)left > (int)right; break; case "SMaxU32": result = Math.Max(left, right); + scalarConditionCode = left > right; break; case "SCselectB32": result = scalarConditionCode ? left : right; @@ -935,6 +945,7 @@ internal static class Gen5ShaderScalarEvaluator var offset = (int)right & 31; var width = Math.Min(((int)right >> 16) & 0x7F, 32 - offset); result = width == 0 ? 0 : left >> offset & (uint.MaxValue >> (32 - width)); + scalarConditionCode = result != 0; break; } case "SBfeI32": @@ -944,23 +955,41 @@ internal static class Gen5ShaderScalarEvaluator result = width == 0 ? 0 : unchecked((uint)(((int)(left << (32 - width - offset))) >> (32 - width))); + scalarConditionCode = result != 0; break; } case "SAbsdiffI32": result = unchecked((uint)Math.Abs((long)(int)left - (int)right)); + scalarConditionCode = result != 0; break; case "SLshl1AddU32": - result = (left << 1) + right; - break; + { + var wide = ((ulong)left << 1) + right; + result = (uint)wide; + scalarConditionCode = wide > uint.MaxValue; + break; + } case "SLshl2AddU32": - result = (left << 2) + right; - break; + { + var wide = ((ulong)left << 2) + right; + result = (uint)wide; + scalarConditionCode = wide > uint.MaxValue; + break; + } case "SLshl3AddU32": - result = (left << 3) + right; - break; + { + var wide = ((ulong)left << 3) + right; + result = (uint)wide; + scalarConditionCode = wide > uint.MaxValue; + break; + } case "SLshl4AddU32": - result = (left << 4) + right; - break; + { + var wide = ((ulong)left << 4) + right; + result = (uint)wide; + scalarConditionCode = wide > uint.MaxValue; + break; + } case "SPackLlB32B16": result = (left & 0xFFFFu) | (right << 16); break; @@ -1002,7 +1031,8 @@ internal static class Gen5ShaderScalarEvaluator "SNandSaveexecB64" or "SNorSaveexecB64" or "SXnorSaveexecB64" or - "SAndn1SaveexecB64")) + "SAndn1SaveexecB64" or + "SOrn1SaveexecB64")) { return false; } @@ -1030,11 +1060,12 @@ internal static class Gen5ShaderScalarEvaluator "SAndSaveexecB64" => oldExec & source, "SOrSaveexecB64" => oldExec | source, "SXorSaveexecB64" => oldExec ^ source, - "SAndn1SaveexecB64" => ~oldExec & source, - "SAndn2SaveexecB64" => oldExec & ~source, - "SOrn2SaveexecB64" => oldExec | ~source, - "SNandSaveexecB64" => ~(oldExec & source), - "SNorSaveexecB64" => ~(oldExec | source), + "SAndn1SaveexecB64" => ~source & oldExec, + "SAndn2SaveexecB64" => source & ~oldExec, + "SOrn1SaveexecB64" => ~source | oldExec, + "SOrn2SaveexecB64" => source | ~oldExec, + "SNandSaveexecB64" => ~(source & oldExec), + "SNorSaveexecB64" => ~(source | oldExec), _ => ~(oldExec ^ source), }; @@ -1104,6 +1135,12 @@ internal static class Gen5ShaderScalarEvaluator private static ulong MaskWaveValue(ulong value) => value & RdnaWaveMask; + private static bool SignedAddOverflow(uint left, uint right, uint result) => + ((left ^ result) & (right ^ result) & 0x80000000u) != 0; + + private static bool SignedSubOverflow(uint left, uint right, uint result) => + ((left ^ right) & (left ^ result) & 0x80000000u) != 0; + private static bool TryExecuteScalarCompare( Gen5ShaderInstruction instruction, uint[] registers, diff --git a/src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs b/src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs index 143733c..71ec844 100644 --- a/src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs +++ b/src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs @@ -452,6 +452,7 @@ internal static class Gen5ShaderTranslator 0x2A => "SNorSaveexecB64", 0x2B => "SXnorSaveexecB64", 0x37 => "SAndn1SaveexecB64", + 0x38 => "SOrn1SaveexecB64", _ => string.Empty, }; diff --git a/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.Alu.cs b/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.Alu.cs index 21f27fc..5c01072 100644 --- a/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.Alu.cs +++ b/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.Alu.cs @@ -649,6 +649,20 @@ internal static partial class Gen5SpirvTranslator width); break; } + case "VBfiB32": + { + var mask = GetRawSource(instruction, 0); + var insert = GetRawSource(instruction, 1); + var source = GetRawSource(instruction, 2); + result = _module.AddInstruction( + SpirvOp.BitwiseOr, + _uintType, + BitwiseAnd(mask, insert), + BitwiseAnd( + _module.AddInstruction(SpirvOp.Not, _uintType, mask), + source)); + break; + } case "VCvtPkrtzF16F32": { var first = TruncateFloat32ForPack(GetFloatSource(instruction, 0)); @@ -961,6 +975,16 @@ internal static partial class Gen5SpirvTranslator StoreS(destination, result); Store(_scc, IsNotZero(result)); return true; + case "SBitset1B32": + result = _module.AddInstruction( + SpirvOp.BitFieldInsert, + _uintType, + LoadS(destination), + UInt(1), + BitwiseAnd(left, UInt(31)), + UInt(1)); + StoreS(destination, result); + return true; default: { if (instruction.Sources.Count < 2) @@ -987,13 +1011,14 @@ internal static partial class Gen5SpirvTranslator left, right); Store(_scc, _module.AddInstruction( - SpirvOp.UGreaterThanEqual, + SpirvOp.UGreaterThan, _boolType, - left, - right)); + right, + left)); break; case "SAddI32": result = IAdd(left, right); + Store(_scc, SignedAddOverflow(left, right, result)); break; case "SSubI32": result = _module.AddInstruction( @@ -1001,6 +1026,7 @@ internal static partial class Gen5SpirvTranslator _uintType, left, right); + Store(_scc, SignedSubOverflow(left, right, result)); break; case "SAddcU32": { @@ -1037,8 +1063,8 @@ internal static partial class Gen5SpirvTranslator SpirvOp.Select, _uintType, Load(_boolType, _scc), - UInt(0), - UInt(1)); + UInt(1), + UInt(0)); var partial = _module.AddInstruction( SpirvOp.ISub, _uintType, @@ -1049,23 +1075,31 @@ internal static partial class Gen5SpirvTranslator _uintType, partial, borrow); - var firstNoBorrow = _module.AddInstruction( - SpirvOp.UGreaterThanEqual, + var firstBorrow = _module.AddInstruction( + SpirvOp.UGreaterThan, _boolType, - left, - right); - var secondNoBorrow = _module.AddInstruction( - SpirvOp.UGreaterThanEqual, + right, + left); + var secondBorrow = _module.AddInstruction( + SpirvOp.LogicalAnd, _boolType, - partial, - borrow); + _module.AddInstruction( + SpirvOp.IEqual, + _boolType, + borrow, + UInt(1)), + _module.AddInstruction( + SpirvOp.IEqual, + _boolType, + right, + left)); Store( _scc, _module.AddInstruction( - SpirvOp.LogicalAnd, + SpirvOp.LogicalOr, _boolType, - firstNoBorrow, - secondNoBorrow)); + firstBorrow, + secondBorrow)); break; } case "SMulI32": @@ -1077,6 +1111,7 @@ internal static partial class Gen5SpirvTranslator break; case "SAndB32": result = BitwiseAnd(left, right); + Store(_scc, IsNotZero(result)); break; case "SOrB32": result = _module.AddInstruction( @@ -1084,6 +1119,7 @@ internal static partial class Gen5SpirvTranslator _uintType, left, right); + Store(_scc, IsNotZero(result)); break; case "SXorB32": result = _module.AddInstruction( @@ -1091,22 +1127,64 @@ internal static partial class Gen5SpirvTranslator _uintType, left, right); + Store(_scc, IsNotZero(result)); break; case "SAndn2B32": result = BitwiseAnd( left, _module.AddInstruction(SpirvOp.Not, _uintType, right)); + Store(_scc, IsNotZero(result)); + break; + case "SOrn2B32": + result = _module.AddInstruction( + SpirvOp.BitwiseOr, + _uintType, + left, + _module.AddInstruction(SpirvOp.Not, _uintType, right)); + Store(_scc, IsNotZero(result)); + break; + case "SNandB32": + result = _module.AddInstruction( + SpirvOp.Not, + _uintType, + BitwiseAnd(left, right)); + Store(_scc, IsNotZero(result)); + break; + case "SNorB32": + result = _module.AddInstruction( + SpirvOp.Not, + _uintType, + _module.AddInstruction( + SpirvOp.BitwiseOr, + _uintType, + left, + right)); + Store(_scc, IsNotZero(result)); + break; + case "SXnorB32": + result = _module.AddInstruction( + SpirvOp.Not, + _uintType, + _module.AddInstruction( + SpirvOp.BitwiseXor, + _uintType, + left, + right)); + Store(_scc, IsNotZero(result)); break; case "SLshlB32": result = ShiftLeftLogical(left, right); + Store(_scc, IsNotZero(result)); break; case "SLshrB32": result = ShiftRightLogical( left, BitwiseAnd(right, UInt(31))); + Store(_scc, IsNotZero(result)); break; case "SAshrI32": result = ShiftRightArithmetic(left, right); + Store(_scc, IsNotZero(result)); break; case "SBfmB32": result = _module.AddInstruction( @@ -1149,6 +1227,7 @@ internal static partial class Gen5SpirvTranslator left, offset, width); + Store(_scc, IsNotZero(result)); break; } case "SCselectB32": @@ -1161,9 +1240,47 @@ internal static partial class Gen5SpirvTranslator break; case "SMinU32": result = Ext(38, _uintType, left, right); + Store( + _scc, + _module.AddInstruction( + SpirvOp.ULessThan, + _boolType, + left, + right)); + break; + case "SMinI32": + result = Bitcast( + _uintType, + Ext(39, _intType, Bitcast(_intType, left), Bitcast(_intType, right))); + Store( + _scc, + _module.AddInstruction( + SpirvOp.SLessThan, + _boolType, + Bitcast(_intType, left), + Bitcast(_intType, right))); break; case "SMaxU32": result = Ext(41, _uintType, left, right); + Store( + _scc, + _module.AddInstruction( + SpirvOp.UGreaterThan, + _boolType, + left, + right)); + break; + case "SMaxI32": + result = Bitcast( + _uintType, + Ext(42, _intType, Bitcast(_intType, left), Bitcast(_intType, right))); + Store( + _scc, + _module.AddInstruction( + SpirvOp.SGreaterThan, + _boolType, + Bitcast(_intType, left), + Bitcast(_intType, right))); break; case "SLshl1AddU32": case "SLshl2AddU32": @@ -1308,17 +1425,55 @@ internal static partial class Gen5SpirvTranslator "SXorSaveexecB64" => _module.AddInstruction( SpirvOp.BitwiseXor, _ulongType, oldExec, left), "SAndn2SaveexecB64" => _module.AddInstruction( - SpirvOp.BitwiseAnd, _ulongType, oldExec, notLeft), - "SAndn1SaveexecB64" => _module.AddInstruction( SpirvOp.BitwiseAnd, _ulongType, + left, _module.AddInstruction( SpirvOp.Not, _ulongType, - oldExec), - left), + oldExec)), + "SAndn1SaveexecB64" => _module.AddInstruction( + SpirvOp.BitwiseAnd, + _ulongType, + notLeft, + oldExec), + "SOrn1SaveexecB64" => _module.AddInstruction( + SpirvOp.BitwiseOr, + _ulongType, + notLeft, + oldExec), "SOrn2SaveexecB64" => _module.AddInstruction( - SpirvOp.BitwiseOr, _ulongType, oldExec, notLeft), + SpirvOp.BitwiseOr, + _ulongType, + left, + _module.AddInstruction( + SpirvOp.Not, + _ulongType, + oldExec)), + "SNandSaveexecB64" => _module.AddInstruction( + SpirvOp.Not, + _ulongType, + _module.AddInstruction( + SpirvOp.BitwiseAnd, + _ulongType, + left, + oldExec)), + "SNorSaveexecB64" => _module.AddInstruction( + SpirvOp.Not, + _ulongType, + _module.AddInstruction( + SpirvOp.BitwiseOr, + _ulongType, + left, + oldExec)), + "SXnorSaveexecB64" => _module.AddInstruction( + SpirvOp.Not, + _ulongType, + _module.AddInstruction( + SpirvOp.BitwiseXor, + _ulongType, + left, + oldExec)), _ => 0u, }; if (newExec == 0) @@ -1524,6 +1679,22 @@ internal static partial class Gen5SpirvTranslator } StoreS64(destination, value); + if (instruction.Opcode is + "SNotB64" or + "SAndB64" or + "SOrB64" or + "SXorB64" or + "SAndn1B64" or + "SAndn2B64" or + "SOrn1B64" or + "SOrn2B64" or + "SNandB64" or + "SNorB64" or + "SXnorB64") + { + Store(_scc, IsNotZero64(value)); + } + return true; } @@ -2110,6 +2281,53 @@ internal static partial class Gen5SpirvTranslator value, _module.Constant64(_ulongType, 0)); + private uint SignBit(uint value) => + ShiftRightLogical(value, UInt(31)); + + private uint SignedAddOverflow(uint left, uint right, uint result) + { + var leftSign = SignBit(left); + var rightSign = SignBit(right); + var resultSign = SignBit(result); + var sameSourceSign = _module.AddInstruction( + SpirvOp.IEqual, + _boolType, + leftSign, + rightSign); + var resultSignChanged = _module.AddInstruction( + SpirvOp.INotEqual, + _boolType, + leftSign, + resultSign); + return _module.AddInstruction( + SpirvOp.LogicalAnd, + _boolType, + sameSourceSign, + resultSignChanged); + } + + private uint SignedSubOverflow(uint left, uint right, uint result) + { + var leftSign = SignBit(left); + var rightSign = SignBit(right); + var resultSign = SignBit(result); + var differentSourceSign = _module.AddInstruction( + SpirvOp.INotEqual, + _boolType, + leftSign, + rightSign); + var resultSignChanged = _module.AddInstruction( + SpirvOp.INotEqual, + _boolType, + leftSign, + resultSign); + return _module.AddInstruction( + SpirvOp.LogicalAnd, + _boolType, + differentSourceSign, + resultSignChanged); + } + private static bool TryDecodeInlineConstant(uint encoded, out uint value) { if (encoded == 125)