// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Numerics;
namespace SharpEmu.ShaderCompiler;
public static class Gen5ShaderScalarEvaluator
{
///
/// Optional fallback for global-memory reads that ctx.Memory cannot satisfy (the
/// emulator installs the HLE-tracked libc heap reader here at module load). Kept as
/// a hook so this project never depends on the HLE module implementations.
///
public static Gen5FallbackMemoryReader? FallbackMemoryReader { get; set; }
public delegate bool Gen5FallbackMemoryReader(ulong baseAddress, Span destination);
private const int ScalarRegisterCount = 256;
private const int ImageDescriptorDwords = 8;
private const int SamplerDescriptorDwords = 4;
private const int MaxGlobalMemoryBindingBytes = 16 * 1024 * 1024;
private static readonly int DefaultGlobalMemoryBindingBytes =
int.TryParse(
Environment.GetEnvironmentVariable("SHARPEMU_GLOBAL_BINDING_BYTES"),
out var configured) && configured >= sizeof(uint)
? Math.Min(configured, MaxGlobalMemoryBindingBytes)
: 1 * 1024 * 1024;
public static long GlobalMemoryReadCount;
public static long GlobalMemoryReadBytes;
public static long GlobalMemoryReadCacheHits;
public static long GlobalMemoryReadPvmBytes;
public static long GlobalMemoryReadLibcBytes;
public static long GlobalMemoryReadReuses;
private const long CrossFrameReadCacheMaxBytes = 1024L * 1024 * 1024;
private static readonly object _crossFrameReadGate = new();
private static readonly Dictionary<(ulong BaseAddress, int SizeBytes), byte[]> _crossFrameReadCache = new();
private static long _crossFrameReadCacheBytes;
private const ulong RdnaWaveMask = 0xFFFF_FFFFUL;
private readonly record struct BufferDescriptor(
ulong BaseAddress,
uint Stride,
uint NumRecords,
ulong SizeBytes,
uint NumberFormat,
uint DataFormat);
public static bool TryResolveImageBindings(
CpuContext ctx,
Gen5ShaderState state,
out IReadOnlyList bindings,
out string error)
{
if (TryEvaluate(ctx, state, out var evaluation, out error))
{
bindings = evaluation.ImageBindings;
return true;
}
bindings = [];
return false;
}
public static bool TryEvaluate(
CpuContext ctx,
Gen5ShaderState state,
out Gen5ShaderEvaluation evaluation,
out string error,
bool resolveVertexInputs = false,
uint? vertexRecordLimit = null)
{
evaluation = default!;
error = string.Empty;
var scalarRegisters = new uint[ScalarRegisterCount];
for (var index = 0;
index < state.UserData.Count &&
state.UserDataScalarRegisterBase + (uint)index < scalarRegisters.Length;
index++)
{
scalarRegisters[state.UserDataScalarRegisterBase + (uint)index] =
state.UserData[index];
}
if (state.ComputeSystemRegisters is { } computeSystemRegisters)
{
computeSystemRegisters.ClearStaticValues(scalarRegisters);
}
var execMask = RdnaWaveMask;
WriteScalarPair(scalarRegisters, 106, 0, ref execMask);
WriteScalarPair(scalarRegisters, 126, execMask, ref execMask);
var initialScalarRegisters = (uint[])scalarRegisters.Clone();
var resolved = new List();
var globalMemoryBindings = new List();
var globalMemoryByAddress = new Dictionary<(uint ScalarAddress, ulong BaseAddress), Gen5GlobalMemoryBinding>();
var vertexInputBindings = new List();
var runtimeScalarRegisters = CollectRuntimeScalarRegisters(state.Program);
var scalarRegisterSnapshots = new Dictionary>();
var scalarConditionCode = false;
uint? skipUntilPc = null;
foreach (var instruction in state.Program.Instructions)
{
if (skipUntilPc.HasValue)
{
if (instruction.Pc < skipUntilPc.Value)
{
continue;
}
skipUntilPc = null;
}
scalarRegisterSnapshots[instruction.Pc] = (uint[])scalarRegisters.Clone();
if (instruction.Opcode == "SEndpgm")
{
break;
}
if (instruction.Opcode == "SBranch" &&
TryGetSoppBranchTargetPc(instruction, out var targetPc) &&
targetPc > instruction.Pc)
{
skipUntilPc = targetPc;
continue;
}
if (instruction.Encoding == Gen5ShaderEncoding.Sopc)
{
if (!TryExecuteScalarCompare(
instruction,
scalarRegisters,
out scalarConditionCode,
out error))
{
return false;
}
continue;
}
if (instruction.Encoding == Gen5ShaderEncoding.Sopk &&
instruction.Opcode.StartsWith("SCmpk", StringComparison.Ordinal))
{
if (!TryExecuteScalarCompareK(
instruction,
scalarRegisters,
out scalarConditionCode,
out error))
{
return false;
}
continue;
}
if (instruction.Encoding is
Gen5ShaderEncoding.Sop1 or
Gen5ShaderEncoding.Sop2 or
Gen5ShaderEncoding.Sopk)
{
if (instruction.Opcode is "SSetpcB64" or "SSwappcB64")
{
break;
}
if (!TryExecuteScalarAlu(
instruction,
state.Program.Address,
scalarRegisters,
ref execMask,
ref scalarConditionCode,
out error))
{
return false;
}
continue;
}
if (instruction.Control is Gen5ScalarMemoryControl scalarMemory)
{
foreach (var destination in instruction.Destinations)
{
if (destination.Kind == Gen5OperandKind.ScalarRegister && destination.Value < ScalarRegisterCount)
{
runtimeScalarRegisters.Add(destination.Value);
}
}
if (!TryExecuteScalarLoad(ctx, state, instruction, scalarMemory, scalarRegisters, globalMemoryBindings, globalMemoryByAddress, runtimeScalarRegisters, out error))
{
return false;
}
continue;
}
if (instruction.Control is Gen5GlobalMemoryControl globalMemory)
{
if (globalMemory.ScalarAddress >= ScalarRegisterCount - 1)
{
error =
$"global-address-register-range pc=0x{instruction.Pc:X} " +
$"s{globalMemory.ScalarAddress}";
return false;
}
var baseAddress =
scalarRegisters[globalMemory.ScalarAddress] |
((ulong)scalarRegisters[globalMemory.ScalarAddress + 1] << 32);
if (baseAddress == 0)
{
error = $"global-address-null pc=0x{instruction.Pc:X}";
return false;
}
var key = (globalMemory.ScalarAddress, baseAddress);
if (globalMemoryByAddress.TryGetValue(key, out var existingBinding))
{
if (existingBinding.InstructionPcs is List instructionPcs)
{
instructionPcs.Add(instruction.Pc);
}
}
else
{
byte[] data;
if (!TryReadGlobalMemory(ctx, baseAddress, out data))
{
error =
$"global-memory-read-failed pc=0x{instruction.Pc:X} " +
$"address=0x{baseAddress:X16}";
return false;
}
var binding = new Gen5GlobalMemoryBinding(
globalMemory.ScalarAddress,
baseAddress,
new List { instruction.Pc },
data);
globalMemoryByAddress.Add(key, binding);
globalMemoryBindings.Add(binding);
}
continue;
}
if (instruction.Control is Gen5BufferMemoryControl bufferMemory)
{
if (bufferMemory.ScalarResource >= ScalarRegisterCount - 3)
{
error =
$"buffer-resource-register-range pc=0x{instruction.Pc:X} " +
$"s{bufferMemory.ScalarResource}";
return false;
}
if (!TryDecodeBufferDescriptor(
scalarRegisters,
bufferMemory.ScalarResource,
strictType: true,
out var bufferDescriptor))
{
error =
$"buffer-descriptor-invalid pc=0x{instruction.Pc:X} " +
$"s{bufferMemory.ScalarResource}";
return false;
}
if (bufferDescriptor.BaseAddress == 0)
{
error = $"buffer-address-null pc=0x{instruction.Pc:X}";
return false;
}
if (resolveVertexInputs &&
IsVertexFetchCandidate(instruction, bufferMemory, bufferDescriptor))
{
var vertexReadSize = bufferDescriptor.SizeBytes;
if (vertexRecordLimit is { } recordLimit &&
instruction.Sources.Count > 2 &&
TryEvaluateScalarOperand(
instruction.Sources[2],
scalarRegisters,
out var scalarOffset))
{
var bindingOffset = unchecked((uint)bufferMemory.OffsetBytes + scalarOffset);
var requiredBytes =
(ulong)bindingOffset +
(ulong)(Math.Max(recordLimit, 1u) - 1u) * bufferDescriptor.Stride +
(ulong)bufferMemory.DwordCount * sizeof(uint);
vertexReadSize = Math.Min(
bufferDescriptor.SizeBytes,
Math.Max(requiredBytes, sizeof(uint)));
}
if (!TryReadGlobalMemory(
ctx,
bufferDescriptor.BaseAddress,
vertexReadSize,
out var vertexData))
{
error =
$"vertex-buffer-read-failed pc=0x{instruction.Pc:X} " +
$"address=0x{bufferDescriptor.BaseAddress:X16} " +
$"bytes={bufferDescriptor.SizeBytes} " +
$"stride={bufferDescriptor.Stride} records={bufferDescriptor.NumRecords}";
return false;
}
if (!TryCreateVertexInputBinding(
instruction,
bufferMemory,
bufferDescriptor,
vertexData,
(uint)vertexInputBindings.Count,
scalarRegisters,
out var vertexInputBinding))
{
error =
$"vertex-input-binding-failed pc=0x{instruction.Pc:X} " +
$"s{bufferMemory.ScalarResource}";
return false;
}
vertexInputBindings.Add(vertexInputBinding);
continue;
}
var key = (bufferMemory.ScalarResource, bufferDescriptor.BaseAddress);
if (globalMemoryByAddress.TryGetValue(key, out var existingBinding))
{
if (existingBinding.InstructionPcs is List instructionPcs)
{
instructionPcs.Add(instruction.Pc);
}
}
else
{
if (!TryReadGlobalMemory(
ctx,
bufferDescriptor.BaseAddress,
bufferDescriptor.SizeBytes,
out var data))
{
var descriptorWords = string.Join(
':',
Enumerable.Range(0, 4).Select(index =>
$"{scalarRegisters[bufferMemory.ScalarResource + (uint)index]:X8}"));
error =
$"buffer-memory-read-failed pc=0x{instruction.Pc:X} " +
$"address=0x{bufferDescriptor.BaseAddress:X16} " +
$"bytes={bufferDescriptor.SizeBytes} " +
$"stride={bufferDescriptor.Stride} records={bufferDescriptor.NumRecords} " +
$"s{bufferMemory.ScalarResource}=[{descriptorWords}]";
return false;
}
var binding = new Gen5GlobalMemoryBinding(
bufferMemory.ScalarResource,
bufferDescriptor.BaseAddress,
new List { instruction.Pc },
data);
globalMemoryByAddress.Add(key, binding);
globalMemoryBindings.Add(binding);
}
continue;
}
if (instruction.Control is not Gen5ImageControl image)
{
continue;
}
if (!TryCopyRegisters(
scalarRegisters,
image.ScalarResource,
ImageDescriptorDwords,
out var resourceDescriptor))
{
error = $"resource-register-range pc=0x{instruction.Pc:X} s{image.ScalarResource}";
return false;
}
IReadOnlyList samplerDescriptor = [];
if (UsesSampler(instruction.Opcode) &&
!TryCopyRegisters(
scalarRegisters,
image.ScalarSampler,
SamplerDescriptorDwords,
out samplerDescriptor))
{
error = $"sampler-register-range pc=0x{instruction.Pc:X} s{image.ScalarSampler}";
return false;
}
resolved.Add(new Gen5ImageBinding(
instruction.Pc,
instruction.Opcode,
image,
resourceDescriptor,
samplerDescriptor,
instruction.Opcode is "ImageLoadMip" or "ImageStoreMip" &&
TryResolveVectorConstantBefore(
state.Program,
instruction.Pc,
image.GetAddressRegister(2),
out var mipLevel)
? mipLevel
: null));
}
evaluation = new Gen5ShaderEvaluation(
initialScalarRegisters,
scalarRegisters,
scalarRegisterSnapshots,
resolved,
globalMemoryBindings,
state.ComputeSystemRegisters,
runtimeScalarRegisters,
vertexInputBindings);
return true;
}
private static bool TryCreateVertexInputBinding(
Gen5ShaderInstruction instruction,
Gen5BufferMemoryControl control,
BufferDescriptor descriptor,
byte[] data,
uint location,
uint[] scalarRegisters,
out Gen5VertexInputBinding binding)
{
binding = default!;
if (!IsVertexFetchCandidate(instruction, control, descriptor) ||
instruction.Sources.Count <= 2 ||
!TryEvaluateScalarOperand(instruction.Sources[2], scalarRegisters, out var scalarOffset))
{
return false;
}
var bindingData = data;
var bindingStride = descriptor.Stride;
var bindingOffset = unchecked((uint)control.OffsetBytes + scalarOffset);
var bindingDataFormat = descriptor.DataFormat;
var bindingNumberFormat = descriptor.NumberFormat;
binding = new Gen5VertexInputBinding(
instruction.Pc,
location,
control.DwordCount,
bindingDataFormat,
bindingNumberFormat,
descriptor.BaseAddress,
bindingStride,
bindingOffset,
bindingData);
return true;
}
private static bool IsVertexFetchCandidate(
Gen5ShaderInstruction instruction,
Gen5BufferMemoryControl control,
BufferDescriptor descriptor) =>
control.IndexEnabled &&
!control.OffsetEnabled &&
control.DwordCount is >= 1 and <= 4 &&
descriptor.BaseAddress != 0 &&
descriptor.Stride != 0 &&
(instruction.Opcode.StartsWith("BufferLoadFormat", StringComparison.Ordinal) ||
instruction.Opcode.StartsWith("TBufferLoadFormat", StringComparison.Ordinal));
private static HashSet CollectRuntimeScalarRegisters(Gen5ShaderProgram program)
{
var registers = new HashSet();
foreach (var instruction in program.Instructions)
{
foreach (var operand in instruction.Sources.Concat(instruction.Destinations))
{
if (operand.Kind == Gen5OperandKind.ScalarRegister &&
operand.Value < ScalarRegisterCount)
{
registers.Add(operand.Value);
}
}
if (instruction.Control is Gen5ScalarMemoryControl
{
DynamicOffsetRegister: { } offsetRegister,
} &&
offsetRegister < ScalarRegisterCount)
{
registers.Add(offsetRegister);
}
}
return registers;
}
private static bool TryGetSoppBranchTargetPc(
Gen5ShaderInstruction instruction,
out uint targetPc)
{
targetPc = 0;
if (instruction.Encoding != Gen5ShaderEncoding.Sopp ||
instruction.Words.Count == 0)
{
return false;
}
var offset = unchecked((short)(instruction.Words[0] & 0xFFFF));
var nextPc = (long)instruction.Pc + instruction.Words.Count * sizeof(uint);
var target = nextPc + offset * sizeof(uint);
if (target < 0 || target > uint.MaxValue)
{
return false;
}
targetPc = (uint)target;
return true;
}
private static bool TryResolveVectorConstantBefore(
Gen5ShaderProgram program,
uint pc,
uint vectorRegister,
out uint value)
{
for (var index = program.Instructions.Count - 1; index >= 0; index--)
{
var instruction = program.Instructions[index];
if (instruction.Pc >= pc ||
instruction.Destinations.Count != 1 ||
instruction.Destinations[0] is not
{
Kind: Gen5OperandKind.VectorRegister,
Value: var destination,
} ||
destination != vectorRegister)
{
continue;
}
if (instruction.Opcode == "VMovB32" &&
instruction.Sources.Count == 1 &&
TryResolveConstantOperand(instruction.Sources[0], out value))
{
return true;
}
break;
}
value = 0;
return false;
}
private static bool TryResolveConstantOperand(Gen5Operand operand, out uint value)
{
if (operand.Kind == Gen5OperandKind.LiteralConstant)
{
value = operand.Value;
return true;
}
if (operand.Kind == Gen5OperandKind.EncodedConstant)
{
return TryDecodeInlineConstant(operand.Value, out value);
}
value = 0;
return false;
}
[ThreadStatic]
private static Dictionary<(ulong BaseAddress, int SizeBytes), byte[]>? _globalMemoryReadCache;
public static void BeginGlobalMemoryReadScope()
{
_globalMemoryReadCache = new Dictionary<(ulong, int), byte[]>();
}
public static void EndGlobalMemoryReadScope()
{
_globalMemoryReadCache = null;
}
private static bool TryReadGlobalMemory(
CpuContext ctx,
ulong baseAddress,
out byte[] data)
{
return TryReadGlobalMemory(
ctx,
baseAddress,
(ulong)DefaultGlobalMemoryBindingBytes,
out data);
}
private static bool TryReadGlobalMemory(
CpuContext ctx,
ulong baseAddress,
ulong sizeBytes,
out byte[] data)
{
if (sizeBytes == 0)
{
data = [];
return false;
}
var cappedSize = Math.Min(sizeBytes, MaxGlobalMemoryBindingBytes);
if (cappedSize > int.MaxValue)
{
data = [];
return false;
}
var cache = _globalMemoryReadCache;
var cacheKey = (baseAddress, (int)cappedSize);
if (cache is not null && cache.TryGetValue(cacheKey, out var cached))
{
Interlocked.Increment(ref GlobalMemoryReadCacheHits);
data = cached;
return true;
}
byte[]? previous;
lock (_crossFrameReadGate)
{
_crossFrameReadCache.TryGetValue(cacheKey, out previous);
}
if (previous is not null && ctx.Memory.TryCompare(baseAddress, previous))
{
Interlocked.Increment(ref GlobalMemoryReadReuses);
if (cache is not null)
{
cache[cacheKey] = previous;
}
data = previous;
return true;
}
var candidateSize = (int)cappedSize;
while (candidateSize >= sizeof(uint))
{
data = GC.AllocateUninitializedArray(candidateSize);
var readFromPvm = ctx.Memory.TryRead(baseAddress, data);
if (readFromPvm ||
FallbackMemoryReader?.Invoke(baseAddress, data) == true)
{
Interlocked.Increment(ref GlobalMemoryReadCount);
Interlocked.Add(ref GlobalMemoryReadBytes, data.Length);
if (readFromPvm)
{
Interlocked.Add(ref GlobalMemoryReadPvmBytes, data.Length);
}
else
{
Interlocked.Add(ref GlobalMemoryReadLibcBytes, data.Length);
}
if (cache is not null)
{
cache[cacheKey] = data;
}
lock (_crossFrameReadGate)
{
if (_crossFrameReadCache.TryGetValue(cacheKey, out var replaced))
{
_crossFrameReadCacheBytes -= replaced.Length;
}
if (_crossFrameReadCacheBytes + data.Length > CrossFrameReadCacheMaxBytes)
{
_crossFrameReadCache.Clear();
_crossFrameReadCacheBytes = 0;
}
_crossFrameReadCache[cacheKey] = data;
_crossFrameReadCacheBytes += data.Length;
}
return true;
}
if (candidateSize == sizeof(uint))
{
break;
}
candidateSize = Math.Max(candidateSize / 2, sizeof(uint));
}
data = [];
return false;
}
private static bool TryExecuteScalarAlu(
Gen5ShaderInstruction instruction,
ulong programAddress,
uint[] registers,
ref ulong execMask,
ref bool scalarConditionCode,
out string error)
{
error = string.Empty;
if (instruction.Destinations.Count != 1 ||
instruction.Destinations[0] is not
{
Kind: Gen5OperandKind.ScalarRegister,
Value: < ScalarRegisterCount,
} destination)
{
error = $"unsupported-scalar-destination pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
if (instruction.Opcode == "SMovkI32")
{
registers[destination.Value] = unchecked((uint)(short)instruction.Sources[0].Value);
return true;
}
if (instruction.Opcode is "SAddkI32" or "SMulkI32")
{
var immediate = unchecked((uint)(short)instruction.Sources[0].Value);
registers[destination.Value] = instruction.Opcode == "SAddkI32"
? registers[destination.Value] + immediate
: unchecked((uint)((int)registers[destination.Value] * (int)immediate));
return true;
}
if (instruction.Opcode == "SGetpcB64")
{
var pc = programAddress + instruction.Pc + (ulong)(instruction.Words.Count * sizeof(uint));
WriteScalarPair(registers, destination.Value, pc, ref execMask);
return true;
}
if (TryExecuteSaveExecScalarAlu(
instruction,
registers,
ref execMask,
ref scalarConditionCode,
out error))
{
return true;
}
if (instruction.Opcode is "SMovB64" or "SWqmB64" or "SNotB64")
{
if (destination.Value >= ScalarRegisterCount - 1 ||
!TryEvaluateScalarOperand64(
instruction.Sources[0],
registers,
execMask,
out var value))
{
error = $"scalar-source64 pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
if (instruction.Opcode == "SNotB64")
{
value = ~value;
scalarConditionCode = value != 0;
}
WriteScalarPair(registers, destination.Value, value, ref execMask);
return true;
}
if (instruction.Opcode is "SLshlB64" or "SLshrB64")
{
if (instruction.Sources.Count < 2 ||
destination.Value >= ScalarRegisterCount - 1 ||
!TryEvaluateScalarOperand64(
instruction.Sources[0],
registers,
execMask,
out var value) ||
!TryEvaluateScalarOperand(
instruction.Sources[1],
registers,
out var shift))
{
error = $"scalar-source64 pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
value = instruction.Opcode == "SLshlB64"
? value << ((int)shift & 63)
: value >> ((int)shift & 63);
WriteScalarPair(registers, destination.Value, value, ref execMask);
scalarConditionCode = value != 0;
return true;
}
if (instruction.Opcode is "SBfeU64" or "SBfeI64")
{
if (instruction.Sources.Count < 2 ||
destination.Value >= ScalarRegisterCount - 1 ||
!TryEvaluateScalarOperand64(
instruction.Sources[0],
registers,
execMask,
out var source) ||
!TryEvaluateScalarOperand(
instruction.Sources[1],
registers,
out var control))
{
error = $"scalar-source64 pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
var offset = (int)control & 63;
var width = Math.Min(((int)control >> 16) & 0x7F, 64 - offset);
ulong value;
if (width == 0)
{
value = 0;
}
else
{
value = source >> offset;
if (width < 64)
{
value &= ulong.MaxValue >> (64 - width);
if (instruction.Opcode == "SBfeI64")
{
value = unchecked((ulong)((long)(value << (64 - width)) >> (64 - width)));
}
}
}
WriteScalarPair(registers, destination.Value, value, ref execMask);
scalarConditionCode = value != 0;
return true;
}
if (instruction.Opcode is
"SCselectB64" or
"SAndB64" or
"SOrB64" or
"SXorB64" or
"SAndn2B64" or
"SOrn2B64" or
"SNandB64" or
"SNorB64" or
"SXnorB64")
{
if (instruction.Sources.Count < 2 ||
!TryEvaluateScalarOperand64(
instruction.Sources[0],
registers,
execMask,
out var maskLeft) ||
!TryEvaluateScalarOperand64(
instruction.Sources[1],
registers,
execMask,
out var maskRight))
{
error = $"scalar-source64 pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
var value = instruction.Opcode switch
{
"SCselectB64" => scalarConditionCode ? maskLeft : maskRight,
"SAndB64" => maskLeft & maskRight,
"SOrB64" => maskLeft | maskRight,
"SXorB64" => maskLeft ^ maskRight,
"SAndn2B64" => maskLeft & ~maskRight,
"SOrn2B64" => maskLeft | ~maskRight,
"SNandB64" => ~(maskLeft & maskRight),
"SNorB64" => ~(maskLeft | maskRight),
_ => ~(maskLeft ^ maskRight),
};
WriteScalarPair(registers, destination.Value, value, ref execMask);
scalarConditionCode = value != 0;
return true;
}
if (instruction.Sources.Count == 0 ||
!TryEvaluateScalarOperand(instruction.Sources[0], registers, out var left))
{
var source = instruction.Sources.Count == 0
? ""
: instruction.Sources[0].ToString();
error = $"scalar-source0 pc=0x{instruction.Pc:X} op={instruction.Opcode} source={source}";
return false;
}
if (instruction.Opcode == "SMovB32")
{
registers[destination.Value] = left;
return true;
}
if (instruction.Opcode is
"SNotB32" or
"SBrevB32" or
"SBcnt1I32B32" or
"SFF1I32B32" or
"SBitset1B32")
{
registers[destination.Value] = instruction.Opcode switch
{
"SNotB32" => ~left,
"SBrevB32" => ReverseBits(left),
"SBcnt1I32B32" => (uint)BitOperations.PopCount(left),
"SFF1I32B32" => left == 0 ? uint.MaxValue : (uint)BitOperations.TrailingZeroCount(left),
_ => registers[destination.Value] | (1u << ((int)left & 31)),
};
if (instruction.Opcode != "SBitset1B32")
{
scalarConditionCode = registers[destination.Value] != 0;
}
return true;
}
if (instruction.Sources.Count < 2 ||
!TryEvaluateScalarOperand(instruction.Sources[1], registers, out var right))
{
var source = instruction.Sources.Count < 2
? ""
: instruction.Sources[1].ToString();
error = $"scalar-source1 pc=0x{instruction.Pc:X} op={instruction.Opcode} source={source}";
return false;
}
uint result;
switch (instruction.Opcode)
{
case "SAddU32":
{
var wide = (ulong)left + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SSubU32":
result = 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":
{
var wide = (ulong)left + right + (scalarConditionCode ? 1UL : 0UL);
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SSubbU32":
{
var borrow = scalarConditionCode ? 1UL : 0UL;
var subtrahend = (ulong)right + borrow;
result = unchecked(left - (uint)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;
break;
case "SAndB32":
result = left & right;
scalarConditionCode = result != 0;
break;
case "SOrB32":
result = left | right;
scalarConditionCode = result != 0;
break;
case "SXorB32":
result = left ^ right;
scalarConditionCode = result != 0;
break;
case "SAndn2B32":
result = left & ~right;
scalarConditionCode = result != 0;
break;
case "SOrn2B32":
result = left | ~right;
scalarConditionCode = result != 0;
break;
case "SNandB32":
result = ~(left & right);
scalarConditionCode = result != 0;
break;
case "SNorB32":
result = ~(left | right);
scalarConditionCode = result != 0;
break;
case "SXnorB32":
result = ~(left ^ right);
scalarConditionCode = result != 0;
break;
case "SLshlB32":
result = left << ((int)right & 31);
scalarConditionCode = result != 0;
break;
case "SLshrB32":
result = left >> ((int)right & 31);
scalarConditionCode = result != 0;
break;
case "SAshrI32":
result = unchecked((uint)((int)left >> ((int)right & 31)));
scalarConditionCode = result != 0;
break;
case "SBfmB32":
{
var width = (int)left & 31;
var offset = (int)right & 31;
result = width == 0 ? 0 : ((1u << width) - 1u) << offset;
break;
}
case "SMulI32":
result = unchecked((uint)((int)left * (int)right));
break;
case "SBfeU32":
{
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":
{
var offset = (int)right & 31;
var width = Math.Min(((int)right >> 16) & 0x7F, 32 - offset);
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":
{
var wide = ((ulong)left << 1) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SLshl2AddU32":
{
var wide = ((ulong)left << 2) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SLshl3AddU32":
{
var wide = ((ulong)left << 3) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SLshl4AddU32":
{
var wide = ((ulong)left << 4) + right;
result = (uint)wide;
scalarConditionCode = wide > uint.MaxValue;
break;
}
case "SPackLlB32B16":
result = (left & 0xFFFFu) | (right << 16);
break;
case "SPackLhB32B16":
result = (left & 0xFFFFu) | (right & 0xFFFF0000u);
break;
case "SPackHhB32B16":
result = (left >> 16) | (right & 0xFFFF0000u);
break;
case "SMulHiU32":
result = (uint)(((ulong)left * right) >> 32);
break;
case "SMulHiI32":
result = unchecked((uint)(((long)(int)left * (int)right) >> 32));
break;
default:
error = $"unsupported-scalar-op pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
registers[destination.Value] = result;
return true;
}
private static bool TryExecuteSaveExecScalarAlu(
Gen5ShaderInstruction instruction,
uint[] registers,
ref ulong execMask,
ref bool scalarConditionCode,
out string error)
{
error = string.Empty;
if (instruction.Opcode is not (
"SAndSaveexecB64" or
"SOrSaveexecB64" or
"SXorSaveexecB64" or
"SAndn2SaveexecB64" or
"SOrn2SaveexecB64" or
"SNandSaveexecB64" or
"SNorSaveexecB64" or
"SXnorSaveexecB64" or
"SAndn1SaveexecB64" or
"SOrn1SaveexecB64"))
{
return false;
}
if (instruction.Destinations.Count != 1 ||
instruction.Destinations[0] is not
{
Kind: Gen5OperandKind.ScalarRegister,
Value: < ScalarRegisterCount - 1,
} destination ||
instruction.Sources.Count == 0 ||
!TryEvaluateScalarOperand64(
instruction.Sources[0],
registers,
execMask,
out var source))
{
error = $"scalar-source64 pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
var oldExec = execMask;
var newExec = instruction.Opcode switch
{
"SAndSaveexecB64" => oldExec & source,
"SOrSaveexecB64" => oldExec | source,
"SXorSaveexecB64" => oldExec ^ source,
"SAndn1SaveexecB64" => ~source & oldExec,
"SAndn2SaveexecB64" => source & ~oldExec,
"SOrn1SaveexecB64" => ~source | oldExec,
"SOrn2SaveexecB64" => source | ~oldExec,
"SNandSaveexecB64" => ~(source & oldExec),
"SNorSaveexecB64" => ~(source | oldExec),
_ => ~(oldExec ^ source),
};
WriteScalarPair(registers, destination.Value, oldExec, ref execMask);
execMask = MaskWaveValue(newExec);
WriteScalarPair(registers, 126, execMask, ref execMask);
scalarConditionCode = execMask != 0;
return true;
}
private static bool TryEvaluateScalarOperand64(
Gen5Operand operand,
uint[] registers,
ulong execMask,
out ulong value)
{
if (operand.Kind == Gen5OperandKind.ScalarRegister &&
operand.Value == 126)
{
value = execMask;
return true;
}
if (operand.Kind == Gen5OperandKind.ScalarRegister &&
operand.Value < ScalarRegisterCount - 1)
{
value = registers[operand.Value] | ((ulong)registers[operand.Value + 1] << 32);
return true;
}
if (TryEvaluateScalarOperand(operand, registers, out var low))
{
value = operand.Kind == Gen5OperandKind.EncodedConstant &&
operand.Value is >= 193 and <= 208
? ulong.MaxValue << 32 | low
: low;
return true;
}
value = 0;
return false;
}
private static void WriteScalarPair(
uint[] registers,
uint destination,
ulong value,
ref ulong execMask)
{
if (destination >= ScalarRegisterCount - 1)
{
return;
}
if (destination == 126)
{
value = MaskWaveValue(value);
}
registers[destination] = (uint)value;
registers[destination + 1] = (uint)(value >> 32);
if (destination == 126)
{
execMask = value;
}
}
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,
out bool scalarConditionCode,
out string error)
{
scalarConditionCode = false;
error = string.Empty;
if (instruction.Sources.Count != 2 ||
!TryEvaluateScalarOperand(instruction.Sources[0], registers, out var left) ||
!TryEvaluateScalarOperand(instruction.Sources[1], registers, out var right))
{
error = $"scalar-compare-source pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
if (instruction.Opcode is "SBitcmp0B32" or "SBitcmp1B32")
{
var bit = (int)(right & 31u);
var isSet = ((left >> bit) & 1u) != 0;
scalarConditionCode = instruction.Opcode == "SBitcmp1B32" ? isSet : !isSet;
return true;
}
if (instruction.Opcode is "SBitcmp0B64" or "SBitcmp1B64")
{
if (!TryEvaluateScalarOperand64(instruction.Sources[0], registers, ulong.MaxValue, out var wide))
{
error = $"scalar-bitcmp-source64 pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
var bit = (int)(right & 63u);
var isSet = ((wide >> bit) & 1UL) != 0;
scalarConditionCode = instruction.Opcode == "SBitcmp1B64" ? isSet : !isSet;
return true;
}
scalarConditionCode = instruction.Opcode switch
{
"SCmpEqI32" => (int)left == (int)right,
"SCmpLgI32" => (int)left != (int)right,
"SCmpGtI32" => (int)left > (int)right,
"SCmpGeI32" => (int)left >= (int)right,
"SCmpLtI32" => (int)left < (int)right,
"SCmpLeI32" => (int)left <= (int)right,
"SCmpEqU32" => left == right,
"SCmpLgU32" => left != right,
"SCmpGtU32" => left > right,
"SCmpGeU32" => left >= right,
"SCmpLtU32" => left < right,
"SCmpLeU32" => left <= right,
_ => false,
};
if (!instruction.Opcode.StartsWith("SCmp", StringComparison.Ordinal))
{
error = $"unsupported-scalar-compare pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
return true;
}
private static bool TryExecuteScalarCompareK(
Gen5ShaderInstruction instruction,
uint[] registers,
out bool scalarConditionCode,
out string error)
{
scalarConditionCode = false;
error = string.Empty;
if (instruction.Destinations.Count != 1 ||
instruction.Destinations[0] is not
{
Kind: Gen5OperandKind.ScalarRegister,
Value: < ScalarRegisterCount,
} destination)
{
error = $"scalar-comparek-destination pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
var left = registers[destination.Value];
var right = unchecked((uint)(short)instruction.Sources[0].Value);
scalarConditionCode = instruction.Opcode switch
{
"SCmpkEqI32" => (int)left == (int)right,
"SCmpkLgI32" => (int)left != (int)right,
"SCmpkGtI32" => (int)left > (int)right,
"SCmpkGeI32" => (int)left >= (int)right,
"SCmpkLtI32" => (int)left < (int)right,
"SCmpkLeI32" => (int)left <= (int)right,
"SCmpkEqU32" => left == right,
"SCmpkLgU32" => left != right,
"SCmpkGtU32" => left > right,
"SCmpkGeU32" => left >= right,
"SCmpkLtU32" => left < right,
"SCmpkLeU32" => left <= right,
_ => false,
};
if (!instruction.Opcode.StartsWith("SCmpk", StringComparison.Ordinal))
{
error = $"unsupported-scalar-comparek pc=0x{instruction.Pc:X} op={instruction.Opcode}";
return false;
}
return true;
}
private static bool TryExecuteScalarLoad(
CpuContext ctx,
Gen5ShaderState state,
Gen5ShaderInstruction instruction,
Gen5ScalarMemoryControl control,
uint[] scalarRegisters,
List globalMemoryBindings,
Dictionary<(uint ScalarAddress, ulong BaseAddress), Gen5GlobalMemoryBinding> globalMemoryByAddress,
HashSet runtimeScalarRegisters,
out string error)
{
error = string.Empty;
if (instruction.Sources.Count == 0 ||
instruction.Sources[0] is not
{
Kind: Gen5OperandKind.ScalarRegister,
Value: < ScalarRegisterCount - 1,
} scalarBase)
{
error = $"invalid-scalar-base pc=0x{instruction.Pc:X}";
return false;
}
var isBufferLoad =
instruction.Opcode.StartsWith("SBufferLoad", StringComparison.Ordinal);
BufferDescriptor bufferDescriptor = default;
var hasBufferDescriptor =
isBufferLoad &&
TryDecodeBufferDescriptor(
scalarRegisters,
scalarBase.Value,
strictType: false,
out bufferDescriptor);
var baseAddress = hasBufferDescriptor
? bufferDescriptor.BaseAddress
: scalarRegisters[scalarBase.Value] |
((ulong)scalarRegisters[scalarBase.Value + 1] << 32);
var dynamicOffset = control.DynamicOffsetRegister is { } offsetRegister &&
offsetRegister < ScalarRegisterCount
? scalarRegisters[offsetRegister]
: 0;
var immediateOffset = (ulong)(long)control.ImmediateOffsetBytes;
var byteOffset = unchecked(immediateOffset + dynamicOffset);
var address = unchecked(
baseAddress +
byteOffset) & ~3UL;
var bufferUnbound =
isBufferLoad &&
(!hasBufferDescriptor ||
bufferDescriptor.SizeBytes == 0 ||
(scalarRegisters[scalarBase.Value] == 0 &&
scalarRegisters[scalarBase.Value + 1] == 0 &&
scalarBase.Value + 3 < ScalarRegisterCount &&
scalarRegisters[scalarBase.Value + 2] == 0 &&
scalarRegisters[scalarBase.Value + 3] == 0));
var bufferSize = ulong.MaxValue;
if (isBufferLoad)
{
bufferSize = hasBufferDescriptor ? bufferDescriptor.SizeBytes : ulong.MaxValue;
var key = (scalarBase.Value, bufferDescriptor.BaseAddress);
if (globalMemoryByAddress.TryGetValue(key, out var existingBinding))
{
if (existingBinding.InstructionPcs is List instructionPcs) instructionPcs.Add(instruction.Pc);
}
else
{
TryReadGlobalMemory(ctx, bufferDescriptor.BaseAddress, bufferDescriptor.SizeBytes, out var data);
var binding = new Gen5GlobalMemoryBinding(scalarBase.Value, bufferDescriptor.BaseAddress, new List { instruction.Pc }, data);
globalMemoryByAddress.Add(key, binding);
globalMemoryBindings.Add(binding);
}
}
else if (baseAddress != 0)
{
var key = (scalarBase.Value, baseAddress);
if (globalMemoryByAddress.TryGetValue(key, out var existingBinding))
{
if (existingBinding.InstructionPcs is List instructionPcs)
{
instructionPcs.Add(instruction.Pc);
}
}
else
{
var requiredBytes = Math.Max(
256UL * 1024UL,
checked(
byteOffset +
(ulong)Math.Max(instruction.Destinations.Count, 1) *
sizeof(uint)));
requiredBytes = Math.Min(
(requiredBytes + 4095UL) & ~4095UL,
MaxGlobalMemoryBindingBytes);
if (!TryReadGlobalMemory(
ctx,
baseAddress,
requiredBytes,
out var data))
{
data = [];
}
var binding = new Gen5GlobalMemoryBinding(
scalarBase.Value,
baseAddress,
new List { instruction.Pc },
data);
globalMemoryByAddress.Add(key, binding);
globalMemoryBindings.Add(binding);
}
}
if (!bufferUnbound && address == 0)
{
error = FormatScalarLoadError(
"invalid-load-address",
instruction,
scalarBase.Value,
scalarRegisters,
control,
baseAddress,
dynamicOffset,
address);
return false;
}
for (var index = 0; index < instruction.Destinations.Count; index++)
{
var destination = instruction.Destinations[index];
if (destination.Kind != Gen5OperandKind.ScalarRegister ||
destination.Value >= ScalarRegisterCount)
{
error = FormatScalarLoadError(
"invalid-scalar-destination",
instruction,
scalarBase.Value,
scalarRegisters,
control,
baseAddress,
dynamicOffset,
address);
return false;
}
var componentOffset = unchecked(byteOffset + (ulong)(index * sizeof(uint)));
if (bufferUnbound ||
isBufferLoad &&
(componentOffset >= bufferSize ||
bufferSize - componentOffset < sizeof(uint)))
{
scalarRegisters[destination.Value] = 0;
continue;
}
if (!ctx.TryReadUInt32(
address + (ulong)(index * sizeof(uint)),
out var value) &&
!TryReadUserDataScalarLoad(
state,
instruction,
control,
byteOffset,
index,
out value))
{
if (isBufferLoad)
{
scalarRegisters[destination.Value] = 0;
continue;
}
error = FormatScalarLoadError(
"scalar-load-failed",
instruction,
scalarBase.Value,
scalarRegisters,
control,
baseAddress,
dynamicOffset,
address);
return false;
}
scalarRegisters[destination.Value] = value;
}
return true;
}
private static bool TryDecodeBufferDescriptor(
IReadOnlyList scalarRegisters,
uint scalarBase,
bool strictType,
out BufferDescriptor descriptor)
{
descriptor = default;
if (scalarBase + 3 >= scalarRegisters.Count)
{
return false;
}
var word0 = scalarRegisters[(int)scalarBase];
var word1 = scalarRegisters[(int)scalarBase + 1];
var word2 = scalarRegisters[(int)scalarBase + 2];
var word3 = scalarRegisters[(int)scalarBase + 3];
if (word0 == 0 &&
word1 == 0 &&
word2 == 0 &&
word3 == 0)
{
descriptor = new BufferDescriptor(0, 0, 0, 0, 0, 0);
return true;
}
var type = word3 >> 30;
if (type != 0)
{
if (strictType)
{
return false;
}
descriptor = new BufferDescriptor(0, 0, 0, 0, 0, 0);
return true;
}
var baseAddress = word0 | ((ulong)(word1 & 0xFFFFu) << 32);
var stride = (word1 >> 16) & 0x3FFFu;
var unifiedFormat = (word3 >> 12) & 0x7Fu;
var (dataFormat, numberFormat) =
DecodeGfx10BufferFormat(unifiedFormat);
var sizeBytes = stride == 0
? word2
: (ulong)stride * word2;
descriptor = new BufferDescriptor(baseAddress, stride, word2, sizeBytes, numberFormat, dataFormat);
return true;
}
private static (uint DataFormat, uint NumberFormat)
DecodeGfx10BufferFormat(uint format) =>
format switch
{
0 => (0, 0),
>= 1 and <= 6 => (1, format - 1),
>= 7 and <= 13 => (2, DecodeUnifiedNumber(format - 7, 7)),
>= 14 and <= 19 => (3, format - 14),
>= 20 and <= 22 => (4, DecodeIntegerOrFloatNumber(format - 20)),
>= 23 and <= 29 => (5, DecodeUnifiedNumber(format - 23, 7)),
>= 30 and <= 36 => (6, DecodeUnifiedNumber(format - 30, 7)),
>= 37 and <= 43 => (7, DecodeUnifiedNumber(format - 37, 7)),
>= 44 and <= 49 => (8, format - 44),
>= 50 and <= 55 => (9, format - 50),
>= 56 and <= 61 => (10, format - 56),
>= 62 and <= 64 => (11, DecodeIntegerOrFloatNumber(format - 62)),
>= 65 and <= 71 => (12, DecodeUnifiedNumber(format - 65, 7)),
>= 72 and <= 74 => (13, DecodeIntegerOrFloatNumber(format - 72)),
>= 75 and <= 77 => (14, DecodeIntegerOrFloatNumber(format - 75)),
128 => (1, 9),
129 => (3, 9),
130 => (10, 9),
132 => (34, 7),
133 => (16, 0),
134 => (17, 0),
135 => (18, 0),
136 => (19, 0),
140 => (4, 7),
_ => (0, 0),
};
private static uint DecodeUnifiedNumber(uint offset, uint formatCount) =>
offset == formatCount - 1 ? 7u : offset;
private static uint DecodeIntegerOrFloatNumber(uint offset) =>
offset switch
{
0 => 4,
1 => 5,
_ => 7,
};
private static bool TryReadUserDataScalarLoad(
Gen5ShaderState state,
Gen5ShaderInstruction instruction,
Gen5ScalarMemoryControl control,
ulong byteOffset,
int componentIndex,
out uint value)
{
value = 0;
if (!instruction.Opcode.StartsWith("SLoadDword", StringComparison.Ordinal) ||
state.Metadata is not { } metadata ||
(byteOffset & 3) != 0)
{
return false;
}
var baseDwordOffset = byteOffset >> 2;
if (baseDwordOffset > int.MaxValue)
{
return false;
}
var dwordOffset = (long)baseDwordOffset + componentIndex;
if (dwordOffset < 0 ||
dwordOffset >= state.UserData.Count ||
!IsShaderUserDataResourceOffset(metadata, (uint)dwordOffset))
{
return false;
}
value = state.UserData[(int)dwordOffset];
return true;
}
private static bool IsShaderUserDataResourceOffset(
Gen5ShaderMetadata metadata,
uint dwordOffset)
{
if (dwordOffset < metadata.ShaderResourceTableSizeDwords)
{
return true;
}
foreach (var resource in metadata.Resources)
{
var dwordCount = resource.Kind switch
{
Gen5ShaderResourceKind.ReadOnlyTexture or
Gen5ShaderResourceKind.ReadWriteTexture => 8u,
Gen5ShaderResourceKind.Sampler or
Gen5ShaderResourceKind.ConstantBuffer => 4u,
_ => 1u,
};
if (dwordOffset >= resource.OffsetDwords &&
dwordOffset < resource.OffsetDwords + dwordCount)
{
return true;
}
}
return metadata.DirectResources.Values.Any(offset => offset == dwordOffset);
}
private static string FormatScalarLoadError(
string reason,
Gen5ShaderInstruction instruction,
uint scalarBase,
uint[] scalarRegisters,
Gen5ScalarMemoryControl control,
ulong baseAddress,
uint dynamicOffset,
ulong address)
{
var high = scalarBase + 1 < scalarRegisters.Length
? scalarRegisters[scalarBase + 1]
: 0;
var dynamic = control.DynamicOffsetRegister is { } register
? $" dyn=s{register}=0x{dynamicOffset:X8}"
: " dyn=none";
var descriptor = string.Join(
':',
Enumerable.Range(0, 4).Select(index =>
scalarBase + index < scalarRegisters.Length
? $"{scalarRegisters[scalarBase + index]:X8}"
: "????????"));
var words = string.Join(',', instruction.Words.Select(word => $"{word:X8}"));
return
$"{reason} pc=0x{instruction.Pc:X} op={instruction.Opcode} " +
$"words=[{words}] base=s{scalarBase}[0x{scalarRegisters[scalarBase]:X8}:0x{high:X8}] " +
$"desc=[{descriptor}] " +
$"base_addr=0x{baseAddress:X16} imm={control.ImmediateOffsetBytes}" +
$"{dynamic} address=0x{address:X16}";
}
private static bool TryEvaluateScalarOperand(
Gen5Operand operand,
uint[] scalarRegisters,
out uint value)
{
if (operand.Kind == Gen5OperandKind.ScalarRegister &&
operand.Value < ScalarRegisterCount)
{
value = scalarRegisters[operand.Value];
return true;
}
if (operand.Kind == Gen5OperandKind.LiteralConstant)
{
value = operand.Value;
return true;
}
if (operand.Kind == Gen5OperandKind.EncodedConstant)
{
return TryDecodeInlineConstant(operand.Value, out value);
}
value = 0;
return false;
}
private static bool TryDecodeInlineConstant(uint encoded, out uint value)
{
if (encoded == 125)
{
value = 0;
return true;
}
if (encoded is >= 128 and <= 192)
{
value = encoded - 128;
return true;
}
if (encoded is >= 193 and <= 208)
{
value = unchecked((uint)-(int)(encoded - 192));
return true;
}
var floatingPoint = encoded switch
{
240 => 0.5f,
241 => -0.5f,
242 => 1.0f,
243 => -1.0f,
244 => 2.0f,
245 => -2.0f,
246 => 4.0f,
247 => -4.0f,
248 => 1.0f / (2.0f * MathF.PI),
_ => float.NaN,
};
if (float.IsNaN(floatingPoint))
{
value = 0;
return false;
}
value = BitConverter.SingleToUInt32Bits(floatingPoint);
return true;
}
private static uint ReverseBits(uint value)
{
value = (value >> 1 & 0x55555555u) | ((value & 0x55555555u) << 1);
value = (value >> 2 & 0x33333333u) | ((value & 0x33333333u) << 2);
value = (value >> 4 & 0x0F0F0F0Fu) | ((value & 0x0F0F0F0Fu) << 4);
value = (value >> 8 & 0x00FF00FFu) | ((value & 0x00FF00FFu) << 8);
return value >> 16 | value << 16;
}
private static bool TryCopyRegisters(
uint[] registers,
uint start,
int count,
out IReadOnlyList values)
{
values = [];
if (start > (uint)(registers.Length - count))
{
return false;
}
var copy = new uint[count];
Array.Copy(registers, (int)start, copy, 0, count);
values = copy;
return true;
}
private static bool UsesSampler(string opcode) =>
opcode.StartsWith("ImageSample", StringComparison.Ordinal) ||
opcode.StartsWith("ImageGather", StringComparison.Ordinal);
}