[AGC/Vulkan] Extend PS5 runtime and rendering compatibility (#216)

* [Core] Add POSIX native execution and PS5 SELF support

Extend the native backend, guest TLS, fixed-address memory, and loader paths needed by PS5 titles on Windows, Linux, and macOS. Keep workstation GC so high-core-count hosts do not reserve over fixed guest image bases.

* [HLE] Expand PS5 service and media compatibility

Add the kernel, threading, save-data, networking, audio, video-codec, font, dialog, and service exports required by newer PS5 software. Preserve every SysAbi NID currently registered by main while adding the compatibility surface used by ASTRO BOT.

* [AGC/Vulkan] Extend Gen5 shader and presentation support

Expand PM4 handling, Gen5 shader translation, MRT and packed export support, guest image tracking, depth initialization, texture aliasing, and Vulkan presentation. Add the performance overlay and address-filtered diagnostics used to validate ASTRO BOT with original shaders.

* [Core] Align static TLS reservation across hosts

* [Pad] Align primary user ID with UserService

* [Gpu] Preserve runtime scalar buffers across renderer seam

* [AGC] Restore omitted command helper exports

* [Vulkan] Reuse primary views for promoted MRT targets

* [Vulkan] Preserve scratch storage bindings in compute dispatches
This commit is contained in:
Miguel Cruz
2026-07-15 19:02:34 -04:00
committed by GitHub
parent ad5a7d3799
commit 864cbb0fa0
85 changed files with 36299 additions and 9176 deletions
+83 -5
View File
@@ -175,7 +175,9 @@ public sealed record Gen5ImageControl(
uint Dimension,
bool IsArray,
bool Glc,
bool Slc) : Gen5InstructionControl
bool Slc,
bool A16,
bool D16) : Gen5InstructionControl
{
public uint GetAddressRegister(int component) =>
component < AddressRegisters.Count
@@ -219,16 +221,21 @@ public sealed record Gen5Vop3Control(
uint NegateMask,
uint OutputModifier,
bool Clamp,
uint OperandSelect,
uint? ScalarDestination) : Gen5InstructionControl;
public sealed record Gen5SdwaControl(
uint DestinationSelect,
uint DestinationUnused,
uint Source0Select,
uint Source1Select,
bool Source0SignExtend,
bool Source1SignExtend,
uint AbsoluteMask,
uint NegateMask,
uint OutputModifier,
bool Clamp) : Gen5InstructionControl;
bool Clamp,
uint? ScalarDestination) : Gen5InstructionControl;
public sealed record Gen5DppControl(
uint Control,
@@ -239,6 +246,10 @@ public sealed record Gen5DppControl(
uint BankMask,
uint RowMask) : Gen5InstructionControl;
public sealed record Gen5Dpp8Control(
uint LaneSelectors,
bool FetchInactive) : Gen5InstructionControl;
public sealed record Gen5ScalarMemoryControl(
uint DestinationCount,
int ImmediateOffsetBytes,
@@ -257,11 +268,27 @@ public sealed record Gen5ImageBinding(
IReadOnlyList<uint> SamplerDescriptor,
uint? MipLevel);
// Data arrays may be rented from ArrayPool (oversized): always slice with
// DataLength, never Data.Length. Ownership transfers to the presenter, which
// returns pooled arrays after uploading them into host-visible buffers.
public sealed record Gen5GlobalMemoryBinding(
uint ScalarAddress,
ulong BaseAddress,
IReadOnlyList<uint> InstructionPcs,
byte[] Data);
byte[] Data,
int DataLength,
bool DataPooled)
{
public bool Writable { get; set; }
// Writable describes shader access and is also used to decide whether a
// compute dispatch has observable work. A statically reachable resource
// can nevertheless be unbound for the current scalar path; the evaluator
// supplies zero-filled storage for Vulkan in that case. Such synthetic
// storage must remain shader-writable, but must never be copied to the
// descriptor's unmapped guest address.
public bool WriteBackToGuest { get; set; } = true;
}
public sealed record Gen5VertexInputBinding(
uint Pc,
@@ -272,12 +299,13 @@ public sealed record Gen5VertexInputBinding(
ulong BaseAddress,
uint Stride,
uint OffsetBytes,
byte[] Data);
byte[] Data,
int DataLength,
bool DataPooled);
public sealed record Gen5ShaderEvaluation(
IReadOnlyList<uint> InitialScalarRegisters,
IReadOnlyList<uint> ScalarRegisters,
IReadOnlyDictionary<uint, IReadOnlyList<uint>> ScalarRegistersByPc,
IReadOnlyList<Gen5ImageBinding> ImageBindings,
IReadOnlyList<Gen5GlobalMemoryBinding> GlobalMemoryBindings,
Gen5ComputeSystemRegisters? ComputeSystemRegisters = null,
@@ -297,8 +325,58 @@ public sealed record Gen5ShaderProgram(
ulong Address,
IReadOnlyList<Gen5ShaderInstruction> Instructions)
{
private const int ScalarRegisterCount = 256;
private IReadOnlySet<uint>? _runtimeScalarRegisters;
public IEnumerable<Gen5ImageControl> ImageResources =>
Instructions
.Select(instruction => instruction.Control)
.OfType<Gen5ImageControl>();
/// <summary>
/// The set of scalar registers the program reads or writes as runtime
/// values. It depends only on the (cached) decoded program, so it is
/// computed once and shared read-only across every draw that uses this
/// shader — the evaluator previously rebuilt this HashSet by scanning
/// every instruction on every draw, one of the largest per-draw
/// allocation and CPU sources.
/// </summary>
public IReadOnlySet<uint> RuntimeScalarRegisters =>
_runtimeScalarRegisters ??= ComputeRuntimeScalarRegisters();
private IReadOnlySet<uint> ComputeRuntimeScalarRegisters()
{
var registers = new HashSet<uint>();
foreach (var instruction in Instructions)
{
foreach (var operand in instruction.Sources)
{
if (operand.Kind == Gen5OperandKind.ScalarRegister &&
operand.Value < ScalarRegisterCount)
{
registers.Add(operand.Value);
}
}
foreach (var operand in 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;
}
}
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using System.Buffers.Binary;
namespace SharpEmu.ShaderCompiler;
@@ -17,9 +18,9 @@ public static class Gen5ShaderMetadataReader
out Gen5ShaderMetadata metadata)
{
metadata = default!;
if (!ctx.TryReadUInt64(shaderHeaderAddress + ShaderUserDataOffset, out var userDataAddress) ||
if (!TryReadUInt64(ctx, shaderHeaderAddress + ShaderUserDataOffset, out var userDataAddress) ||
userDataAddress == 0 ||
!ctx.TryReadUInt64(userDataAddress, out var directResourceOffsetsAddress))
!TryReadUInt64(ctx, userDataAddress, out var directResourceOffsetsAddress))
{
return false;
}
@@ -27,7 +28,8 @@ public static class Gen5ShaderMetadataReader
var resourceOffsets = new ulong[ResourceClassCount];
for (var resourceClass = 0; resourceClass < ResourceClassCount; resourceClass++)
{
if (!ctx.TryReadUInt64(
if (!TryReadUInt64(
ctx,
userDataAddress + 0x08 + (ulong)(resourceClass * sizeof(ulong)),
out resourceOffsets[resourceClass]))
{
@@ -35,9 +37,9 @@ public static class Gen5ShaderMetadataReader
}
}
if (!ctx.TryReadUInt16(userDataAddress + 0x28, out var extendedUserDataSize) ||
!ctx.TryReadUInt16(userDataAddress + 0x2A, out var shaderResourceTableSize) ||
!ctx.TryReadUInt16(userDataAddress + 0x2C, out var directResourceCount) ||
if (!TryReadUInt16(ctx, userDataAddress + 0x28, out var extendedUserDataSize) ||
!TryReadUInt16(ctx, userDataAddress + 0x2A, out var shaderResourceTableSize) ||
!TryReadUInt16(ctx, userDataAddress + 0x2C, out var directResourceCount) ||
directResourceCount > MaxMetadataEntries)
{
return false;
@@ -46,7 +48,8 @@ public static class Gen5ShaderMetadataReader
var resourceCounts = new ushort[ResourceClassCount];
for (var resourceClass = 0; resourceClass < ResourceClassCount; resourceClass++)
{
if (!ctx.TryReadUInt16(
if (!TryReadUInt16(
ctx,
userDataAddress + 0x2E + (ulong)(resourceClass * sizeof(ushort)),
out resourceCounts[resourceClass]) ||
resourceCounts[resourceClass] > MaxMetadataEntries)
@@ -65,7 +68,7 @@ public static class Gen5ShaderMetadataReader
for (uint type = 0; type < directResourceCount; type++)
{
if (!ctx.TryReadUInt16(directResourceOffsetsAddress + type * sizeof(ushort), out var offset))
if (!TryReadUInt16(ctx, directResourceOffsetsAddress + type * sizeof(ushort), out var offset))
{
return false;
}
@@ -93,7 +96,8 @@ public static class Gen5ShaderMetadataReader
for (uint slot = 0; slot < count; slot++)
{
if (!ctx.TryReadUInt16(
if (!TryReadUInt16(
ctx,
resourceOffsets[resourceClass] + slot * sizeof(ushort),
out var sharp))
{
@@ -121,4 +125,30 @@ public static class Gen5ShaderMetadataReader
resources);
return true;
}
private static bool TryReadUInt16(CpuContext ctx, ulong address, out ushort value)
{
Span<byte> bytes = stackalloc byte[sizeof(ushort)];
if (!ctx.Memory.TryRead(address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt16LittleEndian(bytes);
return true;
}
private static bool TryReadUInt64(CpuContext ctx, ulong address, out ulong value)
{
Span<byte> bytes = stackalloc byte[sizeof(ulong)];
if (!ctx.Memory.TryRead(address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt64LittleEndian(bytes);
return true;
}
}
File diff suppressed because it is too large Load Diff
@@ -3,6 +3,7 @@
using SharpEmu.HLE;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
@@ -10,9 +11,83 @@ namespace SharpEmu.ShaderCompiler;
public static class Gen5ShaderTranslator
{
private static int _dppVectorsValidated;
/// <summary>
/// Bitmask (256 bits) of scalar registers whose values the program can
/// observe: scalar source operands (widened for 64-bit pairs), the
/// descriptor/sampler/address ranges named by instruction controls, and
/// the implicit state registers. Deterministic per instruction stream, so
/// the SPIR-V that loads exactly these registers from the per-draw
/// initial-state buffer is byte-stable across draws.
/// </summary>
public static ulong[] ComputeConsumedScalarMask(Gen5ShaderProgram program)
{
var mask = new ulong[4];
AddConsumedScalar(mask, 106, 2);
AddConsumedScalar(mask, 124, 2);
AddConsumedScalar(mask, 126, 2);
foreach (var instruction in program.Instructions)
{
foreach (var source in instruction.Sources)
{
if (source.Kind == Gen5OperandKind.ScalarRegister)
{
AddConsumedScalar(mask, source.Value, 2);
}
}
// Scalar memory bases can be a 4-dword buffer descriptor.
if (instruction.Encoding is Gen5ShaderEncoding.Smem or Gen5ShaderEncoding.Smrd &&
instruction.Sources.Count > 0 &&
instruction.Sources[0].Kind == Gen5OperandKind.ScalarRegister)
{
AddConsumedScalar(mask, instruction.Sources[0].Value, 4);
}
switch (instruction.Control)
{
case Gen5ImageControl image:
AddConsumedScalar(mask, image.ScalarResource, 8);
AddConsumedScalar(mask, image.ScalarSampler, 4);
break;
case Gen5ScalarMemoryControl { DynamicOffsetRegister: { } offsetRegister }:
AddConsumedScalar(mask, offsetRegister, 2);
break;
case Gen5GlobalMemoryControl global:
AddConsumedScalar(mask, global.ScalarAddress, 2);
break;
case Gen5BufferMemoryControl buffer:
AddConsumedScalar(mask, buffer.ScalarResource, 4);
break;
}
}
return mask;
}
private static void AddConsumedScalar(ulong[] mask, uint register, uint count)
{
for (uint index = 0; index < count; index++)
{
var target = register + index;
if (target < 256)
{
mask[target >> 6] |= 1UL << (int)(target & 63);
}
}
}
public static bool IsScalarConsumed(ulong[] mask, uint register) =>
register < 256 && (mask[register >> 6] & (1UL << (int)(register & 63))) != 0;
private const int MaxInstructions = 4096;
private const int MinimumUserDataDwords = 16;
private const int MaximumUserDataDwords = 256;
private const uint PsUserDataRegister = 0x0C;
private const uint VsUserDataRegister = 0x4C;
private const uint GsUserDataRegister = 0x8C;
private const uint EsUserDataRegister = 0xCC;
private const uint ComputeUserDataRegister = 0x240;
private const uint ComputePgmRsrc2Register = 0x213;
private const int MaximumHardwareUserSgprs = 64;
private static readonly ConditionalWeakTable<object, ShaderDecodeCache> _decodeCaches = new();
private sealed class ShaderDecodeCache
@@ -124,6 +199,7 @@ public static class Gen5ShaderTranslator
Gen5ComputeSystemRegisters? computeSystemRegisters = null,
uint userDataScalarRegisterBase = 0)
{
ValidateUserSgprCountDecoding();
state = default!;
error = string.Empty;
var cache = _decodeCaches.GetValue(ctx.Memory, static _ => new ShaderDecodeCache());
@@ -172,7 +248,20 @@ public static class Gen5ShaderTranslator
}
}
var userData = new uint[GetUserDataDwordCount(metadata)];
if (!TryGetUserSgprCount(
shaderRegisters,
userDataBaseRegister,
out var userSgprCount,
out var rsrc2Register,
out _))
{
error =
$"missing-user-sgpr-count ud_reg=0x{userDataBaseRegister:X} " +
$"rsrc2_reg=0x{rsrc2Register:X}";
return false;
}
var userData = new uint[userSgprCount];
for (uint index = 0; index < userData.Length; index++)
{
shaderRegisters.TryGetValue(userDataBaseRegister + index, out userData[index]);
@@ -187,35 +276,72 @@ public static class Gen5ShaderTranslator
return true;
}
private static int GetUserDataDwordCount(Gen5ShaderMetadata? metadata)
private static bool TryGetUserSgprCount(
IReadOnlyDictionary<uint, uint> shaderRegisters,
uint userDataBaseRegister,
out int count,
out uint rsrc2Register,
out uint rsrc2)
{
var count = MinimumUserDataDwords;
if (metadata is null)
rsrc2Register = userDataBaseRegister == ComputeUserDataRegister
? ComputePgmRsrc2Register
: userDataBaseRegister - 1;
if (!shaderRegisters.TryGetValue(rsrc2Register, out rsrc2))
{
count = 0;
return false;
}
count = checked((int)((rsrc2 >> 1) & 0x1Fu));
// GFX10 PS/VS/GS expose a sixth USER_SGPR bit. ES and compute do not.
// AGC's logical user-data layout (including its SRT pointer and back
// user data) describes memory reached through these SGPRs; it does not
// increase the hardware register window.
var hasUserSgprMsb = userDataBaseRegister is
PsUserDataRegister or VsUserDataRegister or GsUserDataRegister;
if (hasUserSgprMsb &&
(rsrc2 & (1u << 27)) != 0)
{
count |= 0x20;
}
if (userDataBaseRegister is not (PsUserDataRegister or
VsUserDataRegister or
GsUserDataRegister or
EsUserDataRegister or
ComputeUserDataRegister) ||
count > MaximumHardwareUserSgprs)
{
count = 0;
return false;
}
return true;
}
[Conditional("DEBUG")]
private static void ValidateUserSgprCountDecoding()
{
static int Decode(uint baseRegister, uint rsrc2)
{
var registers = new Dictionary<uint, uint>
{
[baseRegister == ComputeUserDataRegister
? ComputePgmRsrc2Register
: baseRegister - 1] = rsrc2,
};
var decoded =
TryGetUserSgprCount(registers, baseRegister, out var count, out _, out _);
Debug.Assert(decoded);
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;
Debug.Assert(Decode(PsUserDataRegister, 2u << 1) == 2);
Debug.Assert(Decode(PsUserDataRegister, (3u << 1) | (1u << 27)) == 35);
Debug.Assert(Decode(VsUserDataRegister, (1u << 1) | (1u << 27)) == 33);
Debug.Assert(Decode(GsUserDataRegister, (4u << 1) | (1u << 27)) == 36);
Debug.Assert(Decode(EsUserDataRegister, (7u << 1) | (1u << 27)) == 7);
Debug.Assert(Decode(ComputeUserDataRegister, (11u << 1) | (1u << 27)) == 11);
}
public static string DescribeState(Gen5ShaderState state)
@@ -229,7 +355,8 @@ public static class Gen5ShaderTranslator
if (state.Metadata is not { } metadata)
{
return
$"ud_base=s{state.UserDataScalarRegisterBase} ud[{userData}]" +
$"ud_base=s{state.UserDataScalarRegisterBase} hw_ud={state.UserData.Count} " +
$"ud[{userData}]" +
$"{systemRegisters} metadata=missing";
}
@@ -242,7 +369,8 @@ public static class Gen5ShaderTranslator
$"{resource.Kind}[{resource.Slot}]@{resource.OffsetDwords}" +
(resource.SizeFlag ? "+" : string.Empty)));
return
$"ud_base=s{state.UserDataScalarRegisterBase} ud[{userData}]" +
$"ud_base=s{state.UserDataScalarRegisterBase} hw_ud={state.UserData.Count} " +
$"ud[{userData}]" +
$"{systemRegisters} metadata[eud={metadata.ExtendedUserDataSizeDwords}," +
$"srt={metadata.ShaderResourceTableSizeDwords},direct={direct},resources={resources}]";
}
@@ -283,6 +411,7 @@ public static class Gen5ShaderTranslator
out Gen5ShaderProgram program,
out string error)
{
ValidateDppControlVectors();
program = new Gen5ShaderProgram(address, []);
error = string.Empty;
if (address == 0)
@@ -295,7 +424,7 @@ public static class Gen5ShaderTranslator
var instructionCount = 0;
for (uint pc = 0; instructionCount < MaxInstructions;)
{
if (!ctx.TryReadUInt32(address + pc, out var word))
if (!TryReadUInt32(ctx, address + pc, out var word))
{
error = $"read-failed pc=0x{pc:X}";
return false;
@@ -318,7 +447,7 @@ public static class Gen5ShaderTranslator
words[0] = word;
for (uint wordIndex = 1; wordIndex < sizeDwords; wordIndex++)
{
if (!ctx.TryReadUInt32(address + pc + wordIndex * sizeof(uint), out words[wordIndex]))
if (!TryReadUInt32(ctx, address + pc + wordIndex * sizeof(uint), out words[wordIndex]))
{
error = $"read-failed pc=0x{pc + wordIndex * sizeof(uint):X}";
return false;
@@ -340,6 +469,52 @@ public static class Gen5ShaderTranslator
return false;
}
[Conditional("DEBUG")]
private static void ValidateDppControlVectors()
{
if (System.Threading.Interlocked.Exchange(ref _dppVectorsValidated, 1) != 0)
{
return;
}
static (uint Lane, bool InRange) Resolve(uint control, uint lane)
{
var rowBase = lane & ~15u;
var rowLane = lane & 15u;
return control switch
{
>= 0x101 and <= 0x10F => (
rowBase + ((rowLane + (control & 15)) & 15),
rowLane + (control & 15) < 16),
>= 0x111 and <= 0x11F => (
rowBase + ((rowLane - (control & 15)) & 15),
rowLane >= (control & 15)),
>= 0x121 and <= 0x12F => (
rowBase + ((rowLane - (control & 15)) & 15),
true),
0x140 => (rowBase + 15 - rowLane, true),
0x141 => ((lane & ~7u) + 7 - (lane & 7), true),
>= 0x150 and <= 0x15F => (rowBase + (control & 15), true),
>= 0x160 and <= 0x16F => (rowBase + (rowLane ^ (control & 15)), true),
_ => (lane, false),
};
}
Debug.Assert(Resolve(0x101, 0) == (1u, true));
Debug.Assert(Resolve(0x101, 15) == (0u, false));
Debug.Assert(Resolve(0x112, 1) == (15u, false));
Debug.Assert(Resolve(0x123, 0) == (13u, true));
Debug.Assert(Resolve(0x140, 18) == (29u, true));
Debug.Assert(Resolve(0x141, 9) == (14u, true));
Debug.Assert(Resolve(0x153, 20) == (19u, true));
Debug.Assert(Resolve(0x163, 22) == (21u, true));
const uint dpp8 =
(7u << 0) | (6u << 3) | (5u << 6) | (4u << 9) |
(3u << 12) | (2u << 15) | (1u << 18) | (0u << 21);
Debug.Assert(((dpp8 >> (0 * 3)) & 7) == 7);
Debug.Assert(((dpp8 >> (7 * 3)) & 7) == 0);
}
private static bool TryDecodeInstruction(
CpuContext ctx,
ulong baseAddress,
@@ -398,7 +573,7 @@ public static class Gen5ShaderTranslator
case 0x34:
case 0x35:
encoding = Gen5ShaderEncoding.Vop3;
if (!ctx.TryReadUInt32(baseAddress + pc + sizeof(uint), out var vop3Extra))
if (!TryReadUInt32(ctx, baseAddress + pc + sizeof(uint), out var vop3Extra))
{
error = $"vop3-extra-read-failed pc=0x{pc:X}";
return false;
@@ -419,7 +594,7 @@ public static class Gen5ShaderTranslator
return DecodeFlat(word, out name, out sizeDwords, out error);
case 0x38:
encoding = Gen5ShaderEncoding.Mubuf;
if (!ctx.TryReadUInt32(baseAddress + pc + sizeof(uint), out var mubufExtra))
if (!TryReadUInt32(ctx, baseAddress + pc + sizeof(uint), out var mubufExtra))
{
error = $"mubuf-extra-read-failed pc=0x{pc:X}";
return false;
@@ -428,7 +603,7 @@ public static class Gen5ShaderTranslator
return DecodeMubuf(word, mubufExtra, out name, out sizeDwords, out error);
case 0x3A:
encoding = Gen5ShaderEncoding.Mtbuf;
if (!ctx.TryReadUInt32(baseAddress + pc + sizeof(uint), out var mtbufExtra))
if (!TryReadUInt32(ctx, baseAddress + pc + sizeof(uint), out var mtbufExtra))
{
error = $"mtbuf-extra-read-failed pc=0x{pc:X}";
return false;
@@ -455,6 +630,26 @@ public static class Gen5ShaderTranslator
}
}
// Kept beside the production decoder so offline compatibility tools use
// precisely the same opcode tables and instruction-width rules as runtime
// shader translation.
internal static bool TryDecodeInstructionForPreflight(
CpuContext ctx,
uint pc,
uint word,
out string name,
out uint sizeDwords,
out string error) =>
TryDecodeInstruction(
ctx,
0,
pc,
word,
out _,
out name,
out sizeDwords,
out error);
private static bool DecodeSop(uint word, out string name, out uint sizeDwords, out string error)
{
var opcode = (word >> 23) & 0x7F;
@@ -498,6 +693,16 @@ public static class Gen5ShaderTranslator
0x2B => "SXnorSaveexecB64",
0x37 => "SAndn1SaveexecB64",
0x38 => "SOrn1SaveexecB64",
0x3C => "SAndSaveexecB32",
0x3D => "SOrSaveexecB32",
0x3E => "SXorSaveexecB32",
0x3F => "SAndn2SaveexecB32",
0x40 => "SOrn2SaveexecB32",
0x41 => "SNandSaveexecB32",
0x42 => "SNorSaveexecB32",
0x43 => "SXnorSaveexecB32",
0x44 => "SAndn1SaveexecB32",
0x45 => "SOrn1SaveexecB32",
_ => string.Empty,
};
@@ -662,7 +867,7 @@ public static class Gen5ShaderTranslator
{
var opcode = (word >> 9) & 0xFF;
var src0 = word & 0x1FF;
sizeDwords = src0 is 0xF9 or 0xFA or 0xFF ? 2u : 1u;
sizeDwords = src0 is 0xE9 or 0xEA or 0xF9 or 0xFA or 0xFF ? 2u : 1u;
error = string.Empty;
name = opcode switch
{
@@ -721,7 +926,8 @@ public static class Gen5ShaderTranslator
}
var src0 = word & 0x1FF;
sizeDwords = opcode is 0x20 or 0x21 or 0x2C or 0x2D || src0 is 0xF9 or 0xFA or 0xFF ? 2u : 1u;
sizeDwords = opcode is 0x20 or 0x21 or 0x2C or 0x2D ||
src0 is 0xE9 or 0xEA or 0xF9 or 0xFA or 0xFF ? 2u : 1u;
error = string.Empty;
name = opcode switch
{
@@ -748,6 +954,7 @@ public static class Gen5ShaderTranslator
0x1B => "VAndB32",
0x1C => "VOrB32",
0x1D => "VXorB32",
0x1E => "VXnorB32",
0x1F => "VMacF32",
0x20 => "VMadMkF32",
0x21 => "VMadAkF32",
@@ -761,8 +968,8 @@ public static class Gen5ShaderTranslator
0x29 => "VSubbU32",
0x2A => "VSubbrevU32",
0x2B => "VFmacF32",
0x2C => "VFmamkF32",
0x2D => "VFmaakF32",
0x2C => "VFmaMkF32",
0x2D => "VFmaAkF32",
0x2F => "VCvtPkrtzF16F32",
0x30 => "VCvtPkU16U32",
0x31 => "VCvtPkI16I32",
@@ -776,7 +983,7 @@ public static class Gen5ShaderTranslator
{
var opcode = (word >> 17) & 0xFF;
var src0 = word & 0x1FF;
sizeDwords = src0 is 0xF9 or 0xFA or 0xFF ? 2u : 1u;
sizeDwords = src0 is 0xE9 or 0xEA or 0xF9 or 0xFA or 0xFF ? 2u : 1u;
error = string.Empty;
name = opcode switch
{
@@ -868,9 +1075,11 @@ public static class Gen5ShaderTranslator
name = isVop3B
? opcode switch
{
0x128 => "VAddCoCiU32",
0x30F => "VAddCoU32",
0x310 => "VSubCoU32",
0x319 => "VSubrevCoU32",
0x176 => "VMadU64U32",
_ => $"Vop3bRaw{opcode:X3}",
}
: opcode switch
@@ -914,6 +1123,12 @@ public static class Gen5ShaderTranslator
0x360 => "VReadlaneB32",
0x361 => "VWritelaneB32",
0x362 => "VLdexpF32",
0x363 => "VBfmB32",
0x364 => "VBcntU32B32",
0x365 => "VMbcntLoU32B32",
0x366 => "VMbcntHiU32B32",
0x368 => "VCvtPknormI16F32",
0x369 => "VCvtPknormU16F32",
0x373 => "VMadU32U16",
0x346 => "VLshlAddU32",
0x347 => "VAddLshlU32",
@@ -930,7 +1145,7 @@ public static class Gen5ShaderTranslator
}
private static bool IsVop3BOpcode(uint opcode) =>
opcode is 0x16D or 0x16E or 0x176 or 0x177 or 0x30F or 0x310 or 0x319;
opcode is 0x128 or 0x16D or 0x16E or 0x176 or 0x177 or 0x30F or 0x310 or 0x319;
private static bool DecodeRaw2(
uint word,
@@ -956,6 +1171,7 @@ public static class Gen5ShaderTranslator
error = string.Empty;
name = opcode switch
{
0x00 => "DsAddU32",
0x0D => "DsWriteB32",
0x0E => "DsWrite2B32",
0x0F => "DsWrite2St64B32",
@@ -964,6 +1180,10 @@ public static class Gen5ShaderTranslator
0x37 => "DsRead2B32",
0x38 => "DsRead2St64B32",
0x4D => "DsWriteB64",
0xDE => "DsWriteB96",
0xDF => "DsWriteB128",
0xFE => "DsReadB96",
0xFF => "DsReadB128",
_ => string.Empty,
};
@@ -1026,15 +1246,34 @@ public static class Gen5ShaderTranslator
0x01 => "BufferLoadFormatXy",
0x02 => "BufferLoadFormatXyz",
0x03 => "BufferLoadFormatXyzw",
0x04 => "BufferStoreFormatX",
0x05 => "BufferStoreFormatXy",
0x06 => "BufferStoreFormatXyz",
0x07 => "BufferStoreFormatXyzw",
0x08 => "BufferLoadUbyte",
0x09 => "BufferLoadSbyte",
0x0A => "BufferLoadUshort",
0x0B => "BufferLoadSshort",
0x0C => "BufferLoadDword",
0x0D => "BufferLoadDwordx2",
0x0E => "BufferLoadDwordx4",
0x0F => "BufferLoadDwordx3",
0x18 => "BufferStoreByte",
0x19 => "BufferStoreByteD16Hi",
0x1A => "BufferStoreShort",
0x1B => "BufferStoreShortD16Hi",
0x1C => "BufferStoreDword",
0x1D => "BufferStoreDwordx2",
0x1E => "BufferStoreDwordx4",
0x1F => "BufferStoreDwordx3",
0x20 => "BufferLoadUbyteD16",
0x21 => "BufferLoadUbyteD16Hi",
0x22 => "BufferLoadSbyteD16",
0x23 => "BufferLoadSbyteD16Hi",
0x24 => "BufferLoadShortD16",
0x25 => "BufferLoadShortD16Hi",
0x32 => "BufferAtomicAdd",
0x38 => "BufferAtomicUMax",
_ => $"MubufRaw{opcode:X2}",
};
sizeDwords = (extra >> 24) == 0xFF ? 3u : 2u;
@@ -1055,10 +1294,30 @@ public static class Gen5ShaderTranslator
name = segment == 0x2
? opcode switch
{
0x08 => "GlobalLoadUbyte",
0x09 => "GlobalLoadSbyte",
0x0A => "GlobalLoadUshort",
0x0B => "GlobalLoadSshort",
0x0C => "GlobalLoadDword",
0x0D => "GlobalLoadDwordx2",
0x0E => "GlobalLoadDwordx4",
0x0F => "GlobalLoadDwordx3",
0x18 => "GlobalStoreByte",
0x19 => "GlobalStoreByteD16Hi",
0x1A => "GlobalStoreShort",
0x1B => "GlobalStoreShortD16Hi",
0x1C => "GlobalStoreDword",
0x1D => "GlobalStoreDwordx2",
0x1E => "GlobalStoreDwordx4",
0x1F => "GlobalStoreDwordx3",
0x20 => "GlobalLoadUbyteD16",
0x21 => "GlobalLoadUbyteD16Hi",
0x22 => "GlobalLoadSbyteD16",
0x23 => "GlobalLoadSbyteD16Hi",
0x24 => "GlobalLoadShortD16",
0x25 => "GlobalLoadShortD16Hi",
0x32 => "GlobalAtomicAdd",
0x38 => "GlobalAtomicUMax",
_ => string.Empty,
}
: string.Empty;
@@ -1132,6 +1391,7 @@ public static class Gen5ShaderTranslator
0x10 => "ImageAtomicCmpswap",
0x1C => "ImageAtomicDec",
0x20 => "ImageSample",
0x22 => "ImageSampleD",
0x24 => "ImageSampleL",
0x25 => "ImageSampleB",
0x27 => "ImageSampleLz",
@@ -1143,6 +1403,7 @@ public static class Gen5ShaderTranslator
0x47 => "ImageGather4Lz",
0x48 => "ImageGather4C",
0x4E => "ImageGather4CBCl",
0x57 => "ImageGather4LzO",
0x5F => "ImageGather4CLzO",
_ => string.Empty,
};
@@ -1223,6 +1484,7 @@ public static class Gen5ShaderTranslator
name.StartsWith("Image", StringComparison.Ordinal);
public static bool IsStorageImageOperation(string name) =>
name.StartsWith("ImageLoad", StringComparison.Ordinal) ||
name.StartsWith("ImageStore", StringComparison.Ordinal) ||
name.StartsWith("ImageAtomic", StringComparison.Ordinal);
@@ -1239,7 +1501,11 @@ public static class Gen5ShaderTranslator
var isDpp =
encoding is Gen5ShaderEncoding.Vop1 or Gen5ShaderEncoding.Vop2 or Gen5ShaderEncoding.Vopc &&
(word & 0x1FF) == 0xFA;
var literal = !isSdwa && !isDpp && words.Length > MinimumEncodingDwords(encoding)
var isDpp8 =
encoding is Gen5ShaderEncoding.Vop1 or Gen5ShaderEncoding.Vop2 or Gen5ShaderEncoding.Vopc &&
(word & 0x1FF) is 0xE9 or 0xEA;
var literal = !isSdwa && !isDpp && !isDpp8 &&
words.Length > MinimumEncodingDwords(encoding)
? words[^1]
: (uint?)null;
IReadOnlyList<Gen5Operand> sources = [];
@@ -1314,20 +1580,36 @@ public static class Gen5ShaderTranslator
var scalarOffset = (extra >> 25) & 0x7F;
var offset = SignExtend(extra & 0x1FFFFF, 21);
var count = ScalarLoadDwordCount(opcode);
var scalarOffsetOperand = Gen5Operand.Source(scalarOffset);
var dynamicOffsetRegister = scalarOffsetOperand.Kind ==
Gen5OperandKind.ScalarRegister
? scalarOffsetOperand.Value
: (uint?)null;
sources =
[
Gen5Operand.Scalar(scalarBase),
Gen5Operand.Scalar(scalarOffset),
scalarOffsetOperand,
];
destinations = Enumerable
.Range((int)scalarDestination, checked((int)count))
.Select(index => Gen5Operand.Scalar((uint)index))
.ToArray();
control = new Gen5ScalarMemoryControl(count, offset, scalarOffset);
control = new Gen5ScalarMemoryControl(
count,
offset,
dynamicOffsetRegister);
break;
}
case Gen5ShaderEncoding.Vop1:
if (isDpp)
if (isDpp8)
{
var extra = words[1];
sources = [Gen5Operand.Vector(extra & 0xFF)];
control = new Gen5Dpp8Control(
extra >> 8,
(word & 0x1FF) == 0xEA);
}
else if (isDpp)
{
var extra = words[1];
sources = [Gen5Operand.Vector(extra & 0xFF)];
@@ -1339,24 +1621,36 @@ public static class Gen5ShaderTranslator
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);
control = CreateSdwaControl(extra, isCompare: false, hasSource1: false);
}
else
{
sources = [Gen5Operand.Source(word & 0x1FF, literal)];
}
destinations = [Gen5Operand.Vector((word >> 17) & 0xFF)];
// V_READFIRSTLANE_B32 is encoded as VOP1, but its destination
// field names an SGPR rather than a VGPR. Treating it like an
// ordinary vector destination leaves every invocation with a
// different value and corrupts scalar addresses derived from
// lane data.
destinations = opcode == "VReadfirstlaneB32"
? [Gen5Operand.Scalar((word >> 17) & 0x7F)]
: [Gen5Operand.Vector((word >> 17) & 0xFF)];
break;
case Gen5ShaderEncoding.Vop2:
if (isDpp)
if (isDpp8)
{
var extra = words[1];
sources =
[
Gen5Operand.Vector(extra & 0xFF),
Gen5Operand.Vector((word >> 9) & 0xFF),
];
control = new Gen5Dpp8Control(
extra >> 8,
(word & 0x1FF) == 0xEA);
}
else if (isDpp)
{
var extra = words[1];
sources =
@@ -1378,14 +1672,7 @@ public static class Gen5ShaderTranslator
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);
control = CreateSdwaControl(extra, isCompare: false, hasSource1: true);
}
else
{
@@ -1394,7 +1681,7 @@ public static class Gen5ShaderTranslator
Gen5Operand.Source(word & 0x1FF, literal),
Gen5Operand.Vector((word >> 9) & 0xFF),
];
if (opcode is "VMadMkF32" or "VFmamkF32" && literal.HasValue)
if ((opcode is "VMadMkF32" or "VFmaMkF32") && literal.HasValue)
{
sources =
[
@@ -1403,7 +1690,7 @@ public static class Gen5ShaderTranslator
sources[1],
];
}
else if (opcode is "VMadAkF32" or "VFmaakF32" && literal.HasValue)
else if ((opcode is "VMadAkF32" or "VFmaAkF32") && literal.HasValue)
{
sources =
[
@@ -1416,7 +1703,19 @@ public static class Gen5ShaderTranslator
destinations = [Gen5Operand.Vector((word >> 17) & 0xFF)];
break;
case Gen5ShaderEncoding.Vopc:
if (isDpp)
if (isDpp8)
{
var extra = words[1];
sources =
[
Gen5Operand.Vector(extra & 0xFF),
Gen5Operand.Vector((word >> 9) & 0xFF),
];
control = new Gen5Dpp8Control(
extra >> 8,
(word & 0x1FF) == 0xEA);
}
else if (isDpp)
{
var extra = words[1];
sources =
@@ -1439,14 +1738,13 @@ public static class Gen5ShaderTranslator
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);
var sdwa = CreateSdwaControl(extra, isCompare: true, hasSource1: true);
control = sdwa;
if (sdwa.ScalarDestination is { } scalarDestination &&
scalarDestination != 106)
{
destinations = [Gen5Operand.Scalar(scalarDestination)];
}
}
else
{
@@ -1479,6 +1777,7 @@ public static class Gen5ShaderTranslator
(extra >> 29) & 0x7,
(extra >> 27) & 0x3,
((word >> 15) & 1) != 0,
isVop3B ? 0 : (word >> 11) & 0xF,
isVop3B ? (word >> 8) & 0x7F : null);
break;
}
@@ -1495,6 +1794,10 @@ public static class Gen5ShaderTranslator
((word >> 17) & 1) != 0);
sources = opcode switch
{
"DsAddU32" => [
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Vector(vectorData0),
],
"DsWriteB32" => [
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Vector(vectorData0),
@@ -1504,6 +1807,19 @@ public static class Gen5ShaderTranslator
Gen5Operand.Vector(vectorData0),
Gen5Operand.Vector(vectorData0 + 1),
],
"DsWriteB96" => [
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Vector(vectorData0),
Gen5Operand.Vector(vectorData0 + 1),
Gen5Operand.Vector(vectorData0 + 2),
],
"DsWriteB128" => [
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Vector(vectorData0),
Gen5Operand.Vector(vectorData0 + 1),
Gen5Operand.Vector(vectorData0 + 2),
Gen5Operand.Vector(vectorData0 + 3),
],
"DsWrite2B32" or "DsWrite2St64B32" => [
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Vector(vectorData0),
@@ -1521,6 +1837,17 @@ public static class Gen5ShaderTranslator
Gen5Operand.Vector(vectorDestination),
Gen5Operand.Vector(vectorDestination + 1),
],
"DsReadB96" => [
Gen5Operand.Vector(vectorDestination),
Gen5Operand.Vector(vectorDestination + 1),
Gen5Operand.Vector(vectorDestination + 2),
],
"DsReadB128" => [
Gen5Operand.Vector(vectorDestination),
Gen5Operand.Vector(vectorDestination + 1),
Gen5Operand.Vector(vectorDestination + 2),
Gen5Operand.Vector(vectorDestination + 3),
],
_ => [],
};
break;
@@ -1540,10 +1867,30 @@ public static class Gen5ShaderTranslator
var scalarAddress = (extra >> 16) & 0x7F;
var dwordCount = opcode switch
{
"GlobalLoadUbyte" or
"GlobalLoadSbyte" or
"GlobalLoadUshort" or
"GlobalLoadSshort" or
"GlobalLoadUbyteD16" or
"GlobalLoadUbyteD16Hi" or
"GlobalLoadSbyteD16" or
"GlobalLoadSbyteD16Hi" or
"GlobalLoadShortD16" or
"GlobalLoadShortD16Hi" or
"GlobalStoreByte" or
"GlobalStoreByteD16Hi" or
"GlobalStoreShort" or
"GlobalStoreShortD16Hi" or
"GlobalStoreDword" or
"GlobalAtomicAdd" or
"GlobalAtomicUMax" => 1u,
"GlobalLoadDword" => 1u,
"GlobalLoadDwordx2" => 2u,
"GlobalLoadDwordx3" => 3u,
"GlobalLoadDwordx4" => 4u,
"GlobalStoreDwordx2" => 2u,
"GlobalStoreDwordx3" => 3u,
"GlobalStoreDwordx4" => 4u,
_ => 0u,
};
sources =
@@ -1551,10 +1898,12 @@ public static class Gen5ShaderTranslator
Gen5Operand.Vector(vectorAddress),
Gen5Operand.Scalar(scalarAddress),
];
destinations = Enumerable
.Range((int)vectorData, checked((int)dwordCount))
.Select(index => Gen5Operand.Vector((uint)index))
.ToArray();
destinations = opcode.StartsWith("GlobalLoad", StringComparison.Ordinal)
? Enumerable
.Range((int)vectorData, checked((int)dwordCount))
.Select(index => Gen5Operand.Vector((uint)index))
.ToArray()
: [];
control = new Gen5GlobalMemoryControl(
dwordCount,
vectorAddress,
@@ -1578,6 +1927,24 @@ public static class Gen5ShaderTranslator
"BufferLoadFormatXy" => 2u,
"BufferLoadFormatXyz" => 3u,
"BufferLoadFormatXyzw" => 4u,
"BufferStoreFormatX" => 1u,
"BufferStoreFormatXy" => 2u,
"BufferStoreFormatXyz" => 3u,
"BufferStoreFormatXyzw" => 4u,
"BufferLoadUbyte" or
"BufferLoadSbyte" or
"BufferLoadUshort" or
"BufferLoadSshort" or
"BufferStoreByte" or
"BufferStoreByteD16Hi" or
"BufferStoreShort" or
"BufferStoreShortD16Hi" or
"BufferLoadUbyteD16" or
"BufferLoadUbyteD16Hi" or
"BufferLoadSbyteD16" or
"BufferLoadSbyteD16Hi" or
"BufferLoadShortD16" or
"BufferLoadShortD16Hi" => 1u,
"BufferLoadDword" => 1u,
"BufferLoadDwordx2" => 2u,
"BufferLoadDwordx3" => 3u,
@@ -1587,6 +1954,7 @@ public static class Gen5ShaderTranslator
"BufferStoreDwordx3" => 3u,
"BufferStoreDwordx4" => 4u,
"BufferAtomicAdd" => 1u,
"BufferAtomicUMax" => 1u,
_ => 0u,
};
sources =
@@ -1690,7 +2058,9 @@ public static class Gen5ShaderTranslator
dimension,
dimension is 4 or 5 or 7,
((word >> 13) & 1) != 0,
((word >> 25) & 1) != 0);
((word >> 25) & 1) != 0,
((extra >> 30) & 1) != 0,
((extra >> 31) & 1) != 0);
break;
}
case Gen5ShaderEncoding.Exp:
@@ -1726,6 +2096,30 @@ public static class Gen5ShaderTranslator
(word >> 24) & 0xF,
(word >> 28) & 0xF);
private static Gen5SdwaControl CreateSdwaControl(
uint word,
bool isCompare,
bool hasSource1)
{
var scalarDestination = isCompare
? ((word >> 15) & 1) != 0
? (word >> 8) & 0x7Fu
: 106u
: (uint?)null;
return new Gen5SdwaControl(
isCompare ? 6u : (word >> 8) & 0x7u,
isCompare ? 0u : (word >> 11) & 0x3u,
(word >> 16) & 0x7u,
hasSource1 ? (word >> 24) & 0x7u : 6u,
((word >> 19) & 1) != 0,
hasSource1 && ((word >> 27) & 1) != 0,
((word >> 21) & 1) | (hasSource1 ? ((word >> 29) & 1) << 1 : 0),
((word >> 20) & 1) | (hasSource1 ? ((word >> 28) & 1) << 1 : 0),
isCompare ? 0u : (word >> 14) & 0x3u,
!isCompare && ((word >> 13) & 1) != 0,
scalarDestination);
}
private static int MinimumEncodingDwords(Gen5ShaderEncoding encoding) => encoding switch
{
Gen5ShaderEncoding.Vop3 or
@@ -1762,6 +2156,19 @@ public static class Gen5ShaderTranslator
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,
@@ -1824,6 +2231,7 @@ public static class Gen5ShaderTranslator
$"va={addressRegisters},vd=v{image.VectorData}," +
$"sr=s{image.ScalarResource},ss=s{image.ScalarSampler}," +
$"dim={image.Dimension},da={(image.IsArray ? 1 : 0)}," +
$"a16={(image.A16 ? 1 : 0)},d16={(image.D16 ? 1 : 0)}," +
$"glc={(image.Glc ? 1 : 0)}," +
$"slc={(image.Slc ? 1 : 0)}";
}
@@ -0,0 +1,138 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.ShaderCompiler;
/// <summary>
/// Converts the RDNA2 unified FORMAT field used by GFX10 buffer and image
/// descriptors into the data-format/number-format pair used by the rest of
/// SharpEmu's AGC pipeline.
/// </summary>
public static class Gfx10UnifiedFormat
{
public static bool TryDecode(
uint unifiedFormat,
out uint dataFormat,
out uint numberFormat)
{
// RDNA2 ISA table 47 is intentionally sparse. In particular, the
// integer/normalized spellings that some assemblers expose for
// 10_11_11 and 11_11_10 are reserved by the hardware data-format
// table; so are 10_10_10_2 USCALED/SSCALED. Keep this an exact table
// instead of deriving ranges that accidentally make reserved encodings
// look valid.
(dataFormat, numberFormat) = unifiedFormat switch
{
0 => (0u, 0u),
1 => (1u, 0u),
2 => (1u, 1u),
3 => (1u, 2u),
4 => (1u, 3u),
5 => (1u, 4u),
6 => (1u, 5u),
7 => (2u, 0u),
8 => (2u, 1u),
9 => (2u, 2u),
10 => (2u, 3u),
11 => (2u, 4u),
12 => (2u, 5u),
13 => (2u, 7u),
14 => (3u, 0u),
15 => (3u, 1u),
16 => (3u, 2u),
17 => (3u, 3u),
18 => (3u, 4u),
19 => (3u, 5u),
20 => (4u, 4u),
21 => (4u, 5u),
22 => (4u, 7u),
23 => (5u, 0u),
24 => (5u, 1u),
25 => (5u, 2u),
26 => (5u, 3u),
27 => (5u, 4u),
28 => (5u, 5u),
29 => (5u, 7u),
36 => (6u, 7u),
43 => (7u, 7u),
44 => (8u, 0u),
45 => (8u, 1u),
48 => (8u, 4u),
49 => (8u, 5u),
50 => (9u, 0u),
51 => (9u, 1u),
52 => (9u, 2u),
53 => (9u, 3u),
54 => (9u, 4u),
55 => (9u, 5u),
56 => (10u, 0u),
57 => (10u, 1u),
58 => (10u, 2u),
59 => (10u, 3u),
60 => (10u, 4u),
61 => (10u, 5u),
62 => (11u, 4u),
63 => (11u, 5u),
64 => (11u, 7u),
65 => (12u, 0u),
66 => (12u, 1u),
67 => (12u, 2u),
68 => (12u, 3u),
69 => (12u, 4u),
70 => (12u, 5u),
71 => (12u, 7u),
72 => (13u, 4u),
73 => (13u, 5u),
74 => (13u, 7u),
75 => (14u, 4u),
76 => (14u, 5u),
77 => (14u, 7u),
128 => (1u, 9u),
129 => (3u, 9u),
130 => (10u, 9u),
// Image-only encodings without a legacy DATA_FORMAT equivalent
// retain the unified identifier for exact downstream dispatch.
131 => (131u, 7u),
132 => (34u, 7u),
133 => (16u, 0u),
134 => (17u, 0u),
135 => (18u, 0u),
136 => (19u, 0u),
137 => (137u, 0u),
138 => (138u, 0u),
139 => (139u, 0u),
140 => (4u, 7u),
141 => (20u, 0u),
142 => (20u, 4u),
143 => (21u, 0u),
144 => (21u, 4u),
145 => (22u, 4u),
146 => (22u, 7u),
147 => (32u, 0u),
148 => (32u, 1u),
149 => (32u, 4u),
150 => (32u, 9u),
151 => (33u, 0u),
152 => (33u, 1u),
153 => (33u, 4u),
154 => (33u, 9u),
169 => (169u, 0u),
170 => (170u, 9u),
171 => (171u, 0u),
172 => (172u, 9u),
173 => (173u, 0u),
174 => (174u, 9u),
175 => (175u, 0u),
176 => (176u, 1u),
177 => (177u, 0u),
178 => (178u, 1u),
179 => (179u, 7u),
180 => (180u, 7u),
181 => (181u, 0u),
182 => (182u, 9u),
_ => (0u, 0u),
};
return unifiedFormat == 0 || dataFormat != 0;
}
}