Files
sharpemu/src/SharpEmu.Libs/Agc/Gen5ShaderTranslator.cs
Berk 52e17b5056 [shader-decoder-part1] Implemented a shader decoder (Part 1) (#12)
* [shader-decoder-part1] Implemented a shader decoder for Gen5 shaders, including IR generation, metadata reading, scalar evaluation, and SPIR-V translation. Updated related exports and video output components to support the new shader decoding functionality.

* [shader decoder] correct RDNA2 operands, fixing synchronization problems

* [shader-decoder] RDNA2 decoder improvements

* [shader-decoder] fix RDNA2 shift masking and sprite draws

* [shader-decoder] improve RDNA2 shader decoder to support more instructions and fix some issues with the previous implementation.
2026-07-04 13:51:08 +03:00

1858 lines
64 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.VideoOut;
using System.Buffers.Binary;
using System.Text;
namespace SharpEmu.Libs.Agc;
internal static class Gen5ShaderTranslator
{
private const int MaxInstructions = 4096;
private const int MinimumUserDataDwords = 16;
private const int MaximumUserDataDwords = 256;
private static readonly uint[] FullscreenBarycentricEs =
[
0xBFA00001, 0x7E000000, 0x7E000000, 0x7E000000,
0x93EBFF03, 0x00080008, 0x8F6A8C6B, 0x8700FF03,
0x000000FF, 0x887C6A00, 0xBF900009, 0x81EA6BC0,
0x90FE6AC1, 0xF8000941, 0x00000000, 0x81EA00C0,
0xBF8CFF0F, 0x90FE6AC1, 0x36040A81, 0x2C060A81,
0x7E000280, 0x7E0202F2, 0xD7460002, 0x03050302,
0xD7460003, 0x03050303, 0x7E040B02, 0x7E060B03,
0xF80008CF, 0x01000302, 0xBF810000,
];
private static readonly uint[] FullscreenBarycentricPs =
[
0xD52F0000, 0x00000200,
0xD52F0001, 0x00000602,
0xF8001C0F, 0x00000100,
0xBF810000,
];
private static readonly uint[] Gen5RectListExportEs =
[
0xBFA00001, 0x7E000000, 0x7E000000, 0x7E000000,
0x9380FF03, 0x00080008, 0x8F6A8C00, 0x876BFF03,
0x000000FF, 0x887C6A6B, 0xBF900009, 0x81EA6BC0,
0x90FE6AC1, 0x36060A81, 0x2C080A81, 0x7E020280,
0x7E0402F2, 0xD7460003, 0x03050303, 0xD7460004,
0x03050304, 0x7E060B03, 0x7E080B04, 0xF80008CF,
0x02010403, 0x81EA00C0, 0xBF8CFF0F, 0x90FE6AC1,
0xF8000941, 0x00000000, 0xBF810000,
];
private static readonly uint[] Gen5QuadExportEs =
[
0xBEFC03FF, 0x61937B18, 0xBF960000, 0xBFA00002,
0x93EBFF03, 0x00080008, 0x8F6A8C6B, 0x8700FF03,
0x000000FF, 0x887C6A00, 0xBF900009, 0x81EA6BC0,
0x90FE6AC1, 0xF8000941, 0x00000000, 0x81EA00C0,
0xBF8CFF0F, 0x90FE6AC1, 0x2C080A81, 0x36040A81,
0x7E0002F2, 0x7E020280, 0xD5690005, 0x000208C2,
0xD7460003, 0x03050302, 0x7E040B02, 0x7E080B04,
0x4A0A0A81, 0x7E060B03, 0x7E0A0B05, 0xF80008CF,
0x00000503, 0xF800020F, 0x01010402, 0xBF810000,
];
public static bool TryTranslate(
CpuContext ctx,
ulong exportShaderAddress,
ulong pixelShaderAddress,
uint psInputEna,
uint psInputAddr,
out GuestDrawKind drawKind)
{
drawKind = GuestDrawKind.None;
if (exportShaderAddress == 0 ||
pixelShaderAddress == 0 ||
psInputEna != 0x00000002 ||
psInputAddr != 0x00000002 ||
!MatchesProgram(ctx, exportShaderAddress, FullscreenBarycentricEs) ||
!MatchesProgram(ctx, pixelShaderAddress, FullscreenBarycentricPs))
{
return false;
}
drawKind = GuestDrawKind.FullscreenBarycentric;
return true;
}
public static bool IsFullscreenExportShader(CpuContext ctx, ulong exportShaderAddress) =>
exportShaderAddress != 0 &&
(MatchesProgram(ctx, exportShaderAddress, FullscreenBarycentricEs) ||
MatchesProgram(ctx, exportShaderAddress, Gen5RectListExportEs) ||
MatchesProgram(ctx, exportShaderAddress, Gen5QuadExportEs));
public static string Describe(CpuContext ctx, ulong exportShaderAddress, ulong pixelShaderAddress)
{
var es = TryDecodeProgram(ctx, exportShaderAddress, out var esProgram, out var esError)
? ShaderDecodeInfo.Create(esProgram).ToString()
: $"error={esError}";
var ps = TryDecodeProgram(ctx, pixelShaderAddress, out var psProgram, out var psError)
? ShaderDecodeInfo.Create(psProgram).ToString()
: $"error={psError}";
return $"es[{es}] ps[{ps}]";
}
public static string DescribeWords(CpuContext ctx, ulong shaderAddress) =>
TryDecodeProgram(ctx, shaderAddress, out var program, out var error)
? string.Join(',', program.Instructions.SelectMany(instruction => instruction.Words)
.Select(word => $"{word:X8}"))
: $"error={error}";
public static bool TryCreateState(
CpuContext ctx,
ulong shaderAddress,
ulong shaderHeaderAddress,
IReadOnlyDictionary<uint, uint> shaderRegisters,
uint userDataBaseRegister,
out Gen5ShaderState state,
out string error,
Gen5ComputeSystemRegisters? computeSystemRegisters = null,
uint userDataScalarRegisterBase = 0)
{
state = default!;
if (!TryDecodeProgram(ctx, shaderAddress, out var program, out error))
{
return false;
}
Gen5ShaderMetadata? metadata = null;
if (shaderHeaderAddress != 0 &&
Gen5ShaderMetadataReader.TryRead(ctx, shaderHeaderAddress, out var decodedMetadata))
{
metadata = decodedMetadata;
}
var userData = new uint[GetUserDataDwordCount(metadata)];
for (uint index = 0; index < userData.Length; index++)
{
shaderRegisters.TryGetValue(userDataBaseRegister + index, out userData[index]);
}
state = new Gen5ShaderState(
program,
userData,
metadata,
computeSystemRegisters,
userDataScalarRegisterBase);
return true;
}
private static int GetUserDataDwordCount(Gen5ShaderMetadata? metadata)
{
var count = MinimumUserDataDwords;
if (metadata is null)
{
return count;
}
count = Math.Max(count, checked((int)Math.Min(metadata.ExtendedUserDataSizeDwords, MaximumUserDataDwords)));
count = Math.Max(count, checked((int)Math.Min(metadata.ShaderResourceTableSizeDwords, MaximumUserDataDwords)));
foreach (var offset in metadata.DirectResources.Values)
{
count = Math.Max(count, checked((int)Math.Min(offset + 1u, MaximumUserDataDwords)));
}
foreach (var resource in metadata.Resources)
{
var size = resource.Kind switch
{
Gen5ShaderResourceKind.ReadOnlyTexture or Gen5ShaderResourceKind.ReadWriteTexture => 8u,
Gen5ShaderResourceKind.Sampler => 4u,
Gen5ShaderResourceKind.ConstantBuffer => 4u,
_ => 1u,
};
count = Math.Max(count, checked((int)Math.Min(resource.OffsetDwords + size, MaximumUserDataDwords)));
}
return count;
}
public static string DescribeState(Gen5ShaderState state)
{
var userData = string.Join(
',',
state.UserData.Select((value, index) => $"s{index}=0x{value:X8}"));
var systemRegisters = state.ComputeSystemRegisters is { } compute
? $" compute[{DescribeComputeSystemRegisters(compute)}]"
: string.Empty;
if (state.Metadata is not { } metadata)
{
return
$"ud_base=s{state.UserDataScalarRegisterBase} ud[{userData}]" +
$"{systemRegisters} metadata=missing";
}
var direct = string.Join(
',',
metadata.DirectResources.Select(resource => $"{resource.Key}:{resource.Value}"));
var resources = string.Join(
',',
metadata.Resources.Select(resource =>
$"{resource.Kind}[{resource.Slot}]@{resource.OffsetDwords}" +
(resource.SizeFlag ? "+" : string.Empty)));
return
$"ud_base=s{state.UserDataScalarRegisterBase} ud[{userData}]" +
$"{systemRegisters} metadata[eud={metadata.ExtendedUserDataSizeDwords}," +
$"srt={metadata.ShaderResourceTableSizeDwords},direct={direct},resources={resources}]";
}
private static string DescribeComputeSystemRegisters(Gen5ComputeSystemRegisters registers) =>
$"x={DescribeRegister(registers.WorkGroupXRegister)}," +
$"y={DescribeRegister(registers.WorkGroupYRegister)}," +
$"z={DescribeRegister(registers.WorkGroupZRegister)}," +
$"size={DescribeRegister(registers.ThreadGroupSizeRegister)}";
private static string DescribeRegister(uint? register) =>
register.HasValue ? $"s{register.Value}" : "-";
private static bool MatchesProgram(CpuContext ctx, ulong address, ReadOnlySpan<uint> expected)
{
var bytes = new byte[expected.Length * sizeof(uint)];
if (!ctx.Memory.TryRead(address, bytes))
{
return false;
}
for (var index = 0; index < expected.Length; index++)
{
if (BinaryPrimitives.ReadUInt32LittleEndian(bytes.AsSpan(index * sizeof(uint))) != expected[index])
{
return false;
}
}
return true;
}
private static bool TryDecodeProgram(
CpuContext ctx,
ulong address,
out Gen5ShaderProgram program,
out string error)
{
program = new Gen5ShaderProgram(address, []);
error = string.Empty;
if (address == 0)
{
error = "missing";
return false;
}
var instructions = new List<Gen5ShaderInstruction>();
var instructionCount = 0;
for (uint pc = 0; instructionCount < MaxInstructions;)
{
if (!TryReadUInt32(ctx, address + pc, out var word))
{
error = $"read-failed pc=0x{pc:X}";
return false;
}
if (!TryDecodeInstruction(
ctx,
address,
pc,
word,
out var encoding,
out var name,
out var sizeDwords,
out error))
{
return false;
}
var words = new uint[sizeDwords];
words[0] = word;
for (uint wordIndex = 1; wordIndex < sizeDwords; wordIndex++)
{
if (!TryReadUInt32(ctx, address + pc + wordIndex * sizeof(uint), out words[wordIndex]))
{
error = $"read-failed pc=0x{pc + wordIndex * sizeof(uint):X}";
return false;
}
}
instructions.Add(CreateInstruction(pc, encoding, name, words));
instructionCount++;
pc += sizeDwords * sizeof(uint);
if (string.Equals(name, "SEndpgm", StringComparison.Ordinal))
{
program = new Gen5ShaderProgram(address, instructions);
return true;
}
}
error = "unterminated";
return false;
}
private static bool TryDecodeInstruction(
CpuContext ctx,
ulong baseAddress,
uint pc,
uint word,
out Gen5ShaderEncoding encoding,
out string name,
out uint sizeDwords,
out string error)
{
encoding = Gen5ShaderEncoding.Vop2;
name = string.Empty;
sizeDwords = 1;
error = string.Empty;
if ((word & 0x80000000u) == 0)
{
var vopOpcode = (word >> 25) & 0x3F;
encoding = vopOpcode switch
{
0x3E => Gen5ShaderEncoding.Vopc,
0x3F => Gen5ShaderEncoding.Vop1,
_ => Gen5ShaderEncoding.Vop2,
};
return DecodeVop2(word, out name, out sizeDwords, out error);
}
if ((word & 0xF8000000u) == 0xC0000000u)
{
encoding = Gen5ShaderEncoding.Smrd;
return DecodeSmrd(word, out name, out sizeDwords, out error);
}
if ((word & 0xC0000000u) == 0x80000000u)
{
var sopOpcode = (word >> 23) & 0x7F;
encoding = sopOpcode switch
{
0x7D => Gen5ShaderEncoding.Sop1,
0x7E => Gen5ShaderEncoding.Sopc,
0x7F => Gen5ShaderEncoding.Sopp,
>= 0x60 => Gen5ShaderEncoding.Sopk,
_ => Gen5ShaderEncoding.Sop2,
};
return DecodeSop(word, out name, out sizeDwords, out error);
}
switch (word >> 26)
{
case 0x33:
encoding = Gen5ShaderEncoding.Smem;
return DecodeSmem(word, out name, out sizeDwords, out error);
case 0x32:
encoding = Gen5ShaderEncoding.Vintrp;
return DecodeVintrp(word, out name, out sizeDwords, out error);
case 0x34:
case 0x35:
encoding = Gen5ShaderEncoding.Vop3;
if (!TryReadUInt32(ctx, baseAddress + pc + sizeof(uint), out var vop3Extra))
{
error = $"vop3-extra-read-failed pc=0x{pc:X}";
return false;
}
return DecodeVop3(
word,
vop3Extra,
IsVop3BOpcode((word >> 16) & 0x3FF),
out name,
out sizeDwords,
out error);
case 0x36:
encoding = Gen5ShaderEncoding.Ds;
return DecodeDs(word, out name, out sizeDwords, out error);
case 0x37:
encoding = Gen5ShaderEncoding.Flat;
return DecodeFlat(word, out name, out sizeDwords, out error);
case 0x38:
encoding = Gen5ShaderEncoding.Mubuf;
if (!TryReadUInt32(ctx, baseAddress + pc + sizeof(uint), out var mubufExtra))
{
error = $"mubuf-extra-read-failed pc=0x{pc:X}";
return false;
}
return DecodeMubuf(word, mubufExtra, out name, out sizeDwords, out error);
case 0x3A:
encoding = Gen5ShaderEncoding.Mtbuf;
if (!TryReadUInt32(ctx, baseAddress + pc + sizeof(uint), out var mtbufExtra))
{
error = $"mtbuf-extra-read-failed pc=0x{pc:X}";
return false;
}
return DecodeMtbuf(word, mtbufExtra, out name, out sizeDwords, out error);
case 0x3C:
encoding = Gen5ShaderEncoding.Mimg;
return DecodeMimg(word, out name, out sizeDwords, out error);
case 0x3D:
encoding = Gen5ShaderEncoding.Smem;
return DecodeSmem(word, out name, out sizeDwords, out error);
case 0x3E:
encoding = Gen5ShaderEncoding.Exp;
name = "Exp";
sizeDwords = 2;
return true;
case 0x3F:
encoding = Gen5ShaderEncoding.Vop3p;
return DecodeRaw2(word, "Vop3p", out name, out sizeDwords, out error);
default:
error = $"unknown-top pc=0x{pc:X} word=0x{word:X8}";
return false;
}
}
private static bool DecodeSop(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 23) & 0x7F;
return opcode switch
{
0x7D => DecodeSop1(word, out name, out sizeDwords, out error),
0x7E => DecodeSopc(word, out name, out sizeDwords, out error),
0x7F => DecodeSopp(word, out name, out sizeDwords, out error),
>= 0x60 => DecodeSopk(word, out name, out sizeDwords, out error),
_ => DecodeSop2(word, out name, out sizeDwords, out error),
};
}
private static bool DecodeSop1(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 8) & 0xFF;
var src0 = word & 0xFF;
sizeDwords = 1 + (src0 == 0xFF ? 1u : 0u);
error = string.Empty;
name = opcode switch
{
0x03 => "SMovB32",
0x04 => "SMovB64",
0x07 => "SNotB32",
0x08 => "SNotB64",
0x0A => "SWqmB64",
0x0B => "SBrevB32",
0x0F => "SBcnt1I32B32",
0x13 => "SFF1I32B32",
0x1D => "SBitset1B32",
0x1F => "SGetpcB64",
0x20 => "SSetpcB64",
0x21 => "SSwappcB64",
0x24 => "SAndSaveexecB64",
0x25 => "SOrSaveexecB64",
0x26 => "SXorSaveexecB64",
0x27 => "SAndn2SaveexecB64",
0x28 => "SOrn2SaveexecB64",
0x29 => "SNandSaveexecB64",
0x2A => "SNorSaveexecB64",
0x2B => "SXnorSaveexecB64",
0x37 => "SAndn1SaveexecB64",
0x38 => "SOrn1SaveexecB64",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-sop1 op=0x{opcode:X2} word=0x{word:X8}", out error);
}
private static bool DecodeSop2(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 23) & 0x7F;
var src0 = word & 0xFF;
var src1 = (word >> 8) & 0xFF;
sizeDwords = src0 == 0xFF || src1 == 0xFF ? 2u : 1u;
error = string.Empty;
name = opcode switch
{
0x00 => "SAddU32",
0x01 => "SSubU32",
0x02 => "SAddI32",
0x03 => "SSubI32",
0x04 => "SAddcU32",
0x05 => "SSubbU32",
0x06 => "SMinI32",
0x07 => "SMinU32",
0x08 => "SMaxI32",
0x09 => "SMaxU32",
0x0A => "SCselectB32",
0x0B => "SCselectB64",
0x0E => "SAndB32",
0x0F => "SAndB64",
0x10 => "SOrB32",
0x11 => "SOrB64",
0x12 => "SXorB32",
0x13 => "SXorB64",
0x14 => "SAndn2B32",
0x15 => "SAndn2B64",
0x16 => "SOrn2B32",
0x17 => "SOrn2B64",
0x18 => "SNandB32",
0x19 => "SNandB64",
0x1A => "SNorB32",
0x1B => "SNorB64",
0x1C => "SXnorB32",
0x1D => "SXnorB64",
0x1E => "SLshlB32",
0x1F => "SLshlB64",
0x20 => "SLshrB32",
0x21 => "SLshrB64",
0x22 => "SAshrI32",
0x23 => "SAshrI64",
0x24 => "SBfmB32",
0x25 => "SBfmB64",
0x26 => "SMulI32",
0x27 => "SBfeU32",
0x28 => "SBfeI32",
0x29 => "SBfeU64",
0x2A => "SBfeI64",
0x2D => "SAbsdiffI32",
0x2E => "SLshl1AddU32",
0x2F => "SLshl2AddU32",
0x30 => "SLshl3AddU32",
0x31 => "SLshl4AddU32",
0x32 => "SPackLlB32B16",
0x33 => "SPackLhB32B16",
0x34 => "SPackHhB32B16",
0x35 => "SMulHiU32",
0x36 => "SMulHiI32",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-sop2 op=0x{opcode:X2}", out error);
}
private static bool DecodeSopc(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 16) & 0x7F;
var src0 = word & 0xFF;
var src1 = (word >> 8) & 0xFF;
sizeDwords = src0 == 0xFF || src1 == 0xFF ? 2u : 1u;
error = string.Empty;
name = opcode switch
{
0x00 => "SCmpEqI32",
0x01 => "SCmpLgI32",
0x02 => "SCmpGtI32",
0x03 => "SCmpGeI32",
0x04 => "SCmpLtI32",
0x05 => "SCmpLeI32",
0x06 => "SCmpEqU32",
0x07 => "SCmpLgU32",
0x08 => "SCmpGtU32",
0x09 => "SCmpGeU32",
0x0A => "SCmpLtU32",
0x0B => "SCmpLeU32",
0x0C => "SBitcmp0B32",
0x0D => "SBitcmp1B32",
0x0E => "SBitcmp0B64",
0x0F => "SBitcmp1B64",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-sopc op=0x{opcode:X2}", out error);
}
private static bool DecodeSopp(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 16) & 0x7F;
sizeDwords = 1;
error = string.Empty;
name = opcode switch
{
0x00 => "SNop",
0x01 => "SEndpgm",
0x02 => "SBranch",
0x04 => "SCbranchScc0",
0x05 => "SCbranchScc1",
0x06 => "SCbranchVccz",
0x07 => "SCbranchVccnz",
0x08 => "SCbranchExecz",
0x09 => "SCbranchExecnz",
0x0A => "SBarrier",
0x0C => "SWaitcnt",
0x10 => "SSendmsg",
0x16 => "STtraceData",
0x20 => "SInstPrefetch",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-sopp op=0x{opcode:X2}", out error);
}
private static bool DecodeSopk(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = ((word >> 23) & 0x7F) - 0x60;
sizeDwords = 1;
error = string.Empty;
name = opcode switch
{
0x00 => "SMovkI32",
0x03 => "SCmpkEqI32",
0x04 => "SCmpkLgI32",
0x05 => "SCmpkGtI32",
0x06 => "SCmpkGeI32",
0x07 => "SCmpkLtI32",
0x08 => "SCmpkLeI32",
0x09 => "SCmpkEqU32",
0x0A => "SCmpkLgU32",
0x0B => "SCmpkGtU32",
0x0C => "SCmpkGeU32",
0x0D => "SCmpkLtU32",
0x0E => "SCmpkLeU32",
0x0F => "SAddkI32",
0x10 => "SMulkI32",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-sopk op=0x{opcode:X2}", out error);
}
private static bool DecodeVop1(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 9) & 0xFF;
var src0 = word & 0x1FF;
sizeDwords = src0 is 0xF9 or 0xFA or 0xFF ? 2u : 1u;
error = string.Empty;
name = opcode switch
{
0x00 => "VNop",
0x01 => "VMovB32",
0x02 => "VReadfirstlaneB32",
0x05 => "VCvtF32I32",
0x06 => "VCvtF32U32",
0x07 => "VCvtU32F32",
0x08 => "VCvtI32F32",
0x0A => "VCvtF16F32",
0x0B => "VCvtF32F16",
0x0C => "VCvtRpiI32F32",
0x0D => "VCvtFlrI32F32",
0x0E => "VCvtOffF32I4",
0x11 => "VCvtF32Ubyte0",
0x12 => "VCvtF32Ubyte1",
0x13 => "VCvtF32Ubyte2",
0x14 => "VCvtF32Ubyte3",
0x20 => "VFractF32",
0x21 => "VTruncF32",
0x22 => "VCeilF32",
0x23 => "VRndneF32",
0x24 => "VFloorF32",
0x25 => "VExpF32",
0x27 => "VLogF32",
0x2A => "VRcpF32",
0x2B => "VRcpIflagF32",
0x2E => "VRsqF32",
0x33 => "VSqrtF32",
0x35 => "VSinF32",
0x36 => "VCosF32",
0x37 => "VNotB32",
0x38 => "VBfrevB32",
0x3A => "VFfblB32",
0x42 => "VMovreldB32",
0x43 => "VMovrelsB32",
0x44 => "VMovrelsdB32",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-vop1 op=0x{opcode:X2}", out error);
}
private static bool DecodeVop2(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 25) & 0x3F;
if (opcode == 0x3E)
{
return DecodeVopc(word, out name, out sizeDwords, out error);
}
if (opcode == 0x3F)
{
return DecodeVop1(word, out name, out sizeDwords, out error);
}
var src0 = word & 0x1FF;
sizeDwords = opcode is 0x20 or 0x21 || src0 is 0xF9 or 0xFA or 0xFF ? 2u : 1u;
error = string.Empty;
name = opcode switch
{
0x01 => "VCndmaskB32",
0x02 => "VDot2cF32F16",
0x03 => "VAddF32",
0x04 => "VSubF32",
0x05 => "VSubrevF32",
0x08 => "VMulF32",
0x0B => "VMulU32U24",
0x0C => "VMulHiU32U24",
0x0F => "VMinF32",
0x10 => "VMaxF32",
0x11 => "VMinI32",
0x12 => "VMaxI32",
0x13 => "VMinU32",
0x14 => "VMaxU32",
0x15 => "VLshrB32",
0x16 => "VLshrrevB32",
0x17 => "VAshrI32",
0x18 => "VAshrrevI32",
0x19 => "VLshlB32",
0x1A => "VLshlrevB32",
0x1B => "VAndB32",
0x1C => "VOrB32",
0x1D => "VXorB32",
0x1F => "VMacF32",
0x20 => "VMadMkF32",
0x21 => "VMadAkF32",
0x22 => "VBcntU32B32",
0x23 => "VMbcntLoU32B32",
0x24 => "VMbcntHiU32B32",
0x25 => "VAddI32",
0x26 => "VSubI32",
0x27 => "VSubrevI32",
0x28 => "VAddcU32",
0x29 => "VSubbU32",
0x2A => "VSubbrevU32",
0x2B => "VLdexpF32",
0x2F => "VCvtPkrtzF16F32",
0x30 => "VCvtPkU16U32",
0x31 => "VCvtPkI16I32",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-vop2 op=0x{opcode:X2}", out error);
}
private static bool DecodeVopc(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 17) & 0xFF;
var src0 = word & 0x1FF;
sizeDwords = src0 is 0xF9 or 0xFA or 0xFF ? 2u : 1u;
error = string.Empty;
name = opcode switch
{
0x00 => "VCmpFF32",
0x01 => "VCmpLtF32",
0x02 => "VCmpEqF32",
0x03 => "VCmpLeF32",
0x04 => "VCmpGtF32",
0x05 => "VCmpLgF32",
0x06 => "VCmpGeF32",
0x07 => "VCmpOF32",
0x08 => "VCmpUF32",
0x09 => "VCmpNgeF32",
0x0A => "VCmpNlgF32",
0x0B => "VCmpNgtF32",
0x0C => "VCmpNleF32",
0x0D => "VCmpNeqF32",
0x0E => "VCmpNltF32",
0x0F => "VCmpTruF32",
0x10 => "VCmpxFF32",
0x11 => "VCmpxLtF32",
0x12 => "VCmpxEqF32",
0x13 => "VCmpxLeF32",
0x14 => "VCmpxGtF32",
0x15 => "VCmpxLgF32",
0x16 => "VCmpxGeF32",
0x1D => "VCmpxNeqF32",
0x1E => "VCmpxNltF32",
0x80 => "VCmpFI32",
0x81 => "VCmpLtI32",
0x82 => "VCmpEqI32",
0x83 => "VCmpLeI32",
0x84 => "VCmpGtI32",
0x85 => "VCmpNeI32",
0x86 => "VCmpGeI32",
0x87 => "VCmpTI32",
0x88 => "VCmpClassF32",
0x90 => "VCmpxFI32",
0x91 => "VCmpxLtI32",
0x92 => "VCmpxEqI32",
0x93 => "VCmpxLeI32",
0x94 => "VCmpxGtI32",
0x95 => "VCmpxNeI32",
0x96 => "VCmpxGeI32",
0x97 => "VCmpxTI32",
0xC0 => "VCmpFU32",
0xC1 => "VCmpLtU32",
0xC2 => "VCmpEqU32",
0xC3 => "VCmpLeU32",
0xC4 => "VCmpGtU32",
0xC5 => "VCmpNeU32",
0xC6 => "VCmpGeU32",
0xC7 => "VCmpTU32",
0xD0 => "VCmpxFU32",
0xD1 => "VCmpxLtU32",
0xD2 => "VCmpxEqU32",
0xD3 => "VCmpxLeU32",
0xD4 => "VCmpxGtU32",
0xD5 => "VCmpxNeU32",
0xD6 => "VCmpxGeU32",
0xD7 => "VCmpxTU32",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-vopc op=0x{opcode:X2}", out error);
}
private static bool DecodeVop3(
uint word,
uint extra,
bool isVop3B,
out string name,
out uint sizeDwords,
out string error)
{
var opcode = (word >> 16) & 0x3FF;
var src0 = extra & 0x1FF;
var src1 = (extra >> 9) & 0x1FF;
var src2 = (extra >> 18) & 0x1FF;
sizeDwords = src0 == 0xFF || src1 == 0xFF || src2 == 0xFF ? 3u : 2u;
error = string.Empty;
name = isVop3B
? opcode switch
{
0x30F => "VAddCoU32",
0x310 => "VSubCoU32",
0x319 => "VSubrevCoU32",
_ => $"Vop3bRaw{opcode:X3}",
}
: opcode switch
{
0x101 => "VCndmaskB32",
0x103 => "VAddF32",
0x104 => "VSubF32",
0x108 => "VMulF32",
0x10F => "VMinF32",
0x110 => "VMaxF32",
0x11F => "VMacF32",
0x12F => "VCvtPkrtzF16F32",
0x141 => "VMadF32",
0x143 => "VMadU32U24",
0x144 => "VCubeidF32",
0x145 => "VCubescF32",
0x146 => "VCubetcF32",
0x147 => "VCubemaF32",
0x14A => "VBfiB32",
0x14B => "VFmaF32",
0x151 => "VMin3F32",
0x152 => "VMin3I32",
0x153 => "VMin3U32",
0x154 => "VMax3F32",
0x155 => "VMax3I32",
0x156 => "VMax3U32",
0x157 => "VMed3F32",
0x158 => "VMed3I32",
0x159 => "VMed3U32",
0x15A => "VSadU8",
0x15B => "VSadHiU8",
0x15C => "VSadU16",
0x15D => "VSadU32",
0x15E => "VCvtPkU8F32",
0x148 => "VBfeU32",
0x169 => "VMulLoU32",
0x16A => "VMulHiU32",
0x16B => "VMulLoI32",
0x360 => "VMadU32U16",
0x361 => "VMulLoU32",
0x362 => "VLdexpF32",
0x346 => "VLshlAddU32",
0x347 => "VAddLshlU32",
0x36D => "VAdd3U32",
0x36F => "VLshlOrU32",
0x371 => "VAndOrB32",
0x372 => "VOr3U32",
0x377 => "VPermlane16B32",
0x378 => "VPermlanex16B32",
_ => $"Vop3Raw{opcode:X3}",
};
return FinishDecode(name, $"unknown-vop3 op=0x{opcode:X3}", out error);
}
private static bool IsVop3BOpcode(uint opcode) =>
opcode is 0x16D or 0x16E or 0x176 or 0x177 or 0x30F or 0x310 or 0x319;
private static bool DecodeRaw2(
uint word,
string prefix,
out string name,
out uint sizeDwords,
out string error)
{
name = $"{prefix}Raw{word >> 24:X2}";
sizeDwords = 2;
error = string.Empty;
return true;
}
private static bool DecodeDs(
uint word,
out string name,
out uint sizeDwords,
out string error)
{
var opcode = (word >> 18) & 0xFF;
sizeDwords = 2;
error = string.Empty;
name = opcode switch
{
0x0D => "DsWriteB32",
0x0E => "DsWrite2B32",
0x0F => "DsWrite2St64B32",
0x35 => "DsSwizzleB32",
0x36 => "DsReadB32",
0x37 => "DsRead2B32",
0x38 => "DsRead2St64B32",
0x4D => "DsWriteB64",
_ => string.Empty,
};
return FinishDecode(
name,
$"unknown-ds op=0x{opcode:X2} word=0x{word:X8}",
out error);
}
private static bool DecodeBuffer(
uint word,
string prefix,
out string name,
out uint sizeDwords,
out string error)
{
var opcode = (word >> 18) & 0x7F;
name = $"{prefix}Raw{opcode:X2}";
sizeDwords = 2;
error = string.Empty;
return true;
}
private static bool DecodeMtbuf(
uint word,
uint extra,
out string name,
out uint sizeDwords,
out string error)
{
var opcode = (word >> 16) & 0x7;
name = opcode switch
{
0x00 => "TBufferLoadFormatX",
0x01 => "TBufferLoadFormatXy",
0x02 => "TBufferLoadFormatXyz",
0x03 => "TBufferLoadFormatXyzw",
0x04 => "TBufferStoreFormatX",
0x05 => "TBufferStoreFormatXy",
0x06 => "TBufferStoreFormatXyz",
0x07 => "TBufferStoreFormatXyzw",
_ => string.Empty,
};
sizeDwords = (extra >> 24) == 0xFF ? 3u : 2u;
error = string.Empty;
return true;
}
private static bool DecodeMubuf(
uint word,
uint extra,
out string name,
out uint sizeDwords,
out string error)
{
var opcode = (word >> 18) & 0x7F;
name = opcode switch
{
0x00 => "BufferLoadFormatX",
0x01 => "BufferLoadFormatXy",
0x02 => "BufferLoadFormatXyz",
0x03 => "BufferLoadFormatXyzw",
0x0C => "BufferLoadDword",
0x0D => "BufferLoadDwordx2",
0x0E => "BufferLoadDwordx4",
0x0F => "BufferLoadDwordx3",
0x1C => "BufferStoreDword",
0x1D => "BufferStoreDwordx2",
0x1E => "BufferStoreDwordx4",
0x1F => "BufferStoreDwordx3",
0x32 => "BufferAtomicAdd",
_ => $"MubufRaw{opcode:X2}",
};
sizeDwords = (extra >> 24) == 0xFF ? 3u : 2u;
error = string.Empty;
return true;
}
private static bool DecodeFlat(
uint word,
out string name,
out uint sizeDwords,
out string error)
{
var segment = (word >> 14) & 0x3;
var opcode = (word >> 18) & 0x7F;
sizeDwords = 2;
error = string.Empty;
name = segment == 0x2
? opcode switch
{
0x0C => "GlobalLoadDword",
0x0D => "GlobalLoadDwordx2",
0x0E => "GlobalLoadDwordx4",
0x0F => "GlobalLoadDwordx3",
_ => string.Empty,
}
: string.Empty;
return FinishDecode(
name,
$"unknown-flat segment=0x{segment:X1} op=0x{opcode:X2} word=0x{word:X8}",
out error);
}
private static bool DecodeSmrd(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 22) & 0x1F;
var offset = word & 0xFF;
var immediateOffset = ((word >> 8) & 1) != 0;
sizeDwords = !immediateOffset && offset == 0xFF ? 2u : 1u;
error = string.Empty;
name = opcode switch
{
0x00 => "SLoadDword",
0x01 => "SLoadDwordx2",
0x02 => "SLoadDwordx4",
0x03 => "SLoadDwordx8",
0x04 => "SLoadDwordx16",
0x08 => "SBufferLoadDword",
0x09 => "SBufferLoadDwordx2",
0x0A => "SBufferLoadDwordx4",
0x0B => "SBufferLoadDwordx8",
0x0C => "SBufferLoadDwordx16",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-smrd op=0x{opcode:X2}", out error);
}
private static bool DecodeSmem(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 18) & 0xFF;
sizeDwords = 2;
error = string.Empty;
name = opcode switch
{
0x00 => "SLoadDword",
0x01 => "SLoadDwordx2",
0x02 => "SLoadDwordx4",
0x03 => "SLoadDwordx8",
0x04 => "SLoadDwordx16",
0x08 => "SBufferLoadDword",
0x09 => "SBufferLoadDwordx2",
0x0A => "SBufferLoadDwordx4",
0x0B => "SBufferLoadDwordx8",
0x0C => "SBufferLoadDwordx16",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-smem op=0x{opcode:X2}", out error);
}
private static bool DecodeMimg(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 18) & 0x7F;
sizeDwords = 2 + ((word >> 1) & 0x3);
error = string.Empty;
name = opcode switch
{
0x00 => "ImageLoad",
0x01 => "ImageLoadMip",
0x08 => "ImageStore",
0x09 => "ImageStoreMip",
0x0E => "ImageGetResinfo",
0x10 => "ImageAtomicCmpswap",
0x1C => "ImageAtomicDec",
0x20 => "ImageSample",
0x24 => "ImageSampleL",
0x25 => "ImageSampleB",
0x27 => "ImageSampleLz",
0x2F => "ImageSampleCLz",
0x30 => "ImageSampleO",
0x34 => "ImageSampleLO",
0x37 => "ImageSampleLzO",
0x40 => "ImageGather4",
0x47 => "ImageGather4Lz",
0x48 => "ImageGather4C",
0x4E => "ImageGather4CBCl",
0x5F => "ImageGather4CLzO",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-mimg op=0x{opcode:X2}", out error);
}
private static bool DecodeVintrp(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 16) & 0x3;
sizeDwords = 1;
error = string.Empty;
name = opcode switch
{
0x00 => "VInterpP1F32",
0x01 => "VInterpP2F32",
0x02 => "VInterpMovF32",
_ => string.Empty,
};
return FinishDecode(name, $"unknown-vintrp op=0x{opcode:X1}", out error);
}
private static bool FinishDecode(string name, string decodeError, out string error)
{
error = string.Empty;
if (name.Length != 0)
{
return true;
}
error = decodeError;
return false;
}
private static string ClassifyInstruction(string name)
{
if (name.StartsWith("Image", StringComparison.Ordinal))
{
return "image";
}
if (name.StartsWith("Global", StringComparison.Ordinal))
{
return "global_memory";
}
if (name.StartsWith("VInterp", StringComparison.Ordinal))
{
return "interp";
}
if (string.Equals(name, "Exp", StringComparison.Ordinal))
{
return "export";
}
if (name.StartsWith("SLoad", StringComparison.Ordinal) ||
name.StartsWith("SBufferLoad", StringComparison.Ordinal))
{
return "scalar_load";
}
if (name.StartsWith('V'))
{
return "valu";
}
if (name.StartsWith('S'))
{
return "salu";
}
return "other";
}
private static bool IsMimgInstruction(string name) =>
name.StartsWith("Image", StringComparison.Ordinal);
internal static bool IsStorageImageOperation(string name) =>
name.StartsWith("ImageStore", StringComparison.Ordinal) ||
name.StartsWith("ImageAtomic", StringComparison.Ordinal);
private static Gen5ShaderInstruction CreateInstruction(
uint pc,
Gen5ShaderEncoding encoding,
string opcode,
uint[] words)
{
var word = words[0];
var isSdwa =
encoding is Gen5ShaderEncoding.Vop1 or Gen5ShaderEncoding.Vop2 or Gen5ShaderEncoding.Vopc &&
(word & 0x1FF) == 0xF9;
var isDpp =
encoding is Gen5ShaderEncoding.Vop1 or Gen5ShaderEncoding.Vop2 or Gen5ShaderEncoding.Vopc &&
(word & 0x1FF) == 0xFA;
var literal = !isSdwa && !isDpp && words.Length > MinimumEncodingDwords(encoding)
? words[^1]
: (uint?)null;
IReadOnlyList<Gen5Operand> sources = [];
IReadOnlyList<Gen5Operand> destinations = [];
Gen5InstructionControl? control = null;
switch (encoding)
{
case Gen5ShaderEncoding.Sop1:
sources = [Gen5Operand.Source(word & 0xFF, literal)];
destinations = [Gen5Operand.Scalar((word >> 16) & 0x7F)];
break;
case Gen5ShaderEncoding.Sop2:
sources =
[
Gen5Operand.Source(word & 0xFF, literal),
Gen5Operand.Source((word >> 8) & 0xFF, literal),
];
destinations = [Gen5Operand.Scalar((word >> 16) & 0x7F)];
break;
case Gen5ShaderEncoding.Sopc:
sources =
[
Gen5Operand.Source(word & 0xFF, literal),
Gen5Operand.Source((word >> 8) & 0xFF, literal),
];
break;
case Gen5ShaderEncoding.Sopk:
sources = [new Gen5Operand(Gen5OperandKind.EncodedConstant, word & 0xFFFF)];
destinations = [Gen5Operand.Scalar((word >> 16) & 0x7F)];
break;
case Gen5ShaderEncoding.Smrd:
{
var scalarBase = ((word >> 9) & 0x3F) * 2;
var scalarDestination = (word >> 15) & 0x7F;
var immediate = ((word >> 8) & 1) != 0;
var offset = word & 0xFF;
var count = ScalarLoadDwordCount(opcode);
uint? dynamicOffsetRegister = null;
var immediateOffsetBytes = 0;
if (immediate)
{
immediateOffsetBytes = checked((int)(offset * sizeof(uint)));
}
else if (offset == 0xFF && literal.HasValue)
{
immediateOffsetBytes = unchecked((int)literal.Value);
}
else
{
dynamicOffsetRegister = offset;
}
sources = dynamicOffsetRegister.HasValue
? [Gen5Operand.Scalar(scalarBase), Gen5Operand.Scalar(dynamicOffsetRegister.Value)]
: [Gen5Operand.Scalar(scalarBase)];
destinations = Enumerable
.Range((int)scalarDestination, checked((int)count))
.Select(index => Gen5Operand.Scalar((uint)index))
.ToArray();
control = new Gen5ScalarMemoryControl(
count,
immediateOffsetBytes,
dynamicOffsetRegister);
break;
}
case Gen5ShaderEncoding.Smem:
{
var extra = words[1];
var scalarBase = (word & 0x3F) * 2;
var scalarDestination = (word >> 6) & 0x7F;
var scalarOffset = (extra >> 25) & 0x7F;
var offset = SignExtend(extra & 0x1FFFFF, 21);
var count = ScalarLoadDwordCount(opcode);
sources =
[
Gen5Operand.Scalar(scalarBase),
Gen5Operand.Scalar(scalarOffset),
];
destinations = Enumerable
.Range((int)scalarDestination, checked((int)count))
.Select(index => Gen5Operand.Scalar((uint)index))
.ToArray();
control = new Gen5ScalarMemoryControl(count, offset, scalarOffset);
break;
}
case Gen5ShaderEncoding.Vop1:
if (isDpp)
{
var extra = words[1];
sources = [Gen5Operand.Vector(extra & 0xFF)];
control = CreateDppControl(extra);
}
else if (isSdwa)
{
var extra = words[1];
var source0 = (extra & 0xFF) +
((((extra >> 23) & 1) == 0) ? 256u : 0u);
sources = [Gen5Operand.Source(source0)];
control = new Gen5SdwaControl(
(extra >> 8) & 0x7,
(extra >> 16) & 0x7,
6,
(extra >> 21) & 1,
(extra >> 20) & 1,
(extra >> 14) & 0x3,
((extra >> 13) & 1) != 0);
}
else
{
sources = [Gen5Operand.Source(word & 0x1FF, literal)];
}
destinations = [Gen5Operand.Vector((word >> 17) & 0xFF)];
break;
case Gen5ShaderEncoding.Vop2:
if (isDpp)
{
var extra = words[1];
sources =
[
Gen5Operand.Vector(extra & 0xFF),
Gen5Operand.Vector((word >> 9) & 0xFF),
];
control = CreateDppControl(extra);
}
else if (isSdwa)
{
var extra = words[1];
var source0 = (extra & 0xFF) + ((((extra >> 23) & 1) == 0) ? 256u : 0u);
var source1 =
((word >> 9) & 0xFF) +
((((extra >> 31) & 1) == 0) ? 256u : 0u);
sources =
[
Gen5Operand.Source(source0),
Gen5Operand.Source(source1),
];
control = new Gen5SdwaControl(
(extra >> 8) & 0x7,
(extra >> 16) & 0x7,
(extra >> 24) & 0x7,
((extra >> 21) & 1) | (((extra >> 29) & 1) << 1),
((extra >> 20) & 1) | (((extra >> 28) & 1) << 1),
(extra >> 14) & 0x3,
((extra >> 13) & 1) != 0);
}
else
{
sources =
[
Gen5Operand.Source(word & 0x1FF, literal),
Gen5Operand.Vector((word >> 9) & 0xFF),
];
if (opcode == "VMadMkF32" && literal.HasValue)
{
sources =
[
sources[0],
new Gen5Operand(Gen5OperandKind.LiteralConstant, literal.Value),
sources[1],
];
}
else if (opcode == "VMadAkF32" && literal.HasValue)
{
sources =
[
.. sources,
new Gen5Operand(Gen5OperandKind.LiteralConstant, literal.Value),
];
}
}
destinations = [Gen5Operand.Vector((word >> 17) & 0xFF)];
break;
case Gen5ShaderEncoding.Vopc:
if (isDpp)
{
var extra = words[1];
sources =
[
Gen5Operand.Vector(extra & 0xFF),
Gen5Operand.Vector((word >> 9) & 0xFF),
];
control = CreateDppControl(extra);
}
else if (isSdwa)
{
var extra = words[1];
var source0 = (extra & 0xFF) +
((((extra >> 23) & 1) == 0) ? 256u : 0u);
var source1 =
((word >> 9) & 0xFF) +
((((extra >> 31) & 1) == 0) ? 256u : 0u);
sources =
[
Gen5Operand.Source(source0),
Gen5Operand.Source(source1),
];
control = new Gen5SdwaControl(
(extra >> 8) & 0x7,
(extra >> 16) & 0x7,
(extra >> 24) & 0x7,
((extra >> 21) & 1) | (((extra >> 29) & 1) << 1),
((extra >> 20) & 1) | (((extra >> 28) & 1) << 1),
(extra >> 14) & 0x3,
((extra >> 13) & 1) != 0);
}
else
{
sources =
[
Gen5Operand.Source(word & 0x1FF, literal),
Gen5Operand.Vector((word >> 9) & 0xFF),
];
}
break;
case Gen5ShaderEncoding.Vop3:
{
var extra = words[1];
sources =
[
Gen5Operand.Source(extra & 0x1FF, literal),
Gen5Operand.Source((extra >> 9) & 0x1FF, literal),
Gen5Operand.Source((extra >> 18) & 0x1FF, literal),
];
destinations = [Gen5Operand.Vector(word & 0xFF)];
var isVop3B = IsVop3BOpcode((word >> 16) & 0x3FF);
control = new Gen5Vop3Control(
isVop3B ? 0 : (word >> 8) & 0x7,
(extra >> 29) & 0x7,
(extra >> 27) & 0x3,
((word >> 15) & 1) != 0,
isVop3B ? (word >> 8) & 0x7F : null);
break;
}
case Gen5ShaderEncoding.Ds:
{
var extra = words[1];
var vectorAddress = extra & 0xFF;
var vectorData0 = (extra >> 8) & 0xFF;
var vectorData1 = (extra >> 16) & 0xFF;
var vectorDestination = (extra >> 24) & 0xFF;
control = new Gen5DataShareControl(
word & 0xFF,
(word >> 8) & 0xFF,
((word >> 17) & 1) != 0);
sources = opcode switch
{
"DsWriteB32" => [
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Vector(vectorData0),
],
"DsWriteB64" => [
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Vector(vectorData0),
Gen5Operand.Vector(vectorData0 + 1),
],
"DsWrite2B32" or "DsWrite2St64B32" => [
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Vector(vectorData0),
Gen5Operand.Vector(vectorData1),
],
"DsSwizzleB32" => [Gen5Operand.Vector(vectorData0)],
_ => [Gen5Operand.Vector(vectorAddress)],
};
destinations = opcode switch
{
"DsReadB32" or "DsSwizzleB32" => [
Gen5Operand.Vector(vectorDestination),
],
"DsRead2B32" or "DsRead2St64B32" => [
Gen5Operand.Vector(vectorDestination),
Gen5Operand.Vector(vectorDestination + 1),
],
_ => [],
};
break;
}
case Gen5ShaderEncoding.Vintrp:
sources = [Gen5Operand.Vector(word & 0xFF)];
destinations = [Gen5Operand.Vector((word >> 18) & 0xFF)];
control = new Gen5InterpolationControl(
(word >> 10) & 0x3F,
(word >> 8) & 0x3);
break;
case Gen5ShaderEncoding.Flat:
{
var extra = words[1];
var vectorAddress = extra & 0xFF;
var vectorData = (extra >> 8) & 0xFF;
var scalarAddress = (extra >> 16) & 0x7F;
var dwordCount = opcode switch
{
"GlobalLoadDword" => 1u,
"GlobalLoadDwordx2" => 2u,
"GlobalLoadDwordx3" => 3u,
"GlobalLoadDwordx4" => 4u,
_ => 0u,
};
sources =
[
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Scalar(scalarAddress),
];
destinations = Enumerable
.Range((int)vectorData, checked((int)dwordCount))
.Select(index => Gen5Operand.Vector((uint)index))
.ToArray();
control = new Gen5GlobalMemoryControl(
dwordCount,
vectorAddress,
vectorData,
scalarAddress,
SignExtend(word & 0x1FFF, 13),
((word >> 16) & 1) != 0,
((word >> 17) & 1) != 0);
break;
}
case Gen5ShaderEncoding.Mubuf:
{
var extra = words[1];
var vectorAddress = extra & 0xFF;
var vectorData = (extra >> 8) & 0xFF;
var scalarResource = ((extra >> 16) & 0x1F) * 4;
var scalarOffset = (extra >> 24) & 0xFF;
var dwordCount = opcode switch
{
"BufferLoadFormatX" => 1u,
"BufferLoadFormatXy" => 2u,
"BufferLoadFormatXyz" => 3u,
"BufferLoadFormatXyzw" => 4u,
"BufferLoadDword" => 1u,
"BufferLoadDwordx2" => 2u,
"BufferLoadDwordx3" => 3u,
"BufferLoadDwordx4" => 4u,
"BufferStoreDword" => 1u,
"BufferStoreDwordx2" => 2u,
"BufferStoreDwordx3" => 3u,
"BufferStoreDwordx4" => 4u,
"BufferAtomicAdd" => 1u,
_ => 0u,
};
sources =
[
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Scalar(scalarResource),
Gen5Operand.Source(scalarOffset, literal),
];
destinations = Enumerable
.Range((int)vectorData, checked((int)dwordCount))
.Select(index => Gen5Operand.Vector((uint)index))
.ToArray();
control = new Gen5BufferMemoryControl(
dwordCount,
vectorAddress,
vectorData,
scalarResource,
(int)(word & 0xFFF),
((word >> 13) & 1) != 0,
((word >> 12) & 1) != 0,
((word >> 14) & 1) != 0,
((extra >> 22) & 1) != 0);
break;
}
case Gen5ShaderEncoding.Mtbuf:
{
var extra = words[1];
var vectorAddress = extra & 0xFF;
var vectorData = (extra >> 8) & 0xFF;
var scalarResource = ((extra >> 16) & 0x1F) * 4;
var scalarOffset = (extra >> 24) & 0xFF;
var dwordCount = opcode switch
{
"TBufferLoadFormatX" => 1u,
"TBufferLoadFormatXy" => 2u,
"TBufferLoadFormatXyz" => 3u,
"TBufferLoadFormatXyzw" => 4u,
_ => 0u,
};
sources =
[
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Scalar(scalarResource),
Gen5Operand.Source(scalarOffset, literal),
];
destinations = Enumerable
.Range((int)vectorData, checked((int)dwordCount))
.Select(index => Gen5Operand.Vector((uint)index))
.ToArray();
control = new Gen5BufferMemoryControl(
dwordCount,
vectorAddress,
vectorData,
scalarResource,
(int)(word & 0xFFF),
((word >> 13) & 1) != 0,
((word >> 12) & 1) != 0,
((word >> 14) & 1) != 0,
((extra >> 22) & 1) != 0);
break;
}
case Gen5ShaderEncoding.Mimg:
{
var extra = words[1];
var vectorAddress = extra & 0xFF;
var vectorData = (extra >> 8) & 0xFF;
var scalarResource = ((extra >> 16) & 0x1F) * 4;
var scalarSampler = ((extra >> 21) & 0x1F) * 4;
var addressRegisters = new List<uint>(1 + Math.Max(0, words.Length - 2) * 4)
{
vectorAddress,
};
for (var wordIndex = 2; wordIndex < words.Length; wordIndex++)
{
for (var shift = 0; shift < 32; shift += 8)
{
addressRegisters.Add((words[wordIndex] >> shift) & 0xFF);
}
}
var imageSources = new List<Gen5Operand>(addressRegisters.Count + 2);
foreach (var addressRegister in addressRegisters)
{
imageSources.Add(Gen5Operand.Vector(addressRegister));
}
imageSources.Add(Gen5Operand.Scalar(scalarResource));
imageSources.Add(Gen5Operand.Scalar(scalarSampler));
sources = imageSources;
destinations = opcode.StartsWith("ImageStore", StringComparison.Ordinal)
? []
: [Gen5Operand.Vector(vectorData)];
var dimension = (word >> 3) & 0x7;
control = new Gen5ImageControl(
(word >> 8) & 0xF,
vectorAddress,
addressRegisters,
vectorData,
scalarResource,
scalarSampler,
dimension,
dimension is 4 or 5 or 7,
((word >> 13) & 1) != 0,
((word >> 25) & 1) != 0);
break;
}
case Gen5ShaderEncoding.Exp:
{
var extra = words[1];
sources =
[
Gen5Operand.Vector(extra & 0xFF),
Gen5Operand.Vector((extra >> 8) & 0xFF),
Gen5Operand.Vector((extra >> 16) & 0xFF),
Gen5Operand.Vector((extra >> 24) & 0xFF),
];
control = new Gen5ExportControl(
(word >> 4) & 0x3F,
word & 0xF,
((word >> 10) & 1) != 0,
((word >> 11) & 1) != 0,
((word >> 12) & 1) != 0);
break;
}
}
return new Gen5ShaderInstruction(pc, encoding, opcode, words, sources, destinations, control);
}
private static Gen5DppControl CreateDppControl(uint word) =>
new(
(word >> 8) & 0x1FF,
((word >> 18) & 1) != 0,
((word >> 19) & 1) != 0,
((word >> 21) & 1) | (((word >> 23) & 1) << 1),
((word >> 20) & 1) | (((word >> 22) & 1) << 1),
(word >> 24) & 0xF,
(word >> 28) & 0xF);
private static int MinimumEncodingDwords(Gen5ShaderEncoding encoding) => encoding switch
{
Gen5ShaderEncoding.Vop3 or
Gen5ShaderEncoding.Smem or
Gen5ShaderEncoding.Mubuf or
Gen5ShaderEncoding.Mtbuf or
Gen5ShaderEncoding.Ds or
Gen5ShaderEncoding.Flat or
Gen5ShaderEncoding.Vop3p or
Gen5ShaderEncoding.Mimg or
Gen5ShaderEncoding.Exp => 2,
_ => 1,
};
private static uint ScalarLoadDwordCount(string opcode) => opcode switch
{
"SLoadDword" or "SBufferLoadDword" => 1,
"SLoadDwordx2" or "SBufferLoadDwordx2" => 2,
"SLoadDwordx4" or "SBufferLoadDwordx4" => 4,
"SLoadDwordx8" or "SBufferLoadDwordx8" => 8,
"SLoadDwordx16" or "SBufferLoadDwordx16" => 16,
_ => 0,
};
private static int SignExtend(uint value, int bits)
{
var shift = 32 - bits;
return (int)(value << shift) >> shift;
}
private static void AddFeatureCount(Dictionary<string, int> counts, string key)
{
counts.TryGetValue(key, out var count);
counts[key] = count + 1;
}
private static bool TryReadUInt32(CpuContext ctx, ulong address, out uint value)
{
Span<byte> bytes = stackalloc byte[sizeof(uint)];
if (!ctx.Memory.TryRead(address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt32LittleEndian(bytes);
return true;
}
private readonly record struct ShaderDecodeInfo(
int InstructionCount,
Dictionary<string, int> Counts,
Dictionary<string, int> FeatureCounts,
Dictionary<string, int> MimgCounts,
List<string> Details)
{
public static ShaderDecodeInfo Create(Gen5ShaderProgram program)
{
var counts = new Dictionary<string, int>(StringComparer.Ordinal);
var featureCounts = new Dictionary<string, int>(StringComparer.Ordinal);
var mimgCounts = new Dictionary<string, int>(StringComparer.Ordinal);
var details = new List<string>();
foreach (var instruction in program.Instructions)
{
AddFeatureCount(counts, instruction.Opcode);
AddFeatureCount(featureCounts, ClassifyInstruction(instruction.Opcode));
if (instruction.Control is Gen5ImageControl image)
{
AddFeatureCount(mimgCounts, $"{instruction.Opcode}/dmask=0x{image.Dmask:X}");
}
if (details.Count < 16 && DescribeInstruction(instruction) is { } detail)
{
details.Add(detail);
}
}
return new ShaderDecodeInfo(
program.Instructions.Count,
counts,
featureCounts,
mimgCounts,
details);
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append("ins=");
builder.Append(InstructionCount);
AppendCounts(builder, " features=", FeatureCounts, 16);
builder.Append(" ops=");
AppendCounts(builder, string.Empty, Counts, 128);
AppendCounts(builder, " mimg=", MimgCounts, 12);
AppendDetails(builder, Details, 10);
return builder.ToString();
}
private static string? DescribeInstruction(Gen5ShaderInstruction instruction)
{
if (instruction.Control is Gen5ImageControl image)
{
var addressRegisters = string.Join(
'/',
image.AddressRegisters.Select(register => $"v{register}"));
return
$"{instruction.Opcode}@0x{instruction.Pc:X}:dm=0x{image.Dmask:X}," +
$"va={addressRegisters},vd=v{image.VectorData}," +
$"sr=s{image.ScalarResource},ss=s{image.ScalarSampler}," +
$"dim={image.Dimension},da={(image.IsArray ? 1 : 0)}," +
$"glc={(image.Glc ? 1 : 0)}," +
$"slc={(image.Slc ? 1 : 0)}";
}
if (instruction.Control is Gen5ExportControl export)
{
return
$"Exp@0x{instruction.Pc:X}:target=0x{export.Target:X}," +
$"en=0x{export.EnableMask:X},compr={(export.Compressed ? 1 : 0)}," +
$"done={(export.Done ? 1 : 0)},vm={(export.ValidMask ? 1 : 0)}," +
$"src={string.Join('/', instruction.Sources)}";
}
if (instruction.Control is Gen5InterpolationControl interpolation)
{
return
$"{instruction.Opcode}@0x{instruction.Pc:X}:" +
$"attr={interpolation.Attribute},chan={interpolation.Channel}," +
$"src={instruction.Sources[0]},dst={instruction.Destinations[0]}";
}
return null;
}
private static void AppendCounts(
StringBuilder builder,
string prefix,
Dictionary<string, int> counts,
int limit)
{
if (counts.Count == 0)
{
return;
}
builder.Append(prefix);
var written = 0;
foreach (var (name, count) in counts)
{
if (written != 0)
{
builder.Append(',');
}
builder.Append(name);
builder.Append(':');
builder.Append(count);
written++;
if (written == limit && counts.Count > written)
{
builder.Append(",...");
break;
}
}
}
private static void AppendDetails(StringBuilder builder, List<string> details, int limit)
{
if (details.Count == 0)
{
return;
}
builder.Append(" detail=");
var written = 0;
foreach (var detail in details)
{
if (written != 0)
{
builder.Append(';');
}
builder.Append(detail);
written++;
if (written == limit && details.Count > written)
{
builder.Append(";...");
break;
}
}
}
}
}