Files
sharpemu/src/SharpEmu.Libs/Agc/Gen5SpirvTranslator.cs
T

2570 lines
95 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
namespace SharpEmu.Libs.Agc;
internal static partial class Gen5SpirvTranslator
{
private const uint ScalarRegisterCount = 256;
private const uint VectorRegisterCount = 512;
private const uint LdsDwordCount = 8192;
private const uint RdnaWaveLaneCount = 32;
public static bool TryCompilePixelShader(
Gen5ShaderState state,
Gen5ShaderEvaluation evaluation,
Gen5PixelOutputKind outputKind,
out Gen5SpirvShader shader,
out string error,
int globalBufferBase = 0,
int totalGlobalBufferCount = -1,
int imageBindingBase = 0)
{
var context = new CompilationContext(
Gen5SpirvStage.Pixel,
state,
evaluation,
outputKind,
1,
1,
1,
globalBufferBase,
totalGlobalBufferCount,
imageBindingBase);
return context.TryCompile(out shader, out error);
}
public static bool TryCompileVertexShader(
Gen5ShaderState state,
Gen5ShaderEvaluation evaluation,
out Gen5SpirvShader shader,
out string error,
int globalBufferBase = 0,
int totalGlobalBufferCount = -1,
int imageBindingBase = 0)
{
var context = new CompilationContext(
Gen5SpirvStage.Vertex,
state,
evaluation,
Gen5PixelOutputKind.Float,
1,
1,
1,
globalBufferBase,
totalGlobalBufferCount,
imageBindingBase);
return context.TryCompile(out shader, out error);
}
public static bool TryCompileComputeShader(
Gen5ShaderState state,
Gen5ShaderEvaluation evaluation,
uint localSizeX,
uint localSizeY,
uint localSizeZ,
out Gen5SpirvShader shader,
out string error)
{
var context = new CompilationContext(
Gen5SpirvStage.Compute,
state,
evaluation,
Gen5PixelOutputKind.Float,
Math.Max(localSizeX, 1),
Math.Max(localSizeY, 1),
Math.Max(localSizeZ, 1),
0,
-1,
0);
return context.TryCompile(out shader, out error);
}
private sealed partial class CompilationContext
{
private readonly SpirvModuleBuilder _module = new();
private readonly Gen5SpirvStage _stage;
private readonly Gen5ShaderState _state;
private readonly Gen5ShaderEvaluation _evaluation;
private readonly Gen5PixelOutputKind _outputKind;
private readonly uint _localSizeX;
private readonly uint _localSizeY;
private readonly uint _localSizeZ;
private readonly int _globalBufferBase;
private readonly int _totalGlobalBufferCount;
private readonly int _imageBindingBase;
private readonly List<uint> _interfaces = [];
private readonly Dictionary<uint, uint> _pixelInputs = [];
private readonly Dictionary<uint, uint> _vertexOutputs = [];
private readonly Dictionary<uint, SpirvVertexInput> _vertexInputsByPc = [];
private readonly List<SpirvImageResource> _imageResources = [];
private readonly Dictionary<uint, int> _imageBindingByPc = [];
private readonly Dictionary<uint, int> _bufferBindingByPc = [];
private uint _voidType;
private uint _boolType;
private uint _uintType;
private uint _intType;
private uint _longType;
private uint _ulongType;
private uint _floatType;
private uint _vec2Type;
private uint _vec3Type;
private uint _vec4Type;
private uint _uvec2Type;
private uint _uvec3Type;
private uint _uvec4Type;
private uint _privateUintPointer;
private uint _privateBoolPointer;
private uint _scalarRegisters;
private uint _vectorRegisters;
private uint _scc;
private uint _vcc;
private uint _exec;
private uint _programCounter;
private uint _programActive;
private uint _globalBuffers;
private uint _storageUintPointer;
private uint _lds;
private uint _workgroupUintPointer;
private uint _positionOutput;
private uint _pixelOutput;
private uint _vertexIndexInput;
private uint _instanceIndexInput;
private uint _fragCoordInput;
private uint _localInvocationIdInput;
private uint _workGroupIdInput;
private uint _subgroupInvocationIdInput;
private uint _glsl;
private enum ImageComponentKind
{
Float,
Sint,
Uint,
}
private readonly record struct SpirvImageResource(
uint Variable,
uint ImageType,
uint ObjectType,
uint ComponentType,
uint VectorType,
ImageComponentKind ComponentKind,
bool IsStorage);
private readonly record struct SpirvVertexInput(
uint Variable,
uint Type,
uint ComponentCount);
public CompilationContext(
Gen5SpirvStage stage,
Gen5ShaderState state,
Gen5ShaderEvaluation evaluation,
Gen5PixelOutputKind outputKind,
uint localSizeX,
uint localSizeY,
uint localSizeZ,
int globalBufferBase,
int totalGlobalBufferCount,
int imageBindingBase)
{
_stage = stage;
_state = state;
_evaluation = evaluation;
_outputKind = outputKind;
_localSizeX = localSizeX;
_localSizeY = localSizeY;
_localSizeZ = localSizeZ;
_globalBufferBase = globalBufferBase;
_totalGlobalBufferCount = totalGlobalBufferCount < 0
? evaluation.GlobalMemoryBindings.Count
: totalGlobalBufferCount;
_imageBindingBase = imageBindingBase;
}
public bool TryCompile(out Gen5SpirvShader shader, out string error)
{
shader = default!;
error = string.Empty;
try
{
DeclareModule();
var blocks = BuildBasicBlocks(_state.Program.Instructions);
if (blocks.Count == 0)
{
error = "shader contains no executable blocks";
return false;
}
var functionType = _module.TypeFunction(_voidType);
var main = _module.BeginFunction(_voidType, functionType);
_module.AddName(main, "main");
_module.AddLabel();
EmitInitialState();
var loopHeader = _module.AllocateId();
var switchHeader = _module.AllocateId();
var switchMerge = _module.AllocateId();
var loopContinue = _module.AllocateId();
var loopMerge = _module.AllocateId();
var defaultLabel = _module.AllocateId();
var caseLabels = new uint[blocks.Count];
for (var index = 0; index < caseLabels.Length; index++)
{
caseLabels[index] = _module.AllocateId();
}
_module.AddStatement(SpirvOp.Branch, loopHeader);
_module.AddLabel(loopHeader);
_module.AddStatement(SpirvOp.LoopMerge, loopMerge, loopContinue, 0);
_module.AddStatement(SpirvOp.Branch, switchHeader);
_module.AddLabel(switchHeader);
var selector = Load(_uintType, _programCounter);
_module.AddStatement(SpirvOp.SelectionMerge, switchMerge, 0);
var switchOperands = new uint[2 + (blocks.Count * 2)];
switchOperands[0] = selector;
switchOperands[1] = defaultLabel;
for (var index = 0; index < blocks.Count; index++)
{
switchOperands[2 + (index * 2)] = (uint)index;
switchOperands[3 + (index * 2)] = caseLabels[index];
}
_module.AddStatement(SpirvOp.Switch, switchOperands);
for (var index = 0; index < blocks.Count; index++)
{
_module.AddLabel(caseLabels[index]);
if (!TryEmitBlock(blocks, index, out error))
{
error = $"block=0x{blocks[index].StartPc:X}: {error}";
return false;
}
_module.AddStatement(SpirvOp.Branch, switchMerge);
}
_module.AddLabel(defaultLabel);
Store(_programActive, _module.ConstantBool(false));
_module.AddStatement(SpirvOp.Branch, switchMerge);
_module.AddLabel(switchMerge);
_module.AddStatement(SpirvOp.Branch, loopContinue);
_module.AddLabel(loopContinue);
var active = Load(_boolType, _programActive);
_module.AddStatement(
SpirvOp.BranchConditional,
active,
loopHeader,
loopMerge);
_module.AddLabel(loopMerge);
_module.AddStatement(SpirvOp.Return);
_module.EndFunction();
var model = _stage switch
{
Gen5SpirvStage.Vertex => SpirvExecutionModel.Vertex,
Gen5SpirvStage.Pixel => SpirvExecutionModel.Fragment,
_ => SpirvExecutionModel.GLCompute,
};
_module.AddEntryPoint(model, main, "main", _interfaces);
if (_stage == Gen5SpirvStage.Pixel)
{
_module.AddExecutionMode(main, SpirvExecutionMode.OriginUpperLeft);
}
else if (_stage == Gen5SpirvStage.Compute)
{
_module.AddExecutionMode(
main,
SpirvExecutionMode.LocalSize,
_localSizeX,
_localSizeY,
_localSizeZ);
}
var attributeCount = _stage == Gen5SpirvStage.Vertex
? (uint)_vertexOutputs.Count
: (uint)_pixelInputs.Count;
shader = new Gen5SpirvShader(
_module.Build(),
_evaluation.GlobalMemoryBindings,
_evaluation.ImageBindings,
attributeCount,
_stage == Gen5SpirvStage.Vertex
? _evaluation.VertexInputs ?? []
: []);
return true;
}
catch (Exception exception)
{
error = exception.Message;
return false;
}
}
private void DeclareModule()
{
_module.AddCapability(SpirvCapability.Shader);
_module.AddCapability(SpirvCapability.Int64);
_module.AddCapability(SpirvCapability.ImageQuery);
if (_evaluation.ImageBindings.Any(
static binding =>
(binding.Opcode.StartsWith(
"ImageSample",
StringComparison.Ordinal) ||
binding.Opcode.StartsWith(
"ImageGather4",
StringComparison.Ordinal)) &&
binding.Opcode.EndsWith("O", StringComparison.Ordinal)))
{
_module.AddCapability(SpirvCapability.ImageGatherExtended);
}
if (UsesSubgroupOperations())
{
_module.AddCapability(SpirvCapability.GroupNonUniform);
if (UsesSubgroupShuffle())
{
_module.AddCapability(SpirvCapability.GroupNonUniformShuffle);
}
if (UsesWaveControl())
{
_module.AddCapability(SpirvCapability.GroupNonUniformVote);
}
}
_glsl = _module.ImportExtInst("GLSL.std.450");
_voidType = _module.TypeVoid();
_boolType = _module.TypeBool();
_uintType = _module.TypeInt(32, signed: false);
_intType = _module.TypeInt(32, signed: true);
_longType = _module.TypeInt(64, signed: true);
_ulongType = _module.TypeInt(64, signed: false);
_floatType = _module.TypeFloat(32);
_vec2Type = _module.TypeVector(_floatType, 2);
_vec3Type = _module.TypeVector(_floatType, 3);
_vec4Type = _module.TypeVector(_floatType, 4);
_uvec2Type = _module.TypeVector(_uintType, 2);
_uvec3Type = _module.TypeVector(_uintType, 3);
_uvec4Type = _module.TypeVector(_uintType, 4);
_privateUintPointer =
_module.TypePointer(SpirvStorageClass.Private, _uintType);
_privateBoolPointer =
_module.TypePointer(SpirvStorageClass.Private, _boolType);
var scalarArrayType = _module.TypeArray(_uintType, ScalarRegisterCount);
var vectorArrayType = _module.TypeArray(_uintType, VectorRegisterCount);
var privateScalarArrayPointer =
_module.TypePointer(SpirvStorageClass.Private, scalarArrayType);
var privateVectorArrayPointer =
_module.TypePointer(SpirvStorageClass.Private, vectorArrayType);
_scalarRegisters = _module.AddGlobalVariable(
privateScalarArrayPointer,
SpirvStorageClass.Private,
_module.ConstantNull(scalarArrayType));
_vectorRegisters = _module.AddGlobalVariable(
privateVectorArrayPointer,
SpirvStorageClass.Private,
_module.ConstantNull(vectorArrayType));
_scc = _module.AddGlobalVariable(
_privateBoolPointer,
SpirvStorageClass.Private,
_module.ConstantBool(false));
_vcc = _module.AddGlobalVariable(
_privateBoolPointer,
SpirvStorageClass.Private,
_module.ConstantBool(false));
_exec = _module.AddGlobalVariable(
_privateBoolPointer,
SpirvStorageClass.Private,
_module.ConstantBool(true));
_programCounter = _module.AddGlobalVariable(
_privateUintPointer,
SpirvStorageClass.Private,
_module.Constant(_uintType, 0));
_programActive = _module.AddGlobalVariable(
_privateBoolPointer,
SpirvStorageClass.Private,
_module.ConstantBool(true));
_interfaces.Add(_scalarRegisters);
_interfaces.Add(_vectorRegisters);
_interfaces.Add(_scc);
_interfaces.Add(_vcc);
_interfaces.Add(_exec);
_interfaces.Add(_programCounter);
_interfaces.Add(_programActive);
_module.AddName(_scalarRegisters, "sgpr");
_module.AddName(_vectorRegisters, "vgpr");
DeclareBuffers();
DeclareImages();
DeclareLds();
DeclareStageInterface();
}
private void DeclareLds()
{
if (_stage != Gen5SpirvStage.Compute || !UsesLds())
{
return;
}
var ldsArrayType = _module.TypeArray(_uintType, LdsDwordCount);
var ldsPointer =
_module.TypePointer(SpirvStorageClass.Workgroup, ldsArrayType);
_workgroupUintPointer =
_module.TypePointer(SpirvStorageClass.Workgroup, _uintType);
_lds = _module.AddGlobalVariable(
ldsPointer,
SpirvStorageClass.Workgroup);
_module.AddName(_lds, "lds");
_interfaces.Add(_lds);
}
private void DeclareBuffers()
{
for (var index = 0; index < _evaluation.GlobalMemoryBindings.Count; index++)
{
foreach (var pc in _evaluation.GlobalMemoryBindings[index].InstructionPcs)
{
_bufferBindingByPc.TryAdd(pc, _globalBufferBase + index);
}
}
if (_totalGlobalBufferCount == 0)
{
return;
}
var runtimeArray = _module.TypeRuntimeArray(_uintType);
_module.AddDecoration(runtimeArray, SpirvDecoration.ArrayStride, sizeof(uint));
var block = _module.TypeStruct(runtimeArray);
_module.AddDecoration(block, SpirvDecoration.Block);
_module.AddMemberDecoration(block, 0, SpirvDecoration.Offset, 0);
var descriptors = _module.TypeArray(
block,
(uint)_totalGlobalBufferCount);
var descriptorsPointer =
_module.TypePointer(SpirvStorageClass.StorageBuffer, descriptors);
_storageUintPointer =
_module.TypePointer(SpirvStorageClass.StorageBuffer, _uintType);
_globalBuffers = _module.AddGlobalVariable(
descriptorsPointer,
SpirvStorageClass.StorageBuffer);
_module.AddName(_globalBuffers, "guestBuffers");
_module.AddDecoration(_globalBuffers, SpirvDecoration.DescriptorSet, 0);
_module.AddDecoration(_globalBuffers, SpirvDecoration.Binding, 0);
_interfaces.Add(_globalBuffers);
}
private void DeclareImages()
{
for (var index = 0; index < _evaluation.ImageBindings.Count; index++)
{
var binding = _evaluation.ImageBindings[index];
_imageBindingByPc.TryAdd(binding.Pc, index);
var isStorage =
Gen5ShaderTranslator.IsStorageImageOperation(binding.Opcode);
var (format, componentKind) =
DecodeImageFormat(binding.ResourceDescriptor);
var componentType = componentKind switch
{
ImageComponentKind.Sint => _intType,
ImageComponentKind.Uint => _uintType,
_ => _floatType,
};
if (isStorage && format == SpirvImageFormat.Unknown)
{
_module.AddCapability(
SpirvCapability.StorageImageReadWithoutFormat);
_module.AddCapability(
SpirvCapability.StorageImageWriteWithoutFormat);
}
else if (isStorage && RequiresExtendedStorageImageFormat(format))
{
_module.AddCapability(
SpirvCapability.StorageImageExtendedFormats);
}
var imageType = _module.TypeImage(
componentType,
SpirvImageDim.Dim2D,
depth: false,
arrayed: false,
multisampled: false,
sampled: isStorage ? 2u : 1u,
isStorage ? format : SpirvImageFormat.Unknown);
var objectType = isStorage
? imageType
: _module.TypeSampledImage(imageType);
var pointer = _module.TypePointer(
SpirvStorageClass.UniformConstant,
objectType);
var variable = _module.AddGlobalVariable(
pointer,
SpirvStorageClass.UniformConstant);
_module.AddName(variable, isStorage ? $"image{index}" : $"tex{index}");
_module.AddDecoration(variable, SpirvDecoration.DescriptorSet, 0);
_module.AddDecoration(
variable,
SpirvDecoration.Binding,
(uint)(_imageBindingBase + index + 1));
_imageResources.Add(
new SpirvImageResource(
variable,
imageType,
objectType,
componentType,
_module.TypeVector(componentType, 4),
componentKind,
isStorage));
_interfaces.Add(variable);
}
}
private static bool RequiresExtendedStorageImageFormat(
SpirvImageFormat format) =>
format is not SpirvImageFormat.Unknown and
not SpirvImageFormat.Rgba32f and
not SpirvImageFormat.Rgba32i and
not SpirvImageFormat.Rgba32ui;
private static (SpirvImageFormat Format, ImageComponentKind Kind)
DecodeImageFormat(IReadOnlyList<uint> descriptor)
{
if (descriptor.Count < 2)
{
return (SpirvImageFormat.Unknown, ImageComponentKind.Float);
}
var dataFormat = (descriptor[1] >> 20) & 0x1FFu;
var numberType = (descriptor[1] >> 26) & 0xFu;
return (dataFormat, numberType) switch
{
(1, _) => (SpirvImageFormat.R8, ImageComponentKind.Float),
(2, _) => (SpirvImageFormat.R16f, ImageComponentKind.Float),
(3, _) => (SpirvImageFormat.Rg8, ImageComponentKind.Float),
(4, 4) => (SpirvImageFormat.R32ui, ImageComponentKind.Uint),
(4, 5) => (SpirvImageFormat.R32i, ImageComponentKind.Sint),
(4, _) => (SpirvImageFormat.R32f, ImageComponentKind.Float),
(5, 4) => (SpirvImageFormat.Rg16ui, ImageComponentKind.Uint),
(5, 5) => (SpirvImageFormat.Rg16i, ImageComponentKind.Sint),
(5, 0) => (SpirvImageFormat.Rg16, ImageComponentKind.Float),
(5, _) => (SpirvImageFormat.Rg16f, ImageComponentKind.Float),
(6 or 7, _) => (
SpirvImageFormat.R11fG11fB10f,
ImageComponentKind.Float),
(9, 4) => (SpirvImageFormat.Rgb10A2ui, ImageComponentKind.Uint),
(9, _) => (SpirvImageFormat.Rgb10A2, ImageComponentKind.Float),
(10, 4) => (SpirvImageFormat.Rgba8ui, ImageComponentKind.Uint),
(10, 5) => (SpirvImageFormat.Rgba8i, ImageComponentKind.Sint),
(10, _) => (SpirvImageFormat.Rgba8, ImageComponentKind.Float),
(11, 4) => (SpirvImageFormat.Rg32ui, ImageComponentKind.Uint),
(11, 5) => (SpirvImageFormat.Rg32i, ImageComponentKind.Sint),
(11, _) => (SpirvImageFormat.Rg32f, ImageComponentKind.Float),
(12, 4) => (SpirvImageFormat.Rgba16ui, ImageComponentKind.Uint),
(12, 5) => (SpirvImageFormat.Rgba16i, ImageComponentKind.Sint),
(12, 0) => (SpirvImageFormat.Rgba16, ImageComponentKind.Float),
(12, _) => (SpirvImageFormat.Rgba16f, ImageComponentKind.Float),
(13 or 14, 4) => (
SpirvImageFormat.Rgba32ui,
ImageComponentKind.Uint),
(13 or 14, 5) => (
SpirvImageFormat.Rgba32i,
ImageComponentKind.Sint),
(13 or 14, _) => (
SpirvImageFormat.Rgba32f,
ImageComponentKind.Float),
(20, _) => (SpirvImageFormat.R32ui, ImageComponentKind.Uint),
(22, _) => (SpirvImageFormat.Rgba16f, ImageComponentKind.Float),
(29, _) => (SpirvImageFormat.R32f, ImageComponentKind.Float),
(36, _) => (SpirvImageFormat.R8, ImageComponentKind.Float),
(49, _) => (SpirvImageFormat.R8ui, ImageComponentKind.Uint),
(56 or 62 or 64, _) => (
SpirvImageFormat.Rgba8,
ImageComponentKind.Float),
(71, _) => (SpirvImageFormat.Rgba16f, ImageComponentKind.Float),
(75, _) => (SpirvImageFormat.Rg32f, ImageComponentKind.Float),
(_, 4) => (SpirvImageFormat.Unknown, ImageComponentKind.Uint),
(_, 5) => (SpirvImageFormat.Unknown, ImageComponentKind.Sint),
_ => (SpirvImageFormat.Unknown, ImageComponentKind.Float),
};
}
private void DeclareStageInterface()
{
if (UsesSubgroupOperations())
{
var subgroupPointer =
_module.TypePointer(SpirvStorageClass.Input, _uintType);
_subgroupInvocationIdInput = _module.AddGlobalVariable(
subgroupPointer,
SpirvStorageClass.Input);
_module.AddDecoration(
_subgroupInvocationIdInput,
SpirvDecoration.BuiltIn,
(uint)SpirvBuiltIn.SubgroupLocalInvocationId);
_interfaces.Add(_subgroupInvocationIdInput);
}
if (_stage == Gen5SpirvStage.Vertex)
{
DeclareVertexInputs();
var inputPointer =
_module.TypePointer(SpirvStorageClass.Input, _uintType);
_vertexIndexInput = _module.AddGlobalVariable(
inputPointer,
SpirvStorageClass.Input);
_module.AddDecoration(
_vertexIndexInput,
SpirvDecoration.BuiltIn,
(uint)SpirvBuiltIn.VertexIndex);
_interfaces.Add(_vertexIndexInput);
_instanceIndexInput = _module.AddGlobalVariable(
inputPointer,
SpirvStorageClass.Input);
_module.AddDecoration(
_instanceIndexInput,
SpirvDecoration.BuiltIn,
(uint)SpirvBuiltIn.InstanceIndex);
_interfaces.Add(_instanceIndexInput);
var outputPointer =
_module.TypePointer(SpirvStorageClass.Output, _vec4Type);
_positionOutput = _module.AddGlobalVariable(
outputPointer,
SpirvStorageClass.Output);
_module.AddDecoration(
_positionOutput,
SpirvDecoration.BuiltIn,
(uint)SpirvBuiltIn.Position);
_interfaces.Add(_positionOutput);
var parameters = _state.Program.Instructions
.Select(instruction => instruction.Control)
.OfType<Gen5ExportControl>()
.Where(export => export.Target is >= 32 and < 64)
.Select(export => export.Target - 32)
.Distinct()
.Order()
.ToArray();
foreach (var parameter in parameters)
{
var variable = _module.AddGlobalVariable(
outputPointer,
SpirvStorageClass.Output);
_module.AddDecoration(variable, SpirvDecoration.Location, parameter);
_vertexOutputs.Add(parameter, variable);
_interfaces.Add(variable);
}
}
else if (_stage == Gen5SpirvStage.Pixel)
{
var inputVec4Pointer =
_module.TypePointer(SpirvStorageClass.Input, _vec4Type);
var attributes = _state.Program.Instructions
.Select(instruction => instruction.Control)
.OfType<Gen5InterpolationControl>()
.Select(control => control.Attribute)
.Distinct()
.Order()
.ToArray();
foreach (var attribute in attributes)
{
var variable = _module.AddGlobalVariable(
inputVec4Pointer,
SpirvStorageClass.Input);
_module.AddDecoration(variable, SpirvDecoration.Location, attribute);
_pixelInputs.Add(attribute, variable);
_interfaces.Add(variable);
}
_fragCoordInput = _module.AddGlobalVariable(
inputVec4Pointer,
SpirvStorageClass.Input);
_module.AddDecoration(
_fragCoordInput,
SpirvDecoration.BuiltIn,
(uint)SpirvBuiltIn.FragCoord);
_interfaces.Add(_fragCoordInput);
var outputType = _outputKind switch
{
Gen5PixelOutputKind.Uint => _uvec4Type,
Gen5PixelOutputKind.Sint => _module.TypeVector(_intType, 4),
_ => _vec4Type,
};
var outputPointer =
_module.TypePointer(SpirvStorageClass.Output, outputType);
_pixelOutput = _module.AddGlobalVariable(
outputPointer,
SpirvStorageClass.Output);
_module.AddDecoration(_pixelOutput, SpirvDecoration.Location, 0);
_interfaces.Add(_pixelOutput);
}
else
{
var inputPointer =
_module.TypePointer(SpirvStorageClass.Input, _uvec3Type);
_localInvocationIdInput = _module.AddGlobalVariable(
inputPointer,
SpirvStorageClass.Input);
_module.AddDecoration(
_localInvocationIdInput,
SpirvDecoration.BuiltIn,
(uint)SpirvBuiltIn.LocalInvocationId);
_workGroupIdInput = _module.AddGlobalVariable(
inputPointer,
SpirvStorageClass.Input);
_module.AddDecoration(
_workGroupIdInput,
SpirvDecoration.BuiltIn,
(uint)SpirvBuiltIn.WorkgroupId);
_interfaces.Add(_localInvocationIdInput);
_interfaces.Add(_workGroupIdInput);
}
}
private void DeclareVertexInputs()
{
foreach (var input in _evaluation.VertexInputs ?? [])
{
var type = input.ComponentCount switch
{
1u => _floatType,
2u => _vec2Type,
3u => _vec3Type,
4u => _vec4Type,
_ => 0u,
};
if (type == 0)
{
continue;
}
var pointer = _module.TypePointer(SpirvStorageClass.Input, type);
var variable = _module.AddGlobalVariable(
pointer,
SpirvStorageClass.Input);
_module.AddName(variable, $"attr{input.Location}");
_module.AddDecoration(
variable,
SpirvDecoration.Location,
input.Location);
_vertexInputsByPc.TryAdd(
input.Pc,
new SpirvVertexInput(
variable,
type,
input.ComponentCount));
_interfaces.Add(variable);
}
}
private void EmitInitialState()
{
for (uint index = 0;
index < _evaluation.InitialScalarRegisters.Count &&
index < ScalarRegisterCount;
index++)
{
var value = _evaluation.InitialScalarRegisters[(int)index];
if (value != 0)
{
StoreS(index, UInt(value));
}
}
Store(_scc, _module.ConstantBool(false));
if (_subgroupInvocationIdInput != 0)
{
StoreWaveMask(106, _module.ConstantBool(false));
StoreWaveMask(126, _module.ConstantBool(true));
}
else
{
Store(_vcc, _module.ConstantBool(false));
Store(_exec, _module.ConstantBool(true));
}
Store(_programCounter, UInt(0));
Store(_programActive, _module.ConstantBool(true));
if (_stage == Gen5SpirvStage.Vertex)
{
StoreV(5, Load(_uintType, _vertexIndexInput), guardWithExec: false);
StoreV(8, Load(_uintType, _instanceIndexInput), guardWithExec: false);
}
else if (_stage == Gen5SpirvStage.Pixel)
{
var fragCoord = Load(_vec4Type, _fragCoordInput);
var x = _module.AddInstruction(
SpirvOp.CompositeExtract,
_floatType,
fragCoord,
0);
var y = _module.AddInstruction(
SpirvOp.CompositeExtract,
_floatType,
fragCoord,
1);
StoreV(2, Bitcast(_uintType, x), guardWithExec: false);
StoreV(3, Bitcast(_uintType, y), guardWithExec: false);
Store(_pixelOutput, _module.ConstantNull(GetPixelOutputType()));
}
else
{
var localId = Load(_uvec3Type, _localInvocationIdInput);
for (uint component = 0; component < 3; component++)
{
var value = _module.AddInstruction(
SpirvOp.CompositeExtract,
_uintType,
localId,
component);
StoreV(component, value, guardWithExec: false);
}
if (_state.ComputeSystemRegisters is { } registers)
{
var workGroupId = Load(_uvec3Type, _workGroupIdInput);
StoreComputeSystemRegister(
registers.WorkGroupXRegister,
workGroupId,
0);
StoreComputeSystemRegister(
registers.WorkGroupYRegister,
workGroupId,
1);
StoreComputeSystemRegister(
registers.WorkGroupZRegister,
workGroupId,
2);
if (registers.ThreadGroupSizeRegister is { } sizeRegister)
{
StoreS(
sizeRegister,
UInt(checked(_localSizeX * _localSizeY * _localSizeZ)));
}
}
}
}
private void StoreComputeSystemRegister(
uint? register,
uint workGroupId,
uint component)
{
if (register is null)
{
return;
}
var value = _module.AddInstruction(
SpirvOp.CompositeExtract,
_uintType,
workGroupId,
component);
StoreS(register.Value, value);
}
private bool TryEmitBlock(
IReadOnlyList<ShaderBlock> blocks,
int blockIndex,
out string error)
{
error = string.Empty;
var block = blocks[blockIndex];
for (var index = block.StartIndex; index < block.EndIndex; index++)
{
var instruction = _state.Program.Instructions[index];
if (IsBranch(instruction.Opcode) || instruction.Opcode == "SEndpgm")
{
continue;
}
if (!TryEmitInstruction(instruction, out error))
{
error = $"pc=0x{instruction.Pc:X} {instruction.Opcode}: {error}";
return false;
}
}
var terminator = _state.Program.Instructions[block.EndIndex - 1];
if (terminator.Opcode == "SEndpgm")
{
Store(_programActive, _module.ConstantBool(false));
return true;
}
var fallthrough = blockIndex + 1 < blocks.Count
? (uint)(blockIndex + 1)
: uint.MaxValue;
if (terminator.Opcode == "SBranch")
{
if (!TryGetBranchTargetPc(terminator, out var targetPc))
{
error = "invalid scalar branch target";
return false;
}
if (IsExitBranchTarget(_state.Program.Instructions, targetPc))
{
Store(_programActive, _module.ConstantBool(false));
return true;
}
if (!TryFindBlock(blocks, targetPc, out var targetBlock))
{
error = $"invalid scalar branch target pc=0x{terminator.Pc:X} target=0x{targetPc:X} blocks={FormatBlockStarts(blocks)}";
return false;
}
Store(_programCounter, UInt((uint)targetBlock));
return true;
}
if (terminator.Opcode.StartsWith("SCbranch", StringComparison.Ordinal))
{
var hasTarget = TryGetBranchTargetPc(terminator, out var targetPc);
var targetBlock = -1;
var hasTargetBlock = hasTarget && TryFindBlock(blocks, targetPc, out targetBlock);
var targetExits = hasTarget && IsExitBranchTarget(_state.Program.Instructions, targetPc);
var hasCondition = TryGetBranchCondition(terminator.Opcode, out var condition);
if (!hasTarget || (!hasTargetBlock && !targetExits) || !hasCondition)
{
error =
$"invalid conditional scalar branch opcode={terminator.Opcode} " +
$"pc=0x{terminator.Pc:X} " +
$"target={(hasTarget ? $"0x{targetPc:X}" : "invalid")} " +
$"target_block={(hasTargetBlock ? targetBlock.ToString() : targetExits ? "exit" : "missing")} " +
$"fallthrough={(fallthrough == uint.MaxValue ? "end" : fallthrough.ToString())} " +
$"condition={hasCondition} " +
$"blocks={FormatBlockStarts(blocks)}";
return false;
}
var takenBlock = targetExits ? uint.MaxValue : (uint)targetBlock;
var selected = _module.AddInstruction(
SpirvOp.Select,
_uintType,
condition,
UInt(takenBlock),
UInt(fallthrough));
Store(_programCounter, selected);
return true;
}
if (fallthrough == uint.MaxValue)
{
Store(_programActive, _module.ConstantBool(false));
}
else
{
Store(_programCounter, UInt(fallthrough));
}
return true;
}
private static string FormatBlockStarts(IReadOnlyList<ShaderBlock> blocks)
{
const int maxBlocks = 32;
var count = Math.Min(blocks.Count, maxBlocks);
var starts = new string[count];
for (var index = 0; index < count; index++)
{
starts[index] = $"0x{blocks[index].StartPc:X}";
}
return blocks.Count <= maxBlocks
? string.Join(",", starts)
: string.Join(",", starts) + $",...({blocks.Count})";
}
private static bool IsExitBranchTarget(
IReadOnlyList<Gen5ShaderInstruction> instructions,
uint targetPc)
{
if (instructions.Count == 0)
{
return false;
}
var last = instructions[^1];
var lastEndPc = last.Pc + (uint)(last.Words.Count * sizeof(uint));
return targetPc >= lastEndPc;
}
private bool TryGetBranchCondition(string opcode, out uint condition)
{
condition = opcode switch
{
"SCbranchScc0" => LogicalNot(Load(_boolType, _scc)),
"SCbranchScc1" => Load(_boolType, _scc),
"SCbranchVccz" => LogicalNot(SubgroupAny(Load(_boolType, _vcc))),
"SCbranchVccnz" => SubgroupAny(Load(_boolType, _vcc)),
"SCbranchExecz" => LogicalNot(SubgroupAny(Load(_boolType, _exec))),
"SCbranchExecnz" => SubgroupAny(Load(_boolType, _exec)),
_ => 0,
};
return condition != 0;
}
private bool TryEmitInstruction(
Gen5ShaderInstruction instruction,
out string error)
{
error = string.Empty;
if (instruction.Opcode is
"SNop" or
"SWaitcnt" or
"SInstPrefetch" or
"STtraceData" or
"VInterpMovF32")
{
return true;
}
if (instruction.Opcode == "SBarrier")
{
var workgroup = UInt(2);
var semantics = UInt(0x108);
_module.AddStatement(
SpirvOp.ControlBarrier,
workgroup,
workgroup,
semantics);
return true;
}
if (instruction.Control is Gen5ScalarMemoryControl scalarMemory)
{
return TryEmitScalarMemory(instruction, scalarMemory, out error);
}
if (instruction.Control is Gen5InterpolationControl interpolation)
{
return TryEmitInterpolation(instruction, interpolation, out error);
}
if (instruction.Control is Gen5ImageControl image)
{
return TryEmitImage(instruction, image, out error);
}
if (instruction.Control is Gen5GlobalMemoryControl globalMemory)
{
return TryEmitGlobalMemory(instruction, globalMemory, out error);
}
if (instruction.Control is Gen5BufferMemoryControl bufferMemory)
{
return TryEmitBufferMemory(instruction, bufferMemory, out error);
}
if (instruction.Control is Gen5ExportControl export)
{
return TryEmitExport(instruction, export, out error);
}
if (instruction.Control is Gen5DataShareControl)
{
return TryEmitDataShare(instruction, out error);
}
if (instruction.Encoding is
Gen5ShaderEncoding.Sop1 or
Gen5ShaderEncoding.Sop2 or
Gen5ShaderEncoding.Sopc or
Gen5ShaderEncoding.Sopk)
{
return TryEmitScalarAlu(instruction, out error);
}
if (instruction.Encoding is
Gen5ShaderEncoding.Sopp or
Gen5ShaderEncoding.Smrd or
Gen5ShaderEncoding.Smem)
{
return true;
}
return TryEmitVectorAlu(instruction, out error);
}
private bool TryEmitDataShare(
Gen5ShaderInstruction instruction,
out string error)
{
error = string.Empty;
if (_stage != Gen5SpirvStage.Compute ||
_lds == 0 ||
_workgroupUintPointer == 0 ||
instruction.Control is not Gen5DataShareControl control)
{
error = "invalid LDS instruction";
return false;
}
if (control.Gds)
{
error = "GDS data share is not implemented";
return false;
}
switch (instruction.Opcode)
{
case "DsWriteB32":
{
if (instruction.Sources.Count < 2)
{
error = "missing LDS write source";
return false;
}
var address = GetRawSource(instruction, 0);
StoreLds(
LdsPointer(address, EffectiveDsOffsetBytes(control.Offset0)),
GetRawSource(instruction, 1));
return true;
}
case "DsWriteB64":
{
if (instruction.Sources.Count < 3)
{
error = "missing LDS write64 source";
return false;
}
var address = GetRawSource(instruction, 0);
var offset = EffectiveDsOffsetBytes(control.Offset0);
StoreLds(LdsPointer(address, offset), GetRawSource(instruction, 1));
StoreLds(
LdsPointer(address, offset + sizeof(uint)),
GetRawSource(instruction, 2));
return true;
}
case "DsWrite2B32":
case "DsWrite2St64B32":
{
if (instruction.Sources.Count < 3)
{
error = "missing LDS write2 source";
return false;
}
var st64 = instruction.Opcode == "DsWrite2St64B32";
var address = GetRawSource(instruction, 0);
StoreLds(
LdsPointer(
address,
EffectiveDsOffsetBytes(control.Offset0, st64)),
GetRawSource(instruction, 1));
StoreLds(
LdsPointer(
address,
EffectiveDsOffsetBytes(control.Offset1, st64)),
GetRawSource(instruction, 2));
return true;
}
case "DsReadB32":
{
if (instruction.Destinations.Count < 1 ||
instruction.Sources.Count < 1)
{
error = "missing LDS read operand";
return false;
}
var address = GetRawSource(instruction, 0);
var value = Load(
_uintType,
LdsPointer(address, EffectiveDsOffsetBytes(control.Offset0)));
StoreV(instruction.Destinations[0].Value, value);
return true;
}
case "DsRead2B32":
case "DsRead2St64B32":
{
if (instruction.Destinations.Count < 2 ||
instruction.Sources.Count < 1)
{
error = "missing LDS read2 operand";
return false;
}
var st64 = instruction.Opcode == "DsRead2St64B32";
var address = GetRawSource(instruction, 0);
var first = Load(
_uintType,
LdsPointer(
address,
EffectiveDsOffsetBytes(control.Offset0, st64)));
var second = Load(
_uintType,
LdsPointer(
address,
EffectiveDsOffsetBytes(control.Offset1, st64)));
StoreV(instruction.Destinations[0].Value, first);
StoreV(instruction.Destinations[1].Value, second);
return true;
}
default:
error = $"unsupported LDS opcode {instruction.Opcode}";
return false;
}
}
private static uint EffectiveDsOffsetBytes(uint offset, bool st64 = false) =>
offset * (st64 ? 256u : sizeof(uint));
private uint LdsPointer(uint address, uint offsetBytes)
{
var addressWithOffset = offsetBytes == 0
? address
: IAdd(address, UInt(offsetBytes));
var index = ShiftRightLogical(addressWithOffset, UInt(2));
return _module.AddInstruction(
SpirvOp.AccessChain,
_workgroupUintPointer,
_lds,
index);
}
private void StoreLds(uint pointer, uint value)
{
var active = Load(_boolType, _exec);
var oldValue = Load(_uintType, pointer);
var selected = _module.AddInstruction(
SpirvOp.Select,
_uintType,
active,
value,
oldValue);
Store(pointer, selected);
}
private bool TryEmitInterpolation(
Gen5ShaderInstruction instruction,
Gen5InterpolationControl interpolation,
out string error)
{
error = string.Empty;
if (_stage != Gen5SpirvStage.Pixel ||
!_pixelInputs.TryGetValue(interpolation.Attribute, out var input) ||
!TryGetVectorDestination(instruction, out var destination))
{
error = "invalid interpolated attribute";
return false;
}
var vector = Load(_vec4Type, input);
var component = _module.AddInstruction(
SpirvOp.CompositeExtract,
_floatType,
vector,
interpolation.Channel);
StoreV(destination, Bitcast(_uintType, component));
return true;
}
private bool TryEmitScalarMemory(
Gen5ShaderInstruction instruction,
Gen5ScalarMemoryControl control,
out string error)
{
error = string.Empty;
if (!_bufferBindingByPc.TryGetValue(instruction.Pc, out var bindingIndex))
{
foreach (var destination in instruction.Destinations)
{
if (destination.Kind == Gen5OperandKind.ScalarRegister)
{
StoreS(destination.Value, UInt(0));
}
}
return true;
}
var dynamicOffset = control.DynamicOffsetRegister is { } register
? LoadS(register)
: UInt(0);
var byteAddress = IAdd(
dynamicOffset,
UInt(unchecked((uint)control.ImmediateOffsetBytes)));
var dwordAddress = ShiftRightLogical(byteAddress, UInt(2));
for (var index = 0; index < instruction.Destinations.Count; index++)
{
var destination = instruction.Destinations[index];
if (destination.Kind != Gen5OperandKind.ScalarRegister)
{
error = "invalid scalar-memory destination";
return false;
}
var address = index == 0
? dwordAddress
: IAdd(dwordAddress, UInt((uint)index));
StoreS(destination.Value, LoadBufferWord(bindingIndex, address));
}
return true;
}
private bool TryEmitGlobalMemory(
Gen5ShaderInstruction instruction,
Gen5GlobalMemoryControl control,
out string error)
{
error = string.Empty;
if (!_bufferBindingByPc.TryGetValue(instruction.Pc, out var bindingIndex))
{
error = "missing global-memory binding";
return false;
}
var byteAddress = IAdd(
LoadV(control.VectorAddress),
UInt(unchecked((uint)control.OffsetBytes)));
var dwordAddress = ShiftRightLogical(byteAddress, UInt(2));
for (uint index = 0; index < control.DwordCount; index++)
{
var address = index == 0
? dwordAddress
: IAdd(dwordAddress, UInt(index));
StoreV(
control.VectorData + index,
LoadBufferWord(bindingIndex, address));
}
return true;
}
private bool TryEmitBufferMemory(
Gen5ShaderInstruction instruction,
Gen5BufferMemoryControl control,
out string error)
{
error = string.Empty;
if (_stage == Gen5SpirvStage.Vertex &&
_vertexInputsByPc.TryGetValue(instruction.Pc, out var vertexInput))
{
return TryEmitVertexInputFetch(control, vertexInput, out error);
}
if (_stage == Gen5SpirvStage.Vertex &&
IsFormatBufferLoad(instruction.Opcode))
{
error = $"missing vertex input for {instruction.Opcode} pc=0x{instruction.Pc:X}";
return false;
}
if (!_bufferBindingByPc.TryGetValue(instruction.Pc, out var bindingIndex))
{
error = "missing buffer-memory binding";
return false;
}
var scalarOffset = instruction.Sources.Count > 2
? GetRawSource(instruction, 2)
: UInt(0);
var stride = ShiftRightLogical(LoadS(control.ScalarResource + 1), UInt(16));
stride = BitwiseAnd(stride, UInt(0x3FFF));
var vectorIndex = control.IndexEnabled
? LoadV(control.VectorAddress)
: UInt(0);
var vectorOffset = control.OffsetEnabled
? LoadV(control.VectorAddress + (control.IndexEnabled ? 1u : 0u))
: UInt(0);
var byteAddress = IAdd(
UInt(unchecked((uint)control.OffsetBytes)),
scalarOffset);
byteAddress = IAdd(byteAddress, vectorOffset);
byteAddress = IAdd(
byteAddress,
_module.AddInstruction(SpirvOp.IMul, _uintType, vectorIndex, stride));
var dwordAddress = ShiftRightLogical(byteAddress, UInt(2));
if (instruction.Opcode == "BufferAtomicAdd")
{
EmitExecConditional(() =>
{
var original = _module.AddInstruction(
SpirvOp.AtomicIAdd,
_uintType,
BufferWordPointer(bindingIndex, dwordAddress),
UInt(1),
UInt(0x48),
LoadV(control.VectorData));
if (control.Glc)
{
StoreV(control.VectorData, original);
}
});
return true;
}
if (instruction.Opcode.StartsWith("BufferStoreDword", StringComparison.Ordinal))
{
EmitExecConditional(() =>
{
for (uint index = 0; index < control.DwordCount; index++)
{
var address = index == 0
? dwordAddress
: IAdd(dwordAddress, UInt(index));
StoreBufferWord(
bindingIndex,
address,
LoadV(control.VectorData + index));
}
});
return true;
}
if (!instruction.Opcode.StartsWith("BufferLoad", StringComparison.Ordinal) &&
!instruction.Opcode.StartsWith("TBufferLoad", StringComparison.Ordinal))
{
error = $"unsupported buffer opcode {instruction.Opcode}";
return false;
}
for (uint index = 0; index < control.DwordCount; index++)
{
var address = index == 0
? dwordAddress
: IAdd(dwordAddress, UInt(index));
StoreV(
control.VectorData + index,
LoadBufferWord(bindingIndex, address));
}
return true;
}
private static bool IsFormatBufferLoad(string opcode) =>
opcode.StartsWith("BufferLoadFormat", StringComparison.Ordinal) ||
opcode.StartsWith("TBufferLoadFormat", StringComparison.Ordinal);
private bool TryEmitVertexInputFetch(
Gen5BufferMemoryControl control,
SpirvVertexInput input,
out string error)
{
error = string.Empty;
if (control.DwordCount == 0 ||
control.DwordCount > input.ComponentCount)
{
error =
$"invalid vertex input fetch components={control.DwordCount} " +
$"input={input.ComponentCount}";
return false;
}
var loaded = Load(input.Type, input.Variable);
for (uint component = 0; component < control.DwordCount; component++)
{
var value = input.ComponentCount == 1
? loaded
: _module.AddInstruction(
SpirvOp.CompositeExtract,
_floatType,
loaded,
component);
StoreV(control.VectorData + component, Bitcast(_uintType, value));
}
return true;
}
private bool TryEmitImage(
Gen5ShaderInstruction instruction,
Gen5ImageControl image,
out string error)
{
error = string.Empty;
if (!_imageBindingByPc.TryGetValue(instruction.Pc, out var bindingIndex) ||
bindingIndex >= _imageResources.Count)
{
error = "unresolved image binding";
return false;
}
var resource = _imageResources[bindingIndex];
var imageObject = Load(resource.ObjectType, resource.Variable);
if (instruction.Opcode == "ImageGetResinfo")
{
var queryImage = resource.IsStorage
? imageObject
: _module.AddInstruction(
SpirvOp.Image,
resource.ImageType,
imageObject);
var size = _module.AddInstruction(
resource.IsStorage
? SpirvOp.ImageQuerySize
: SpirvOp.ImageQuerySizeLod,
_module.TypeVector(_intType, 2),
resource.IsStorage
? [queryImage]
: [queryImage, UInt(0)]);
uint outputIndex = 0;
for (uint component = 0; component < 4; component++)
{
if ((image.Dmask & (1u << (int)component)) == 0)
{
continue;
}
uint value;
if (component < 2)
{
var signedValue = _module.AddInstruction(
SpirvOp.CompositeExtract,
_intType,
size,
component);
value = Bitcast(_uintType, signedValue);
}
else
{
value = UInt(1);
}
StoreV(image.VectorData + outputIndex++, value);
}
return true;
}
if (instruction.Opcode is "ImageStore" or "ImageStoreMip")
{
if (!resource.IsStorage)
{
error = "image store is not bound as storage";
return false;
}
var coordinates = BuildIntegerCoordinates(image, 0);
var components = new uint[4];
uint sourceIndex = 0;
for (var component = 0; component < components.Length; component++)
{
if ((image.Dmask & (1u << component)) != 0)
{
var raw = LoadV(image.VectorData + sourceIndex++);
components[component] = resource.ComponentKind switch
{
ImageComponentKind.Sint => Bitcast(_intType, raw),
ImageComponentKind.Uint => raw,
_ => Bitcast(_floatType, raw),
};
}
else
{
components[component] = resource.ComponentKind switch
{
ImageComponentKind.Sint =>
_module.Constant(_intType, 0),
ImageComponentKind.Uint => UInt(0),
_ => Float(0),
};
}
}
var texel = _module.AddInstruction(
SpirvOp.CompositeConstruct,
resource.VectorType,
components);
if (TryGetImageBounds(
_evaluation.ImageBindings[bindingIndex].ResourceDescriptor,
out var width,
out var height))
{
EmitBoundsCheckedImageWrite(
coordinates,
width,
height,
imageObject,
texel);
}
else
{
EmitExecConditional(
() => _module.AddStatement(
SpirvOp.ImageWrite,
imageObject,
coordinates,
texel));
}
return true;
}
if (resource.IsStorage)
{
error = $"unsupported storage image opcode {instruction.Opcode}";
return false;
}
uint sampled;
var writeAllComponents = false;
if (instruction.Opcode is "ImageLoad" or "ImageLoadMip")
{
var coordinates = TryGetImageBounds(
_evaluation.ImageBindings[bindingIndex].ResourceDescriptor,
out var width,
out var height)
? BuildClampedIntegerCoordinates(image, 0, width, height)
: BuildIntegerCoordinates(image, 0);
var mipLevel = _evaluation.ImageBindings[bindingIndex].MipLevel ?? 0;
var fetchedImage = _module.AddInstruction(
SpirvOp.Image,
resource.ImageType,
imageObject);
sampled = _module.AddInstruction(
SpirvOp.ImageFetch,
resource.VectorType,
fetchedImage,
coordinates,
2,
UInt(mipLevel));
}
else if (instruction.Opcode.StartsWith(
"ImageSample",
StringComparison.Ordinal))
{
var hasOffset =
instruction.Opcode.EndsWith("O", StringComparison.Ordinal);
var hasCompare =
instruction.Opcode.Contains("SampleC", StringComparison.Ordinal);
var start = (hasOffset ? 1 : 0) + (hasCompare ? 1 : 0);
var coordinates = BuildFloatCoordinates(image, start);
var explicitLod =
instruction.Opcode.Contains("Lz", StringComparison.Ordinal) ||
instruction.Opcode.Contains("SampleL", StringComparison.Ordinal);
var lod = instruction.Opcode.Contains("Lz", StringComparison.Ordinal)
? Float(0)
: Bitcast(
_floatType,
LoadV(image.GetAddressRegister(start + 2)));
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
var imageOperands =
(explicitLod ? 2u : 0u) | (hasOffset ? 0x10u : 0u);
var reference = hasCompare
? Bitcast(_floatType, LoadV(image.GetAddressRegister(hasOffset ? 1 : 0)))
: 0u;
var operands = new List<uint>
{
imageObject,
coordinates,
};
if (imageOperands != 0)
{
operands.Add(imageOperands);
if (explicitLod)
{
operands.Add(lod);
}
if (hasOffset)
{
operands.Add(offset);
}
}
sampled = _module.AddInstruction(
explicitLod
? SpirvOp.ImageSampleExplicitLod
: SpirvOp.ImageSampleImplicitLod,
resource.VectorType,
[.. operands]);
if (hasCompare)
{
sampled = EmitManualDepthCompare(resource, sampled, reference);
}
}
else if (instruction.Opcode.StartsWith(
"ImageGather4",
StringComparison.Ordinal))
{
var hasOffset =
instruction.Opcode.EndsWith("O", StringComparison.Ordinal);
var hasCompare =
instruction.Opcode.Contains("Gather4C", StringComparison.Ordinal);
var start = (hasOffset ? 1 : 0) + (hasCompare ? 1 : 0);
var coordinates = BuildFloatCoordinates(image, start);
var offset = hasOffset ? BuildImageOffset(image, 0) : 0u;
var reference = hasCompare
? Bitcast(_floatType, LoadV(image.GetAddressRegister(hasOffset ? 1 : 0)))
: 0u;
var operands = new List<uint>
{
imageObject,
coordinates,
};
if (hasCompare)
{
operands.Add(UInt(0));
}
else
{
uint component = 0;
while (component < 3 &&
(image.Dmask & (1u << (int)component)) == 0)
{
component++;
}
operands.Add(UInt(component));
}
if (hasOffset)
{
operands.Add(0x10u);
operands.Add(offset);
}
sampled = _module.AddInstruction(
SpirvOp.ImageGather,
resource.VectorType,
[.. operands]);
if (hasCompare)
{
var compared = new uint[4];
for (var component = 0u; component < 4; component++)
{
var texel = _module.AddInstruction(
SpirvOp.CompositeExtract,
resource.ComponentType,
sampled,
component);
compared[component] = EmitDepthCompareScalar(resource, texel, reference);
}
sampled = _module.AddInstruction(
SpirvOp.CompositeConstruct,
resource.VectorType,
compared);
}
writeAllComponents = true;
}
else
{
error = $"unsupported image opcode {instruction.Opcode}";
return false;
}
uint output = 0;
for (uint component = 0; component < 4; component++)
{
if (!writeAllComponents &&
(image.Dmask & (1u << (int)component)) == 0)
{
continue;
}
var value = _module.AddInstruction(
SpirvOp.CompositeExtract,
resource.ComponentType,
sampled,
component);
var raw = resource.ComponentKind switch
{
ImageComponentKind.Uint => value,
_ => Bitcast(_uintType, value),
};
StoreV(image.VectorData + output++, raw);
}
return true;
}
private uint EmitDepthCompareScalar(
SpirvImageResource resource,
uint texel,
uint reference)
{
var texelAsFloat = resource.ComponentKind switch
{
ImageComponentKind.Uint => _module.AddInstruction(
SpirvOp.ConvertUToF, _floatType, texel),
ImageComponentKind.Sint => _module.AddInstruction(
SpirvOp.ConvertSToF, _floatType, texel),
_ => texel,
};
var passes = _module.AddInstruction(
SpirvOp.FOrdLessThanEqual,
_boolType,
reference,
texelAsFloat);
return _module.AddInstruction(
SpirvOp.Select,
resource.ComponentType,
passes,
resource.ComponentKind switch
{
ImageComponentKind.Uint => UInt(1),
ImageComponentKind.Sint => _module.Constant(_intType, 1),
_ => Float(1),
},
resource.ComponentKind switch
{
ImageComponentKind.Uint => UInt(0),
ImageComponentKind.Sint => _module.Constant(_intType, 0),
_ => Float(0),
});
}
private uint EmitManualDepthCompare(
SpirvImageResource resource,
uint sampledVector,
uint reference)
{
var texel = _module.AddInstruction(
SpirvOp.CompositeExtract,
resource.ComponentType,
sampledVector,
0u);
var scalar = EmitDepthCompareScalar(resource, texel, reference);
return _module.AddInstruction(
SpirvOp.CompositeConstruct,
resource.VectorType,
scalar,
scalar,
scalar,
resource.ComponentKind switch
{
ImageComponentKind.Uint => UInt(1),
ImageComponentKind.Sint => _module.Constant(_intType, 1),
_ => Float(1),
});
}
private uint BuildFloatCoordinates(Gen5ImageControl image, int start)
{
var x = Bitcast(
_floatType,
LoadV(image.GetAddressRegister(start)));
var y = Bitcast(
_floatType,
LoadV(image.GetAddressRegister(start + 1)));
return _module.AddInstruction(
SpirvOp.CompositeConstruct,
_vec2Type,
x,
y);
}
private uint BuildIntegerCoordinates(Gen5ImageControl image, int start)
{
var ivec2 = _module.TypeVector(_intType, 2);
var x = Bitcast(
_intType,
LoadV(image.GetAddressRegister(start)));
var y = Bitcast(
_intType,
LoadV(image.GetAddressRegister(start + 1)));
return _module.AddInstruction(
SpirvOp.CompositeConstruct,
ivec2,
x,
y);
}
private uint BuildClampedIntegerCoordinates(
Gen5ImageControl image,
int start,
uint width,
uint height)
{
var ivec2 = _module.TypeVector(_intType, 2);
var x = ClampSignedCoordinate(
Bitcast(
_intType,
LoadV(image.GetAddressRegister(start))),
width);
var y = ClampSignedCoordinate(
Bitcast(
_intType,
LoadV(image.GetAddressRegister(start + 1))),
height);
return _module.AddInstruction(
SpirvOp.CompositeConstruct,
ivec2,
x,
y);
}
private uint ClampSignedCoordinate(uint value, uint extent)
{
var zero = _module.Constant(_intType, 0);
var max = _module.Constant(_intType, Math.Max(extent, 1) - 1);
var belowZero = _module.AddInstruction(
SpirvOp.SLessThan,
_boolType,
value,
zero);
var atLeastZero = _module.AddInstruction(
SpirvOp.Select,
_intType,
belowZero,
zero,
value);
var aboveMax = _module.AddInstruction(
SpirvOp.SGreaterThan,
_boolType,
atLeastZero,
max);
return _module.AddInstruction(
SpirvOp.Select,
_intType,
aboveMax,
max,
atLeastZero);
}
private void EmitBoundsCheckedImageWrite(
uint coordinates,
uint width,
uint height,
uint imageObject,
uint texel)
{
var x = _module.AddInstruction(
SpirvOp.CompositeExtract,
_intType,
coordinates,
0);
var y = _module.AddInstruction(
SpirvOp.CompositeExtract,
_intType,
coordinates,
1);
var zero = _module.Constant(_intType, 0);
var xNonNegative = _module.AddInstruction(
SpirvOp.SGreaterThanEqual,
_boolType,
x,
zero);
var yNonNegative = _module.AddInstruction(
SpirvOp.SGreaterThanEqual,
_boolType,
y,
zero);
var xInRange = _module.AddInstruction(
SpirvOp.SLessThan,
_boolType,
x,
_module.Constant(_intType, width));
var yInRange = _module.AddInstruction(
SpirvOp.SLessThan,
_boolType,
y,
_module.Constant(_intType, height));
var lowerInRange = _module.AddInstruction(
SpirvOp.LogicalAnd,
_boolType,
xNonNegative,
yNonNegative);
var upperInRange = _module.AddInstruction(
SpirvOp.LogicalAnd,
_boolType,
xInRange,
yInRange);
var inRange = _module.AddInstruction(
SpirvOp.LogicalAnd,
_boolType,
lowerInRange,
upperInRange);
inRange = _module.AddInstruction(
SpirvOp.LogicalAnd,
_boolType,
Load(_boolType, _exec),
inRange);
var writeLabel = _module.AllocateId();
var mergeLabel = _module.AllocateId();
_module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0);
_module.AddStatement(
SpirvOp.BranchConditional,
inRange,
writeLabel,
mergeLabel);
_module.AddLabel(writeLabel);
_module.AddStatement(
SpirvOp.ImageWrite,
imageObject,
coordinates,
texel);
_module.AddStatement(SpirvOp.Branch, mergeLabel);
_module.AddLabel(mergeLabel);
}
private static bool TryGetImageBounds(
IReadOnlyList<uint> descriptor,
out uint width,
out uint height)
{
width = 0;
height = 0;
if (descriptor.Count < 3)
{
return false;
}
width = (((descriptor[1] >> 30) & 0x3u) |
((descriptor[2] & 0xFFFu) << 2)) + 1;
height = ((descriptor[2] >> 14) & 0x3FFFu) + 1;
return width != 0 && height != 0 && width <= 16384 && height <= 16384;
}
private uint BuildImageOffset(Gen5ImageControl image, int component)
{
var ivec2 = _module.TypeVector(_intType, 2);
var packed = Bitcast(
_intType,
LoadV(image.GetAddressRegister(component)));
var x = _module.AddInstruction(
SpirvOp.BitFieldSExtract,
_intType,
packed,
UInt(0),
UInt(6));
var y = _module.AddInstruction(
SpirvOp.BitFieldSExtract,
_intType,
packed,
UInt(8),
UInt(6));
return _module.AddInstruction(
SpirvOp.CompositeConstruct,
ivec2,
x,
y);
}
private bool TryEmitExport(
Gen5ShaderInstruction instruction,
Gen5ExportControl export,
out string error)
{
error = string.Empty;
if (instruction.Sources.Count < 4)
{
error = "missing export sources";
return false;
}
if (_stage == Gen5SpirvStage.Pixel)
{
if (export.Target != 0)
{
return true;
}
var outputType = GetPixelOutputType();
var values = new uint[4];
for (var component = 0; component < 4; component++)
{
var enabled = (export.EnableMask & (1u << component)) != 0;
if (!enabled)
{
values[component] = _outputKind == Gen5PixelOutputKind.Sint
? Bitcast(_intType, UInt(0))
: UInt(0);
continue;
}
if (export.Compressed)
{
var value = LoadCompressedExportComponent(
instruction,
component);
values[component] = _outputKind switch
{
Gen5PixelOutputKind.Uint => _module.AddInstruction(
SpirvOp.ConvertFToU,
_uintType,
value),
Gen5PixelOutputKind.Sint => _module.AddInstruction(
SpirvOp.ConvertFToS,
_intType,
value),
_ => value,
};
continue;
}
var raw = LoadV(instruction.Sources[component].Value);
values[component] = _outputKind switch
{
Gen5PixelOutputKind.Uint => raw,
Gen5PixelOutputKind.Sint => Bitcast(_intType, raw),
_ => Bitcast(_floatType, raw),
};
}
var vector = _module.AddInstruction(
SpirvOp.CompositeConstruct,
outputType,
values);
vector = _module.AddInstruction(
SpirvOp.Select,
outputType,
Load(_boolType, _exec),
vector,
Load(outputType, _pixelOutput));
Store(_pixelOutput, vector);
return true;
}
if (_stage != Gen5SpirvStage.Vertex)
{
return true;
}
uint outputVariable;
if (export.Target is >= 12 and < 16)
{
if (export.Target != 12)
{
return true;
}
outputVariable = _positionOutput;
}
else if (export.Target is >= 32 and < 64 &&
_vertexOutputs.TryGetValue(export.Target - 32, out var parameter))
{
outputVariable = parameter;
}
else
{
return true;
}
var components = new uint[4];
for (var component = 0; component < 4; component++)
{
components[component] = (export.EnableMask & (1u << component)) != 0
? export.Compressed
? LoadCompressedExportComponent(instruction, component)
: Bitcast(
_floatType,
LoadV(instruction.Sources[component].Value))
: Float(component == 3 ? 1f : 0f);
}
var outputValue = _module.AddInstruction(
SpirvOp.CompositeConstruct,
_vec4Type,
components);
outputValue = _module.AddInstruction(
SpirvOp.Select,
_vec4Type,
Load(_boolType, _exec),
outputValue,
Load(_vec4Type, outputVariable));
Store(outputVariable, outputValue);
return true;
}
private uint LoadCompressedExportComponent(
Gen5ShaderInstruction instruction,
int component)
{
var packed = LoadV(instruction.Sources[component >> 1].Value);
var unpacked = Ext(62, _vec2Type, packed);
return _module.AddInstruction(
SpirvOp.CompositeExtract,
_floatType,
unpacked,
(uint)(component & 1));
}
private uint GetPixelOutputType() =>
_outputKind switch
{
Gen5PixelOutputKind.Uint => _uvec4Type,
Gen5PixelOutputKind.Sint => _module.TypeVector(_intType, 4),
_ => _vec4Type,
};
private uint LoadBufferWord(int binding, uint dwordAddress)
{
var pointer = BufferWordPointer(binding, dwordAddress);
return Load(_uintType, pointer);
}
private void StoreBufferWord(int binding, uint dwordAddress, uint value)
{
var pointer = BufferWordPointer(binding, dwordAddress);
Store(pointer, value);
}
private uint BufferWordPointer(int binding, uint dwordAddress) =>
_module.AddInstruction(
SpirvOp.AccessChain,
_storageUintPointer,
_globalBuffers,
UInt((uint)binding),
UInt(0),
dwordAddress);
private uint ScalarPointer(uint register) =>
_module.AddInstruction(
SpirvOp.AccessChain,
_privateUintPointer,
_scalarRegisters,
UInt(register));
private uint VectorPointer(uint register) =>
_module.AddInstruction(
SpirvOp.AccessChain,
_privateUintPointer,
_vectorRegisters,
UInt(register));
private uint LoadS(uint register) => Load(_uintType, ScalarPointer(register));
private uint LoadV(uint register) => Load(_uintType, VectorPointer(register));
private void StoreS(uint register, uint value)
{
Store(ScalarPointer(register), value);
if (register is 106 or 107)
{
Store(_vcc, IsWaveMaskActive(LoadS64(106)));
}
else if (register is 126 or 127)
{
Store(_exec, IsWaveMaskActive(LoadS64(126)));
}
}
private void StoreV(uint register, uint value, bool guardWithExec = true)
{
if (guardWithExec)
{
var active = Load(_boolType, _exec);
var oldValue = LoadV(register);
value = _module.AddInstruction(
SpirvOp.Select,
_uintType,
active,
value,
oldValue);
}
Store(VectorPointer(register), value);
}
private uint Load(uint type, uint pointer) =>
_module.AddInstruction(SpirvOp.Load, type, pointer);
private void Store(uint pointer, uint value) =>
_module.AddStatement(SpirvOp.Store, pointer, value);
private uint UInt(uint value) => _module.Constant(_uintType, value);
private uint Float(float value) => _module.ConstantFloat(_floatType, value);
private uint Bitcast(uint type, uint value) =>
_module.AddInstruction(SpirvOp.Bitcast, type, value);
private uint IAdd(uint left, uint right) =>
_module.AddInstruction(SpirvOp.IAdd, _uintType, left, right);
private uint ShiftLeftLogical(uint left, uint right) =>
_module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_uintType,
left,
BitwiseAnd(right, UInt(31)));
private uint ShiftRightLogical(uint left, uint right) =>
_module.AddInstruction(
SpirvOp.ShiftRightLogical,
_uintType,
left,
BitwiseAnd(right, UInt(31)));
private uint ShiftRightArithmetic(uint left, uint right) =>
Bitcast(
_uintType,
_module.AddInstruction(
SpirvOp.ShiftRightArithmetic,
_intType,
Bitcast(_intType, left),
BitwiseAnd(right, UInt(31))));
private uint ShiftLeftLogical64(uint left, uint right) =>
_module.AddInstruction(
SpirvOp.ShiftLeftLogical,
_ulongType,
left,
BitwiseAnd64(right, _module.Constant64(_ulongType, 63)));
private uint ShiftRightLogical64(uint left, uint right) =>
_module.AddInstruction(
SpirvOp.ShiftRightLogical,
_ulongType,
left,
BitwiseAnd64(right, _module.Constant64(_ulongType, 63)));
private uint BitwiseAnd(uint left, uint right) =>
_module.AddInstruction(SpirvOp.BitwiseAnd, _uintType, left, right);
private uint BitwiseAnd64(uint left, uint right) =>
_module.AddInstruction(SpirvOp.BitwiseAnd, _ulongType, left, right);
private uint BitwiseOr(uint left, uint right) =>
_module.AddInstruction(SpirvOp.BitwiseOr, _uintType, left, right);
private uint BitwiseXor(uint left, uint right) =>
_module.AddInstruction(SpirvOp.BitwiseXor, _uintType, left, right);
private uint LogicalNot(uint value) =>
_module.AddInstruction(SpirvOp.LogicalNot, _boolType, value);
private uint SubgroupAny(uint condition) =>
_subgroupInvocationIdInput == 0
? condition
: _module.AddInstruction(
SpirvOp.GroupNonUniformAny,
_boolType,
UInt(3),
condition);
private uint CurrentLaneBit()
{
if (_subgroupInvocationIdInput == 0)
{
return _module.Constant64(_ulongType, 1);
}
var lane = Load(_uintType, _subgroupInvocationIdInput);
var maskedLane = BitwiseAnd(lane, UInt(RdnaWaveLaneCount - 1));
var shifted = ShiftLeftLogical64(
_module.Constant64(_ulongType, 1),
_module.AddInstruction(
SpirvOp.UConvert,
_ulongType,
maskedLane));
return _module.AddInstruction(
SpirvOp.Select,
_ulongType,
IsCurrentLaneInRdnaWave(),
shifted,
_module.Constant64(_ulongType, 0));
}
private uint IsCurrentLaneInRdnaWave() =>
_module.AddInstruction(
SpirvOp.ULessThan,
_boolType,
Load(_uintType, _subgroupInvocationIdInput),
UInt(RdnaWaveLaneCount));
private uint BooleanToLaneMask(uint condition) =>
_module.AddInstruction(
SpirvOp.Select,
_ulongType,
condition,
CurrentLaneBit(),
_module.Constant64(_ulongType, 0));
private uint IsWaveMaskActive(uint mask) =>
_subgroupInvocationIdInput == 0
? IsNotZero64(mask)
: IsCurrentLaneSet(mask);
private uint IsCurrentLaneSet(uint mask) =>
IsNotZero64(
_module.AddInstruction(
SpirvOp.BitwiseAnd,
_ulongType,
mask,
CurrentLaneBit()));
private void StoreWaveMask(uint register, uint condition) =>
StoreS64(register, BooleanToLaneMask(condition));
private void EmitExecConditional(Action emit)
{
var activeLabel = _module.AllocateId();
var mergeLabel = _module.AllocateId();
var active = Load(_boolType, _exec);
_module.AddStatement(SpirvOp.SelectionMerge, mergeLabel, 0);
_module.AddStatement(
SpirvOp.BranchConditional,
active,
activeLabel,
mergeLabel);
_module.AddLabel(activeLabel);
emit();
_module.AddStatement(SpirvOp.Branch, mergeLabel);
_module.AddLabel(mergeLabel);
}
private bool UsesLds() =>
_state.Program.Instructions.Any(instruction =>
instruction.Control is Gen5DataShareControl);
private bool UsesSubgroupShuffle() =>
_state.Program.Instructions.Any(instruction =>
instruction.Opcode is "VPermlane16B32" or "VPermlanex16B32");
private bool UsesWaveControl() =>
_state.Program.Instructions.Any(instruction =>
instruction.Opcode.Contains("Saveexec", StringComparison.Ordinal) ||
instruction.Opcode.StartsWith("SCbranchExec", StringComparison.Ordinal) ||
instruction.Opcode.StartsWith("SCbranchVcc", StringComparison.Ordinal) ||
instruction.Opcode.StartsWith("VCmpx", StringComparison.Ordinal) ||
instruction.Sources.Any(IsWaveMaskOperand) ||
instruction.Destinations.Any(IsWaveMaskOperand));
private bool UsesSubgroupOperations() =>
_stage == Gen5SpirvStage.Compute &&
(UsesSubgroupShuffle() || UsesWaveControl());
private static bool IsWaveMaskOperand(Gen5Operand operand) =>
operand.Kind == Gen5OperandKind.ScalarRegister &&
operand.Value is 106 or 107 or 126 or 127;
private static bool TryGetVectorDestination(
Gen5ShaderInstruction instruction,
out uint destination)
{
if (instruction.Destinations.Count != 0 &&
instruction.Destinations[0].Kind == Gen5OperandKind.VectorRegister)
{
destination = instruction.Destinations[0].Value;
return true;
}
destination = 0;
return false;
}
private static bool IsBranch(string opcode) =>
opcode == "SBranch" ||
opcode.StartsWith("SCbranch", StringComparison.Ordinal);
private static bool TryGetBranchTargetPc(
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 IReadOnlyList<ShaderBlock> BuildBasicBlocks(
IReadOnlyList<Gen5ShaderInstruction> instructions)
{
if (instructions.Count == 0)
{
return [];
}
var leaders = new SortedSet<uint> { instructions[0].Pc };
for (var index = 0; index < instructions.Count; index++)
{
var instruction = instructions[index];
if (IsBranch(instruction.Opcode) &&
TryGetBranchTargetPc(instruction, out var targetPc))
{
leaders.Add(targetPc);
}
if ((IsBranch(instruction.Opcode) || instruction.Opcode == "SEndpgm") &&
index + 1 < instructions.Count)
{
leaders.Add(instructions[index + 1].Pc);
}
}
var starts = leaders
.Where(pc => instructions.Any(instruction => instruction.Pc == pc))
.ToArray();
var blocks = new List<ShaderBlock>(starts.Length);
for (var index = 0; index < starts.Length; index++)
{
var startIndex = FindInstructionIndex(instructions, starts[index]);
var endIndex = index + 1 < starts.Length
? FindInstructionIndex(instructions, starts[index + 1])
: instructions.Count;
if (startIndex >= 0 && endIndex > startIndex)
{
blocks.Add(new ShaderBlock(starts[index], startIndex, endIndex));
}
}
return blocks;
}
private static int FindInstructionIndex(
IReadOnlyList<Gen5ShaderInstruction> instructions,
uint pc)
{
for (var index = 0; index < instructions.Count; index++)
{
if (instructions[index].Pc == pc)
{
return index;
}
}
return -1;
}
private static bool TryFindBlock(
IReadOnlyList<ShaderBlock> blocks,
uint pc,
out int block)
{
for (var index = 0; index < blocks.Count; index++)
{
if (blocks[index].StartPc == pc)
{
block = index;
return true;
}
}
block = -1;
return false;
}
private readonly record struct ShaderBlock(
uint StartPc,
int StartIndex,
int EndIndex);
}
}