398 lines
11 KiB
C#
398 lines
11 KiB
C#
// Copyright (C) 2026 SharpEmu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
namespace SharpEmu.ShaderCompiler;
|
|
|
|
public enum Gen5ShaderEncoding
|
|
{
|
|
Sop1,
|
|
Sop2,
|
|
Sopc,
|
|
Sopp,
|
|
Sopk,
|
|
Smrd,
|
|
Smem,
|
|
Mubuf,
|
|
Mtbuf,
|
|
Vop1,
|
|
Vop2,
|
|
Vopc,
|
|
Vop3,
|
|
Vintrp,
|
|
Ds,
|
|
Flat,
|
|
Vop3p,
|
|
Mimg,
|
|
Exp,
|
|
}
|
|
|
|
public enum Gen5OperandKind
|
|
{
|
|
ScalarRegister,
|
|
VectorRegister,
|
|
EncodedConstant,
|
|
LiteralConstant,
|
|
}
|
|
|
|
public enum Gen5PixelOutputKind
|
|
{
|
|
Float,
|
|
Uint,
|
|
Sint,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects the logical shader component for each physical color component.
|
|
/// Each component uses two bits in <see cref="Packed"/>.
|
|
/// </summary>
|
|
public readonly record struct Gen5ColorComponentMapping
|
|
{
|
|
public const byte IdentityPacked = 0xE4;
|
|
private readonly byte _encoded;
|
|
|
|
public Gen5ColorComponentMapping(byte packed)
|
|
{
|
|
_encoded = (byte)(packed ^ IdentityPacked);
|
|
}
|
|
|
|
public byte Packed => (byte)(_encoded ^ IdentityPacked);
|
|
|
|
public static Gen5ColorComponentMapping Identity { get; } = new(IdentityPacked);
|
|
|
|
public uint Map(uint physicalComponent) =>
|
|
physicalComponent < 4
|
|
? (uint)(Packed >> checked((int)(physicalComponent * 2))) & 0x3u
|
|
: physicalComponent;
|
|
|
|
public uint ApplyMask(uint logicalMask)
|
|
{
|
|
var mappedMask = 0u;
|
|
for (var physicalComponent = 0u; physicalComponent < 4; physicalComponent++)
|
|
{
|
|
mappedMask |= ((logicalMask >> checked((int)Map(physicalComponent))) & 1u)
|
|
<< checked((int)physicalComponent);
|
|
}
|
|
|
|
return mappedMask;
|
|
}
|
|
|
|
public Gen5ColorComponentMapping Then(Gen5ColorComponentMapping next)
|
|
{
|
|
var packed = 0u;
|
|
for (var physicalComponent = 0u; physicalComponent < 4; physicalComponent++)
|
|
{
|
|
packed |= next.Map(Map(physicalComponent))
|
|
<< checked((int)(physicalComponent * 2));
|
|
}
|
|
|
|
return new Gen5ColorComponentMapping(checked((byte)packed));
|
|
}
|
|
|
|
public bool IsIdentity => Packed == IdentityPacked;
|
|
|
|
public static bool TryResolveRenderTarget(
|
|
uint componentSwap,
|
|
uint componentCount,
|
|
out Gen5ColorComponentMapping mapping)
|
|
{
|
|
var packed = (componentSwap, componentCount) switch
|
|
{
|
|
(0, >= 1 and <= 4) => IdentityPacked,
|
|
(1, 1) => 0xE1,
|
|
(1, 2) => 0x6C,
|
|
(1, 3) => 0xB4,
|
|
(1, 4) => 0xC6,
|
|
(2, 1) => 0xC6,
|
|
(2, 2) => 0xE1,
|
|
(2, 3) => 0xC6,
|
|
(2, 4) => 0x1B,
|
|
(3, 1) => 0x27,
|
|
(3, 2) => 0x63,
|
|
(3, 3) => 0x87,
|
|
(3, 4) => 0x93,
|
|
_ => -1,
|
|
};
|
|
mapping = packed >= 0
|
|
? new Gen5ColorComponentMapping(checked((byte)packed))
|
|
: default;
|
|
return packed >= 0;
|
|
}
|
|
}
|
|
|
|
public readonly record struct Gen5PixelOutputBinding(
|
|
uint GuestSlot,
|
|
uint HostLocation,
|
|
Gen5PixelOutputKind Kind,
|
|
Gen5ColorComponentMapping ComponentMapping)
|
|
{
|
|
public Gen5PixelOutputBinding(
|
|
uint guestSlot,
|
|
uint hostLocation,
|
|
Gen5PixelOutputKind kind)
|
|
: this(guestSlot, hostLocation, kind, Gen5ColorComponentMapping.Identity)
|
|
{
|
|
}
|
|
|
|
private readonly uint? _exportTarget;
|
|
|
|
// The EXP MRT target that feeds this slot. It differs from the slot when the pixel
|
|
// program skips targets, because the hardware packs color exports into the slots
|
|
// that CB_SHADER_MASK enables.
|
|
public uint ExportTarget
|
|
{
|
|
get => _exportTarget ?? GuestSlot;
|
|
init => _exportTarget = value;
|
|
}
|
|
}
|
|
|
|
public readonly record struct Gen5ComputeSystemRegisters(
|
|
uint? WorkGroupXRegister,
|
|
uint? WorkGroupYRegister,
|
|
uint? WorkGroupZRegister,
|
|
uint? ThreadGroupSizeRegister);
|
|
|
|
public readonly record struct Gen5Operand(Gen5OperandKind Kind, uint Value)
|
|
{
|
|
public static Gen5Operand Scalar(uint index) =>
|
|
new(Gen5OperandKind.ScalarRegister, index);
|
|
|
|
public static Gen5Operand Vector(uint index) =>
|
|
new(Gen5OperandKind.VectorRegister, index);
|
|
|
|
public static Gen5Operand Source(uint encoded, uint? literal = null)
|
|
{
|
|
if (encoded >= 256)
|
|
{
|
|
return Vector(encoded - 256);
|
|
}
|
|
|
|
if (encoded is 249 or 255 && literal.HasValue)
|
|
{
|
|
return new(Gen5OperandKind.LiteralConstant, literal.Value);
|
|
}
|
|
|
|
// 108..123 are the temporary SGPRs (TTMP0..TTMP15). They use the
|
|
// same scalar register file as ordinary SGPRs; treating them as
|
|
// encoded constants loses values in shader continuations that spill
|
|
// through a temporary register.
|
|
if (encoded <= 105 || encoded is 106 or 107 or (>= 108 and <= 123) or 124 or 126 or 127)
|
|
{
|
|
return Scalar(encoded);
|
|
}
|
|
|
|
return new(Gen5OperandKind.EncodedConstant, encoded);
|
|
}
|
|
|
|
public override string ToString() => Kind switch
|
|
{
|
|
Gen5OperandKind.ScalarRegister => $"s{Value}",
|
|
Gen5OperandKind.VectorRegister => $"v{Value}",
|
|
Gen5OperandKind.LiteralConstant => $"0x{Value:X8}",
|
|
_ => $"src[{Value}]",
|
|
};
|
|
}
|
|
|
|
public abstract record Gen5InstructionControl;
|
|
|
|
public sealed record Gen5ImageControl(
|
|
uint Dmask,
|
|
uint VectorAddress,
|
|
IReadOnlyList<uint> AddressRegisters,
|
|
uint VectorData,
|
|
uint ScalarResource,
|
|
uint ScalarSampler,
|
|
uint Dimension,
|
|
bool IsArray,
|
|
bool Glc,
|
|
bool Slc,
|
|
bool A16,
|
|
bool D16) : Gen5InstructionControl
|
|
{
|
|
public uint GetAddressRegister(int component) =>
|
|
component < AddressRegisters.Count
|
|
? AddressRegisters[component]
|
|
: VectorAddress + (uint)component;
|
|
}
|
|
|
|
public sealed record Gen5RayIntersectControl(
|
|
uint VectorAddress,
|
|
IReadOnlyList<uint> AddressRegisters,
|
|
uint VectorData,
|
|
uint ScalarResource,
|
|
bool A16) : Gen5InstructionControl
|
|
{
|
|
public const uint ResultDwords = 4;
|
|
|
|
public uint GetAddressRegister(int component) =>
|
|
component < AddressRegisters.Count
|
|
? AddressRegisters[component]
|
|
: VectorAddress + (uint)component;
|
|
}
|
|
|
|
public sealed record Gen5GlobalMemoryControl(
|
|
uint DwordCount,
|
|
uint VectorAddress,
|
|
uint SourceVectorRegister,
|
|
uint DestinationVectorRegister,
|
|
uint ScalarAddress,
|
|
int OffsetBytes,
|
|
bool Glc,
|
|
bool Slc,
|
|
bool UsesFlatAddress = false,
|
|
uint? DynamicOffsetRegister = null,
|
|
bool SourceIsScalar = false) : Gen5InstructionControl;
|
|
|
|
// A typed access carries the unified format from the instruction; a formatted
|
|
// untyped access reads the descriptor format when it executes.
|
|
public sealed record Gen5BufferMemoryControl(
|
|
uint DwordCount,
|
|
uint VectorAddress,
|
|
uint VectorData,
|
|
uint ScalarResource,
|
|
int OffsetBytes,
|
|
bool IndexEnabled,
|
|
bool OffsetEnabled,
|
|
bool Glc,
|
|
bool Slc,
|
|
bool Typed = false,
|
|
uint TypedFormat = 0) : Gen5InstructionControl;
|
|
|
|
public sealed record Gen5ExportControl(
|
|
uint Target,
|
|
uint EnableMask,
|
|
bool Compressed,
|
|
bool Done,
|
|
bool ValidMask) : Gen5InstructionControl;
|
|
|
|
public sealed record Gen5InterpolationControl(
|
|
uint Attribute,
|
|
uint Channel) : Gen5InstructionControl;
|
|
|
|
public sealed record Gen5Vop3Control(
|
|
uint AbsoluteMask,
|
|
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,
|
|
uint? ScalarDestination) : Gen5InstructionControl;
|
|
|
|
// Packed (VOP3P) source and destination modifiers. Each mask holds one bit per
|
|
// source operand. OpSel/OpSelHi pick which 16-bit half of a source feeds the low
|
|
// and high result lanes respectively; NegLo/NegHi negate the value routed to each
|
|
// lane. Clamp saturates each output half to [0, 1].
|
|
public sealed record Gen5Vop3pControl(
|
|
uint OpSelMask,
|
|
uint OpSelHiMask,
|
|
uint NegLoMask,
|
|
uint NegHiMask,
|
|
bool Clamp) : Gen5InstructionControl;
|
|
|
|
public sealed record Gen5DppControl(
|
|
uint Control,
|
|
bool FetchInactive,
|
|
bool BoundControl,
|
|
uint AbsoluteMask,
|
|
uint NegateMask,
|
|
uint BankMask,
|
|
uint RowMask) : Gen5InstructionControl;
|
|
|
|
public sealed record Gen5Dpp8Control(
|
|
uint LaneSelectors,
|
|
bool FetchInactive) : Gen5InstructionControl;
|
|
|
|
public sealed record Gen5ScalarMemoryControl(
|
|
uint DestinationCount,
|
|
int ImmediateOffsetBytes,
|
|
uint? DynamicOffsetRegister) : Gen5InstructionControl;
|
|
|
|
public sealed record Gen5DataShareControl(
|
|
uint Offset0,
|
|
uint Offset1,
|
|
bool Gds) : Gen5InstructionControl
|
|
{
|
|
// Single-address DS instructions encode one 16-bit byte offset across
|
|
// OFFSET0 and OFFSET1. The paired read2/write2 forms instead interpret
|
|
// them as two independent scaled 8-bit offsets.
|
|
public uint SingleOffsetBytes => Offset0 | (Offset1 << 8);
|
|
}
|
|
|
|
public sealed record Gen5ShaderInstruction(
|
|
uint Pc,
|
|
Gen5ShaderEncoding Encoding,
|
|
string Opcode,
|
|
IReadOnlyList<uint> Words,
|
|
IReadOnlyList<Gen5Operand> Sources,
|
|
IReadOnlyList<Gen5Operand> Destinations,
|
|
Gen5InstructionControl? Control)
|
|
{
|
|
public ulong? AddressOffset { get; init; }
|
|
|
|
public ulong ProgramOffset => AddressOffset ?? Pc;
|
|
}
|
|
|
|
public sealed record Gen5ShaderProgram(
|
|
ulong Address,
|
|
IReadOnlyList<Gen5ShaderInstruction> Instructions)
|
|
{
|
|
private const uint PixelColorTargetCount = 8;
|
|
private const int PixelColorMaskBits = 4;
|
|
private readonly uint _pixelColorExportMasks = ComputePixelColorExportMasks(Instructions);
|
|
private readonly uint _parameterExportMask = ComputeParameterExportMask(Instructions);
|
|
|
|
public uint PixelColorExportMasks => _pixelColorExportMasks;
|
|
|
|
public uint ParameterExportMask => _parameterExportMask;
|
|
|
|
private static uint ComputePixelColorExportMasks(
|
|
IReadOnlyList<Gen5ShaderInstruction> instructions)
|
|
{
|
|
var masks = 0u;
|
|
foreach (var instruction in instructions)
|
|
{
|
|
if (instruction.Control is Gen5ExportControl export &&
|
|
export.Target < PixelColorTargetCount)
|
|
{
|
|
masks |= (export.EnableMask & 0xFu) <<
|
|
(int)(export.Target * PixelColorMaskBits);
|
|
}
|
|
}
|
|
|
|
return masks;
|
|
}
|
|
|
|
private static uint ComputeParameterExportMask(
|
|
IReadOnlyList<Gen5ShaderInstruction> instructions)
|
|
{
|
|
var mask = 0u;
|
|
foreach (var instruction in instructions)
|
|
{
|
|
if (instruction.Control is Gen5ExportControl export &&
|
|
export.Target is >= 32 and < 64 &&
|
|
export.EnableMask != 0)
|
|
{
|
|
mask |= 1u << (int)(export.Target - 32);
|
|
}
|
|
}
|
|
|
|
return mask;
|
|
}
|
|
|
|
public IEnumerable<Gen5ImageControl> ImageResources =>
|
|
Instructions
|
|
.Select(instruction => instruction.Control)
|
|
.OfType<Gen5ImageControl>();
|
|
}
|