// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using System.Buffers.Binary; using System.Text; namespace SharpEmu.Libs.Agc; internal enum SpirvOp : ushort { Nop = 0, Name = 5, Extension = 10, ExtInstImport = 11, ExtInst = 12, MemoryModel = 14, EntryPoint = 15, ExecutionMode = 16, Capability = 17, TypeVoid = 19, TypeBool = 20, TypeInt = 21, TypeFloat = 22, TypeVector = 23, TypeImage = 25, TypeSampler = 26, TypeSampledImage = 27, TypeArray = 28, TypeRuntimeArray = 29, TypeStruct = 30, TypePointer = 32, TypeFunction = 33, ConstantTrue = 41, ConstantFalse = 42, Constant = 43, ConstantComposite = 44, ConstantNull = 46, Function = 54, FunctionParameter = 55, FunctionEnd = 56, FunctionCall = 57, Variable = 59, Load = 61, Store = 62, AccessChain = 65, ArrayLength = 68, Decorate = 71, VectorExtractDynamic = 77, VectorInsertDynamic = 78, VectorShuffle = 79, CompositeConstruct = 80, CompositeExtract = 81, CompositeInsert = 82, CopyObject = 83, SampledImage = 86, ImageSampleImplicitLod = 87, ImageSampleExplicitLod = 88, ImageSampleDrefImplicitLod = 89, ImageSampleDrefExplicitLod = 90, ImageFetch = 95, ImageGather = 96, ImageDrefGather = 97, ImageRead = 98, ImageWrite = 99, Image = 100, ImageQuerySizeLod = 103, ImageQuerySize = 104, ImageQueryLod = 105, ImageQueryLevels = 106, ImageQuerySamples = 107, ConvertFToU = 109, ConvertFToS = 110, ConvertSToF = 111, ConvertUToF = 112, UConvert = 113, SConvert = 114, FConvert = 115, Bitcast = 124, SNegate = 126, FNegate = 127, IAdd = 128, FAdd = 129, ISub = 130, FSub = 131, IMul = 132, FMul = 133, UDiv = 134, SDiv = 135, FDiv = 136, UMod = 137, SRem = 138, SMod = 139, FRem = 140, FMod = 141, IAddCarry = 149, ISubBorrow = 150, UMulExtended = 151, SMulExtended = 152, Any = 154, All = 155, IsNan = 156, IsInf = 157, LogicalEqual = 164, LogicalNotEqual = 165, LogicalOr = 166, LogicalAnd = 167, LogicalNot = 168, Select = 169, IEqual = 170, INotEqual = 171, UGreaterThan = 172, SGreaterThan = 173, UGreaterThanEqual = 174, SGreaterThanEqual = 175, ULessThan = 176, SLessThan = 177, ULessThanEqual = 178, SLessThanEqual = 179, FOrdEqual = 180, FUnordEqual = 181, FOrdNotEqual = 182, FUnordNotEqual = 183, FOrdLessThan = 184, FUnordLessThan = 185, FOrdGreaterThan = 186, FUnordGreaterThan = 187, FOrdLessThanEqual = 188, FUnordLessThanEqual = 189, FOrdGreaterThanEqual = 190, FUnordGreaterThanEqual = 191, ShiftRightLogical = 194, ShiftRightArithmetic = 195, ShiftLeftLogical = 196, BitwiseOr = 197, BitwiseXor = 198, BitwiseAnd = 199, Not = 200, BitFieldInsert = 201, BitFieldSExtract = 202, BitFieldUExtract = 203, BitReverse = 204, BitCount = 205, ControlBarrier = 224, MemoryBarrier = 225, AtomicIAdd = 234, Phi = 245, LoopMerge = 246, SelectionMerge = 247, Label = 248, Branch = 249, BranchConditional = 250, Switch = 251, Kill = 252, Return = 253, ReturnValue = 254, Unreachable = 255, GroupNonUniformElect = 333, GroupNonUniformAll = 334, GroupNonUniformAny = 335, GroupNonUniformAllEqual = 336, GroupNonUniformBroadcast = 337, GroupNonUniformBroadcastFirst = 338, GroupNonUniformBallot = 339, GroupNonUniformShuffle = 345, GroupNonUniformShuffleXor = 346, GroupNonUniformShuffleUp = 347, GroupNonUniformShuffleDown = 348, } internal enum SpirvCapability : uint { Shader = 1, Float16 = 9, Float64 = 10, Int64 = 11, Int16 = 22, ImageGatherExtended = 25, StorageImageExtendedFormats = 49, ImageQuery = 50, StorageImageReadWithoutFormat = 55, StorageImageWriteWithoutFormat = 56, GroupNonUniform = 61, GroupNonUniformVote = 62, GroupNonUniformBallot = 64, GroupNonUniformShuffle = 65, RuntimeDescriptorArray = 5302, } internal enum SpirvStorageClass : uint { UniformConstant = 0, Input = 1, Uniform = 2, Output = 3, Workgroup = 4, Private = 6, Function = 7, PushConstant = 9, Image = 11, StorageBuffer = 12, } internal enum SpirvExecutionModel : uint { Vertex = 0, Fragment = 4, GLCompute = 5, } internal enum SpirvExecutionMode : uint { OriginUpperLeft = 7, DepthReplacing = 12, LocalSize = 17, } internal enum SpirvDecoration : uint { Block = 2, ArrayStride = 6, BuiltIn = 11, NoPerspective = 13, Flat = 14, Location = 30, Binding = 33, DescriptorSet = 34, Offset = 35, } internal enum SpirvBuiltIn : uint { Position = 0, VertexIndex = 42, InstanceIndex = 43, FragCoord = 15, FrontFacing = 17, WorkgroupId = 26, LocalInvocationId = 27, GlobalInvocationId = 28, LocalInvocationIndex = 29, SubgroupLocalInvocationId = 41, } internal enum SpirvImageDim : uint { Dim1D = 0, Dim2D = 1, Dim3D = 2, Cube = 3, Buffer = 5, } internal enum SpirvImageFormat : uint { Unknown = 0, Rgba32f = 1, Rgba16f = 2, R32f = 3, Rgba8 = 4, Rgba8Snorm = 5, Rg32f = 6, Rg16f = 7, R11fG11fB10f = 8, R16f = 9, Rgba16 = 10, Rgb10A2 = 11, Rg16 = 12, Rg8 = 13, R16 = 14, R8 = 15, Rgba16Snorm = 16, Rg16Snorm = 17, Rg8Snorm = 18, R16Snorm = 19, R8Snorm = 20, Rgba32i = 21, Rgba16i = 22, Rgba8i = 23, R32i = 24, Rg32i = 25, Rg16i = 26, Rg8i = 27, R16i = 28, R8i = 29, Rgba32ui = 30, Rgba16ui = 31, Rgba8ui = 32, R32ui = 33, Rgb10A2ui = 34, Rg32ui = 35, Rg16ui = 36, Rg8ui = 37, R16ui = 38, R8ui = 39, } internal sealed class SpirvModuleBuilder { private const uint Magic = 0x07230203; private const uint Version15 = 0x00010500; private const uint Generator = 0x53504500; // "SPE" private readonly List _capabilities = []; private readonly List _extensions = []; private readonly List _imports = []; private readonly List _memoryModel = []; private readonly List _entryPoints = []; private readonly List _executionModes = []; private readonly List _debug = []; private readonly List _annotations = []; private readonly List _typesConstantsGlobals = []; private readonly List _functions = []; private readonly Dictionary<(uint Width, bool Signed), uint> _integerTypes = []; private readonly Dictionary _floatTypes = []; private readonly Dictionary<(uint Component, uint Count), uint> _vectorTypes = []; private readonly Dictionary< ( uint SampledType, SpirvImageDim Dimension, bool Depth, bool Arrayed, bool Multisampled, uint Sampled, SpirvImageFormat Format ), uint> _imageTypes = []; private readonly Dictionary _sampledImageTypes = []; private readonly Dictionary<(SpirvStorageClass Storage, uint Type), uint> _pointerTypes = []; private readonly Dictionary<(uint Element, uint Count), uint> _arrayTypes = []; private readonly Dictionary _runtimeArrayTypes = []; private readonly Dictionary _functionTypes = []; private readonly Dictionary<(uint Type, ulong Value), uint> _constants = []; private readonly HashSet _declaredCapabilities = []; private readonly Dictionary _extInstImports = []; private uint _nextId = 1; private uint? _voidType; private uint? _boolType; public uint AllocateId() => _nextId++; public void AddCapability(SpirvCapability capability) { if (_declaredCapabilities.Add(capability)) { Emit(_capabilities, SpirvOp.Capability, (uint)capability); } } public void AddExtension(string extension) => EmitWithString(_extensions, SpirvOp.Extension, [], extension); public uint ImportExtInst(string name) { if (_extInstImports.TryGetValue(name, out var existing)) { return existing; } var id = AllocateId(); EmitWithString(_imports, SpirvOp.ExtInstImport, [id], name); _extInstImports.Add(name, id); return id; } public void SetLogicalGlsl450MemoryModel() => Emit(_memoryModel, SpirvOp.MemoryModel, 0, 1); public void AddEntryPoint( SpirvExecutionModel model, uint function, string name, IReadOnlyList interfaces) { var prefix = new uint[2 + interfaces.Count]; prefix[0] = (uint)model; prefix[1] = function; for (var index = 0; index < interfaces.Count; index++) { prefix[index + 2] = interfaces[index]; } EmitWithString(_entryPoints, SpirvOp.EntryPoint, prefix, name, stringBeforeTailCount: 2); } public void AddExecutionMode(uint function, SpirvExecutionMode mode, params uint[] operands) { var values = new uint[2 + operands.Length]; values[0] = function; values[1] = (uint)mode; operands.CopyTo(values, 2); Emit(_executionModes, SpirvOp.ExecutionMode, values); } public void AddName(uint target, string name) => EmitWithString(_debug, SpirvOp.Name, [target], name); public void AddDecoration(uint target, SpirvDecoration decoration, params uint[] operands) { var values = new uint[2 + operands.Length]; values[0] = target; values[1] = (uint)decoration; operands.CopyTo(values, 2); Emit(_annotations, SpirvOp.Decorate, values); } public void AddMemberDecoration( uint target, uint member, SpirvDecoration decoration, params uint[] operands) { var values = new uint[3 + operands.Length]; values[0] = target; values[1] = member; values[2] = (uint)decoration; operands.CopyTo(values, 3); EmitRaw(_annotations, 72, values); } public uint TypeVoid() { if (_voidType is { } existing) { return existing; } var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.TypeVoid, id); _voidType = id; return id; } public uint TypeBool() { if (_boolType is { } existing) { return existing; } var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.TypeBool, id); _boolType = id; return id; } public uint TypeInt(uint width, bool signed) { var key = (width, signed); if (_integerTypes.TryGetValue(key, out var existing)) { return existing; } var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.TypeInt, id, width, signed ? 1u : 0u); _integerTypes.Add(key, id); return id; } public uint TypeFloat(uint width) { if (_floatTypes.TryGetValue(width, out var existing)) { return existing; } var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.TypeFloat, id, width); _floatTypes.Add(width, id); return id; } public uint TypeVector(uint componentType, uint count) { var key = (componentType, count); if (_vectorTypes.TryGetValue(key, out var existing)) { return existing; } var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.TypeVector, id, componentType, count); _vectorTypes.Add(key, id); return id; } public uint TypeImage( uint sampledType, SpirvImageDim dimension, bool depth, bool arrayed, bool multisampled, uint sampled, SpirvImageFormat format) { var key = ( sampledType, dimension, depth, arrayed, multisampled, sampled, format); if (_imageTypes.TryGetValue(key, out var existing)) { return existing; } var id = AllocateId(); Emit( _typesConstantsGlobals, SpirvOp.TypeImage, id, sampledType, (uint)dimension, depth ? 1u : 0u, arrayed ? 1u : 0u, multisampled ? 1u : 0u, sampled, (uint)format); _imageTypes.Add(key, id); return id; } public uint TypeSampledImage(uint imageType) { if (_sampledImageTypes.TryGetValue(imageType, out var existing)) { return existing; } var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.TypeSampledImage, id, imageType); _sampledImageTypes.Add(imageType, id); return id; } public uint TypeArray(uint elementType, uint count) { var key = (elementType, count); if (_arrayTypes.TryGetValue(key, out var existing)) { return existing; } var length = Constant(TypeInt(32, false), count); var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.TypeArray, id, elementType, length); _arrayTypes.Add(key, id); return id; } public uint TypeRuntimeArray(uint elementType) { if (_runtimeArrayTypes.TryGetValue(elementType, out var existing)) { return existing; } var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.TypeRuntimeArray, id, elementType); _runtimeArrayTypes.Add(elementType, id); return id; } public uint TypeStruct(params uint[] memberTypes) { var id = AllocateId(); var operands = new uint[memberTypes.Length + 1]; operands[0] = id; memberTypes.CopyTo(operands, 1); Emit(_typesConstantsGlobals, SpirvOp.TypeStruct, operands); return id; } public uint TypePointer(SpirvStorageClass storageClass, uint type) { var key = (storageClass, type); if (_pointerTypes.TryGetValue(key, out var existing)) { return existing; } var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.TypePointer, id, (uint)storageClass, type); _pointerTypes.Add(key, id); return id; } public uint TypeFunction(uint returnType, params uint[] parameterTypes) { var key = returnType + ":" + string.Join(',', parameterTypes); if (_functionTypes.TryGetValue(key, out var existing)) { return existing; } var id = AllocateId(); var operands = new uint[parameterTypes.Length + 2]; operands[0] = id; operands[1] = returnType; parameterTypes.CopyTo(operands, 2); Emit(_typesConstantsGlobals, SpirvOp.TypeFunction, operands); _functionTypes.Add(key, id); return id; } public uint ConstantBool(bool value) { var type = TypeBool(); var key = (type, value ? 1UL : 0UL); if (_constants.TryGetValue(key, out var existing)) { return existing; } var id = AllocateId(); Emit( _typesConstantsGlobals, value ? SpirvOp.ConstantTrue : SpirvOp.ConstantFalse, type, id); _constants.Add(key, id); return id; } public uint Constant(uint type, uint value) { var key = (type, (ulong)value); if (_constants.TryGetValue(key, out var existing)) { return existing; } var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.Constant, type, id, value); _constants.Add(key, id); return id; } public uint Constant64(uint type, ulong value) { var key = (type, value); if (_constants.TryGetValue(key, out var existing)) { return existing; } var id = AllocateId(); Emit( _typesConstantsGlobals, SpirvOp.Constant, type, id, (uint)value, (uint)(value >> 32)); _constants.Add(key, id); return id; } public uint ConstantFloat(uint type, float value) => Constant(type, BitConverter.SingleToUInt32Bits(value)); public uint ConstantComposite(uint type, params uint[] constituents) { var id = AllocateId(); var operands = new uint[constituents.Length + 2]; operands[0] = type; operands[1] = id; constituents.CopyTo(operands, 2); Emit(_typesConstantsGlobals, SpirvOp.ConstantComposite, operands); return id; } public uint ConstantNull(uint type) { var key = (type, ulong.MaxValue); if (_constants.TryGetValue(key, out var existing)) { return existing; } var id = AllocateId(); Emit(_typesConstantsGlobals, SpirvOp.ConstantNull, type, id); _constants.Add(key, id); return id; } public uint AddGlobalVariable( uint pointerType, SpirvStorageClass storageClass, uint? initializer = null) { var id = AllocateId(); if (initializer.HasValue) { Emit( _typesConstantsGlobals, SpirvOp.Variable, pointerType, id, (uint)storageClass, initializer.Value); } else { Emit(_typesConstantsGlobals, SpirvOp.Variable, pointerType, id, (uint)storageClass); } return id; } public uint BeginFunction(uint returnType, uint functionType) { var id = AllocateId(); Emit(_functions, SpirvOp.Function, returnType, id, 0, functionType); return id; } public uint AddFunctionParameter(uint type) => EmitResult(_functions, SpirvOp.FunctionParameter, type); public uint AddLabel(uint? id = null) { var result = id ?? AllocateId(); Emit(_functions, SpirvOp.Label, result); return result; } public uint AddFunctionVariable(uint pointerType, uint? initializer = null) { var id = AllocateId(); if (initializer.HasValue) { Emit( _functions, SpirvOp.Variable, pointerType, id, (uint)SpirvStorageClass.Function, initializer.Value); } else { Emit( _functions, SpirvOp.Variable, pointerType, id, (uint)SpirvStorageClass.Function); } return id; } public uint AddInstruction(SpirvOp opcode, uint resultType, params uint[] operands) => EmitResult(_functions, opcode, resultType, operands); public void AddStatement(SpirvOp opcode, params uint[] operands) => Emit(_functions, opcode, operands); public void EndFunction() => Emit(_functions, SpirvOp.FunctionEnd); public byte[] Build() { if (_memoryModel.Count == 0) { SetLogicalGlsl450MemoryModel(); } var wordCount = 5 + _capabilities.Count + _extensions.Count + _imports.Count + _memoryModel.Count + _entryPoints.Count + _executionModes.Count + _debug.Count + _annotations.Count + _typesConstantsGlobals.Count + _functions.Count; var words = new uint[wordCount]; var offset = 0; WriteWord(Magic); WriteWord(Version15); WriteWord(Generator); WriteWord(_nextId); WriteWord(0); WriteSection(_capabilities); WriteSection(_extensions); WriteSection(_imports); WriteSection(_memoryModel); WriteSection(_entryPoints); WriteSection(_executionModes); WriteSection(_debug); WriteSection(_annotations); WriteSection(_typesConstantsGlobals); WriteSection(_functions); var bytes = new byte[wordCount * sizeof(uint)]; Buffer.BlockCopy(words, 0, bytes, 0, bytes.Length); return bytes; void WriteWord(uint value) { words[offset++] = value; } void WriteSection(List section) { foreach (var value in section) { WriteWord(value); } } } private uint EmitResult( List section, SpirvOp opcode, uint resultType, params uint[] operands) { var result = AllocateId(); var values = new uint[operands.Length + 2]; values[0] = resultType; values[1] = result; operands.CopyTo(values, 2); Emit(section, opcode, values); return result; } private static void Emit(List section, SpirvOp opcode, params uint[] operands) => EmitRaw(section, (ushort)opcode, operands); private static void EmitRaw(List section, ushort opcode, params uint[] operands) { section.Add(((uint)(operands.Length + 1) << 16) | opcode); section.AddRange(operands); } private static void EmitWithString( List section, SpirvOp opcode, IReadOnlyList prefix, string value, int stringBeforeTailCount = -1) { var encoded = EncodeString(value); if (stringBeforeTailCount < 0) { var operands = new uint[prefix.Count + encoded.Length]; for (var index = 0; index < prefix.Count; index++) { operands[index] = prefix[index]; } encoded.CopyTo(operands, prefix.Count); Emit(section, opcode, operands); return; } var result = new uint[prefix.Count + encoded.Length]; for (var index = 0; index < stringBeforeTailCount; index++) { result[index] = prefix[index]; } encoded.CopyTo(result, stringBeforeTailCount); for (var index = stringBeforeTailCount; index < prefix.Count; index++) { result[index + encoded.Length] = prefix[index]; } Emit(section, opcode, result); } private static uint[] EncodeString(string value) { var byteCount = Encoding.UTF8.GetByteCount(value) + 1; var words = new uint[(byteCount + 3) / 4]; Encoding.UTF8.GetBytes(value, System.Runtime.InteropServices.MemoryMarshal.AsBytes(words.AsSpan())); return words; } }