// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later namespace SharpEmu.Libs.Agc; internal static partial class Gen5SpirvTranslator { private sealed partial class CompilationContext { private bool TryEmitVectorAlu( Gen5ShaderInstruction instruction, out string error) { error = string.Empty; if (instruction.Opcode == "VNop") { return true; } if (instruction.Opcode.StartsWith("VCmp", StringComparison.Ordinal)) { return TryEmitVectorCompare(instruction, out error); } if (!TryGetVectorDestination(instruction, out var destination)) { error = "missing vector destination"; return false; } uint result; switch (instruction.Opcode) { case "VMovB32": case "VReadlaneB32": case "VReadfirstlaneB32": result = GetRawSource(instruction, 0); break; case "VCndmaskB32": { var condition = instruction.Sources.Count > 2 ? IsCurrentLaneSet(GetRawSource64(instruction, 2)) : Load(_boolType, _vcc); result = _module.AddInstruction( SpirvOp.Select, _uintType, condition, GetRawSource(instruction, 1), GetRawSource(instruction, 0)); break; } case "VCvtU32F32": result = _module.AddInstruction( SpirvOp.ConvertFToU, _uintType, GetFloatSource(instruction, 0)); break; case "VCvtI32F32": case "VCvtRpiI32F32": case "VCvtFlrI32F32": { var source = GetFloatSource(instruction, 0); if (instruction.Opcode == "VCvtRpiI32F32") { source = Ext(9, _floatType, source); } else if (instruction.Opcode == "VCvtFlrI32F32") { source = Ext(8, _floatType, source); } result = Bitcast( _uintType, _module.AddInstruction(SpirvOp.ConvertFToS, _intType, source)); break; } case "VCvtF32I32": { var signed = Bitcast(_intType, GetRawSource(instruction, 0)); result = Bitcast( _uintType, _module.AddInstruction(SpirvOp.ConvertSToF, _floatType, signed)); break; } case "VCvtF32U32": result = Bitcast( _uintType, _module.AddInstruction( SpirvOp.ConvertUToF, _floatType, GetRawSource(instruction, 0))); break; case "VCvtF32Ubyte0": case "VCvtF32Ubyte1": case "VCvtF32Ubyte2": case "VCvtF32Ubyte3": { var shift = (uint)(instruction.Opcode[^1] - '0') * 8; var raw = ShiftRightLogical(GetRawSource(instruction, 0), UInt(shift)); raw = BitwiseAnd(raw, UInt(0xFF)); result = Bitcast( _uintType, _module.AddInstruction(SpirvOp.ConvertUToF, _floatType, raw)); break; } case "VCvtF16F32": { var vector = _module.AddInstruction( SpirvOp.CompositeConstruct, _vec2Type, GetFloatSource(instruction, 0), Float(0)); result = BitwiseAnd(Ext(58, _uintType, vector), UInt(0xFFFF)); break; } case "VCvtF32F16": { var unpacked = Ext(62, _vec2Type, GetRawSource(instruction, 0)); var value = _module.AddInstruction( SpirvOp.CompositeExtract, _floatType, unpacked, 0); result = Bitcast(_uintType, value); break; } case "VCvtOffF32I4": result = EmitCvtOffF32I4(instruction); break; case "VCvtPkU8F32": { var converted = _module.AddInstruction( SpirvOp.ConvertFToU, _uintType, GetFloatSource(instruction, 0)); var offset = ShiftLeftLogical( BitwiseAnd(GetRawSource(instruction, 1), UInt(3)), UInt(3)); result = _module.AddInstruction( SpirvOp.BitFieldInsert, _uintType, GetRawSource(instruction, 2), converted, offset, UInt(8)); break; } case "VRcpF32": case "VRcpIflagF32": result = EmitFloatResult( instruction, _module.AddInstruction( SpirvOp.FDiv, _floatType, Float(1), GetFloatSource(instruction, 0))); break; case "VLogF32": result = EmitFloatResult( instruction, Ext(30, _floatType, GetFloatSource(instruction, 0))); break; case "VLdexpF32": result = EmitFloatResult( instruction, Ext( 53, _floatType, GetFloatSource(instruction, 0), Bitcast(_intType, GetRawSource(instruction, 1)))); break; case "VExpF32": result = EmitFloatResult( instruction, Ext(29, _floatType, GetFloatSource(instruction, 0))); break; case "VRsqF32": result = EmitFloatResult( instruction, Ext(32, _floatType, GetFloatSource(instruction, 0))); break; case "VFractF32": result = EmitFloatResult( instruction, Ext(10, _floatType, GetFloatSource(instruction, 0))); break; case "VTruncF32": result = EmitFloatResult( instruction, Ext(3, _floatType, GetFloatSource(instruction, 0))); break; case "VCeilF32": result = EmitFloatResult( instruction, Ext(9, _floatType, GetFloatSource(instruction, 0))); break; case "VRndneF32": result = EmitFloatResult( instruction, Ext(2, _floatType, GetFloatSource(instruction, 0))); break; case "VFloorF32": result = EmitFloatResult( instruction, Ext(8, _floatType, GetFloatSource(instruction, 0))); break; case "VSqrtF32": result = EmitFloatResult( instruction, Ext(31, _floatType, GetFloatSource(instruction, 0))); break; case "VSinF32": result = EmitFloatResult( instruction, Ext( 13, _floatType, _module.AddInstruction( SpirvOp.FMul, _floatType, GetFloatSource(instruction, 0), Float(MathF.Tau)))); break; case "VCosF32": result = EmitFloatResult( instruction, Ext( 14, _floatType, _module.AddInstruction( SpirvOp.FMul, _floatType, GetFloatSource(instruction, 0), Float(MathF.Tau)))); break; case "VAddF32": result = EmitFloatBinary(instruction, SpirvOp.FAdd); break; case "VSubF32": result = EmitFloatBinary(instruction, SpirvOp.FSub); break; case "VSubrevF32": result = EmitFloatBinary(instruction, SpirvOp.FSub, reverse: true); break; case "VMulF32": result = EmitFloatBinary(instruction, SpirvOp.FMul); break; case "VMinF32": result = EmitFloatExtBinary(instruction, 37); break; case "VMaxF32": result = EmitFloatExtBinary(instruction, 40); break; case "VMadF32": case "VFmaF32": case "VMadMkF32": case "VMadAkF32": case "VFmamkF32": case "VFmaakF32": result = EmitFloatResult( instruction, Ext( 50, _floatType, GetFloatSource(instruction, 0), GetFloatSource(instruction, 1), GetFloatSource(instruction, 2))); break; case "VMacF32": case "VFmacF32": { var addend = Bitcast(_floatType, LoadV(destination)); result = EmitFloatResult( instruction, Ext( 50, _floatType, GetFloatSource(instruction, 0), GetFloatSource(instruction, 1), addend)); break; } case "VMin3F32": result = EmitFloatTernaryExt(instruction, 37); break; case "VMax3F32": result = EmitFloatTernaryExt(instruction, 40); break; case "VAndB32": result = EmitIntegerBinary(instruction, SpirvOp.BitwiseAnd); break; case "VOrB32": result = EmitIntegerBinary(instruction, SpirvOp.BitwiseOr); break; case "VXorB32": result = EmitIntegerBinary(instruction, SpirvOp.BitwiseXor); break; case "VNotB32": result = _module.AddInstruction( SpirvOp.Not, _uintType, GetRawSource(instruction, 0)); break; case "VBfrevB32": result = _module.AddInstruction( SpirvOp.BitReverse, _uintType, GetRawSource(instruction, 0)); break; case "VFfblB32": result = Bitcast( _uintType, Ext( 73, _intType, Bitcast(_intType, GetRawSource(instruction, 0)))); break; case "VAddI32": case "VAddU32": result = EmitIntegerBinary(instruction, SpirvOp.IAdd); break; case "VAddcU32": result = EmitAddWithCarry(instruction); break; case "VSubI32": case "VSubU32": result = EmitIntegerBinary(instruction, SpirvOp.ISub); break; case "VSubrevI32": case "VSubrevU32": result = EmitIntegerBinary(instruction, SpirvOp.ISub, reverse: true); break; case "VSubbU32": result = EmitSubtractWithBorrow(instruction, reverse: false); break; case "VSubbrevU32": result = EmitSubtractWithBorrow(instruction, reverse: true); break; case "VMulLoU32": case "VMulLoI32": case "VMulU32U24": result = EmitIntegerBinary(instruction, SpirvOp.IMul); break; case "VMulHiU32": case "VMulHiU32U24": { var left = GetRawSource(instruction, 0); var right = GetRawSource(instruction, 1); if (instruction.Opcode == "VMulHiU32U24") { left = BitwiseAnd(left, UInt(0x00FF_FFFF)); right = BitwiseAnd(right, UInt(0x00FF_FFFF)); } var wideLeft = _module.AddInstruction( SpirvOp.UConvert, _ulongType, left); var wideRight = _module.AddInstruction( SpirvOp.UConvert, _ulongType, right); var product = _module.AddInstruction( SpirvOp.IMul, _ulongType, wideLeft, wideRight); result = _module.AddInstruction( SpirvOp.UConvert, _uintType, ShiftRightLogical64( product, _module.Constant64(_ulongType, 32))); break; } case "VMulHiI32": { var wideLeft = _module.AddInstruction( SpirvOp.SConvert, _longType, Bitcast(_intType, GetRawSource(instruction, 0))); var wideRight = _module.AddInstruction( SpirvOp.SConvert, _longType, Bitcast(_intType, GetRawSource(instruction, 1))); var product = _module.AddInstruction( SpirvOp.IMul, _longType, wideLeft, wideRight); result = _module.AddInstruction( SpirvOp.UConvert, _uintType, ShiftRightLogical64( Bitcast(_ulongType, product), _module.Constant64(_ulongType, 32))); break; } case "VBcntU32B32": result = IAdd( _module.AddInstruction( SpirvOp.BitCount, _uintType, GetRawSource(instruction, 0)), GetRawSource(instruction, 1)); break; case "VMadU32U24": { var left = BitwiseAnd( GetRawSource(instruction, 0), UInt(0x00FF_FFFF)); var right = BitwiseAnd( GetRawSource(instruction, 1), UInt(0x00FF_FFFF)); result = IAdd( _module.AddInstruction( SpirvOp.IMul, _uintType, left, right), GetRawSource(instruction, 2)); break; } case "VMadU32U16": { var left = BitwiseAnd( GetRawSource(instruction, 0), UInt(0xFFFF)); var right = BitwiseAnd( GetRawSource(instruction, 1), UInt(0xFFFF)); result = IAdd( _module.AddInstruction( SpirvOp.IMul, _uintType, left, right), GetRawSource(instruction, 2)); break; } case "VLshrB32": result = EmitIntegerBinary(instruction, SpirvOp.ShiftRightLogical); break; case "VLshrrevB32": result = EmitIntegerBinary( instruction, SpirvOp.ShiftRightLogical, reverse: true); break; case "VLshlB32": result = EmitIntegerBinary(instruction, SpirvOp.ShiftLeftLogical); break; case "VLshlrevB32": result = EmitIntegerBinary( instruction, SpirvOp.ShiftLeftLogical, reverse: true); break; case "VAshrI32": case "VAshrrevI32": { var reverse = instruction.Opcode == "VAshrrevI32"; var left = GetRawSource(instruction, reverse ? 1 : 0); var right = GetRawSource(instruction, reverse ? 0 : 1); result = ShiftRightArithmetic(left, right); break; } case "VLshlAddU32": { var shifted = ShiftLeftLogical( GetRawSource(instruction, 0), BitwiseAnd(GetRawSource(instruction, 1), UInt(31))); result = IAdd(shifted, GetRawSource(instruction, 2)); break; } case "VLshlOrU32": { var shifted = ShiftLeftLogical( GetRawSource(instruction, 0), BitwiseAnd(GetRawSource(instruction, 1), UInt(31))); result = BitwiseOr( shifted, GetRawSource(instruction, 2)); break; } case "VAndOrB32": result = BitwiseOr( BitwiseAnd( GetRawSource(instruction, 0), GetRawSource(instruction, 1)), GetRawSource(instruction, 2)); break; case "VOr3U32": result = BitwiseOr( BitwiseOr( GetRawSource(instruction, 0), GetRawSource(instruction, 1)), GetRawSource(instruction, 2)); break; case "VPermlane16B32": result = EmitPermlane16(instruction, exchangeRows: false); break; case "VPermlanex16B32": result = EmitPermlane16(instruction, exchangeRows: true); break; case "VAddLshlU32": { var added = IAdd( GetRawSource(instruction, 0), GetRawSource(instruction, 1)); result = ShiftLeftLogical(added, GetRawSource(instruction, 2)); break; } case "VAdd3U32": result = IAdd( IAdd( GetRawSource(instruction, 0), GetRawSource(instruction, 1)), GetRawSource(instruction, 2)); break; case "VMinU32": result = Ext( 38, _uintType, GetRawSource(instruction, 0), GetRawSource(instruction, 1)); break; case "VMaxU32": result = Ext( 41, _uintType, GetRawSource(instruction, 0), GetRawSource(instruction, 1)); break; case "VMin3U32": result = Ext( 38, _uintType, Ext( 38, _uintType, GetRawSource(instruction, 0), GetRawSource(instruction, 1)), GetRawSource(instruction, 2)); break; case "VMax3U32": result = Ext( 41, _uintType, Ext( 41, _uintType, GetRawSource(instruction, 0), GetRawSource(instruction, 1)), GetRawSource(instruction, 2)); break; case "VMinI32": case "VMaxI32": { var signedResult = Ext( instruction.Opcode == "VMinI32" ? 39u : 42u, _intType, Bitcast(_intType, GetRawSource(instruction, 0)), Bitcast(_intType, GetRawSource(instruction, 1))); result = Bitcast(_uintType, signedResult); break; } case "VMin3I32": case "VMax3I32": { var operation = instruction.Opcode == "VMin3I32" ? 39u : 42u; var left = Bitcast( _intType, GetRawSource(instruction, 0)); var middle = Bitcast( _intType, GetRawSource(instruction, 1)); var right = Bitcast( _intType, GetRawSource(instruction, 2)); result = Bitcast( _uintType, Ext( operation, _intType, Ext(operation, _intType, left, middle), right)); break; } case "VMed3U32": { var left = GetRawSource(instruction, 0); var middle = GetRawSource(instruction, 1); var right = GetRawSource(instruction, 2); var low = Ext(38, _uintType, left, middle); var high = Ext(41, _uintType, left, middle); result = Ext( 41, _uintType, low, Ext(38, _uintType, high, right)); break; } case "VSadU32": { var left = GetRawSource(instruction, 0); var right = GetRawSource(instruction, 1); var difference = _module.AddInstruction( SpirvOp.ISub, _uintType, Ext(41, _uintType, left, right), Ext(38, _uintType, left, right)); result = IAdd(difference, GetRawSource(instruction, 2)); break; } case "VMed3I32": { var left = Bitcast(_intType, GetRawSource(instruction, 0)); var middle = Bitcast(_intType, GetRawSource(instruction, 1)); var right = Bitcast(_intType, GetRawSource(instruction, 2)); var low = Ext(39, _intType, left, middle); var high = Ext(42, _intType, left, middle); result = Bitcast( _uintType, Ext( 42, _intType, low, Ext(39, _intType, high, right))); break; } case "VMed3F32": { var left = GetFloatSource(instruction, 0); var middle = GetFloatSource(instruction, 1); var right = GetFloatSource(instruction, 2); var low = Ext(37, _floatType, left, middle); var high = Ext(40, _floatType, left, middle); result = EmitFloatResult( instruction, Ext( 40, _floatType, low, Ext(37, _floatType, high, right))); break; } case "VCubeidF32": result = EmitCubeCoordinate(instruction, CubeCoordinate.Id); break; case "VCubescF32": result = EmitCubeCoordinate(instruction, CubeCoordinate.Sc); break; case "VCubetcF32": result = EmitCubeCoordinate(instruction, CubeCoordinate.Tc); break; case "VCubemaF32": result = EmitCubeCoordinate(instruction, CubeCoordinate.Ma); break; case "VAddCoU32": { var left = GetRawSource(instruction, 0); var right = GetRawSource(instruction, 1); result = IAdd(left, right); var carry = _module.AddInstruction( SpirvOp.ULessThan, _boolType, result, left); StoreCarryOut(instruction, carry); break; } case "VSubCoU32": case "VSubrevCoU32": { var reverse = instruction.Opcode == "VSubrevCoU32"; var left = GetRawSource(instruction, reverse ? 1 : 0); var right = GetRawSource(instruction, reverse ? 0 : 1); result = _module.AddInstruction(SpirvOp.ISub, _uintType, left, right); var borrow = _module.AddInstruction( SpirvOp.ULessThan, _boolType, left, right); StoreCarryOut(instruction, borrow); break; } case "VBfeU32": { var width = BitwiseAnd(GetRawSource(instruction, 2), UInt(31)); result = _module.AddInstruction( SpirvOp.BitFieldUExtract, _uintType, GetRawSource(instruction, 0), BitwiseAnd(GetRawSource(instruction, 1), UInt(31)), width); break; } case "VBfiB32": { var mask = GetRawSource(instruction, 0); var insert = GetRawSource(instruction, 1); var source = GetRawSource(instruction, 2); result = _module.AddInstruction( SpirvOp.BitwiseOr, _uintType, BitwiseAnd(mask, insert), BitwiseAnd( _module.AddInstruction(SpirvOp.Not, _uintType, mask), source)); break; } case "VCvtPkrtzF16F32": { var first = TruncateFloat32ForPack(GetFloatSource(instruction, 0)); var second = TruncateFloat32ForPack(GetFloatSource(instruction, 1)); var vector = _module.AddInstruction( SpirvOp.CompositeConstruct, _vec2Type, first, second); result = Ext(58, _uintType, vector); break; } case "VCvtPkU16U32": case "VCvtPkI16I32": result = BitwiseOr( BitwiseAnd(GetRawSource(instruction, 0), UInt(0xFFFF)), ShiftLeftLogical( BitwiseAnd(GetRawSource(instruction, 1), UInt(0xFFFF)), UInt(16))); break; default: error = $"unsupported vector opcode {instruction.Opcode}"; return false; } StoreV(destination, result); return true; } private bool TryEmitVectorCompare( Gen5ShaderInstruction instruction, out string error) { error = string.Empty; uint condition = _module.ConstantBool(false); var opcode = instruction.Opcode; if (opcode is "VCmpClassF32" or "VCmpxClassF32") { var source = GetFloatSource(instruction, 0); var raw = GetRawSource(instruction, 0); var mask = GetRawSource(instruction, 1); var negative = IsNotZero(BitwiseAnd(raw, UInt(0x8000_0000))); var positive = _module.AddInstruction( SpirvOp.LogicalNot, _boolType, negative); var nan = _module.AddInstruction(SpirvOp.IsNan, _boolType, source); var infinity = _module.AddInstruction(SpirvOp.IsInf, _boolType, source); var zero = _module.AddInstruction( SpirvOp.FOrdEqual, _boolType, source, Float(0)); var absolute = Ext(4, _floatType, source); var nonzero = _module.AddInstruction( SpirvOp.FOrdGreaterThan, _boolType, absolute, Float(0)); var belowNormal = _module.AddInstruction( SpirvOp.FOrdLessThan, _boolType, absolute, Bitcast(_floatType, UInt(0x0080_0000))); var subnormal = _module.AddInstruction( SpirvOp.LogicalAnd, _boolType, nonzero, belowNormal); var special = _module.AddInstruction( SpirvOp.LogicalOr, _boolType, nan, _module.AddInstruction( SpirvOp.LogicalOr, _boolType, infinity, _module.AddInstruction( SpirvOp.LogicalOr, _boolType, zero, subnormal))); var normal = _module.AddInstruction( SpirvOp.LogicalNot, _boolType, special); uint MaskedClass(uint bits, uint value) { var enabled = IsNotZero(BitwiseAnd(mask, UInt(bits))); return _module.AddInstruction( SpirvOp.LogicalAnd, _boolType, enabled, value); } uint SignedClass(uint negativeBit, uint positiveBit, uint value) { var negativeClass = MaskedClass( negativeBit, _module.AddInstruction( SpirvOp.LogicalAnd, _boolType, negative, value)); var positiveClass = MaskedClass( positiveBit, _module.AddInstruction( SpirvOp.LogicalAnd, _boolType, positive, value)); return _module.AddInstruction( SpirvOp.LogicalOr, _boolType, negativeClass, positiveClass); } condition = MaskedClass(0x003, nan); condition = _module.AddInstruction( SpirvOp.LogicalOr, _boolType, condition, SignedClass(0x004, 0x200, infinity)); condition = _module.AddInstruction( SpirvOp.LogicalOr, _boolType, condition, SignedClass(0x008, 0x100, normal)); condition = _module.AddInstruction( SpirvOp.LogicalOr, _boolType, condition, SignedClass(0x010, 0x080, subnormal)); condition = _module.AddInstruction( SpirvOp.LogicalOr, _boolType, condition, SignedClass(0x020, 0x040, zero)); } else if (opcode is "VCmpFF32" or "VCmpxFF32" or "VCmpFI32" or "VCmpFU32") { condition = _module.ConstantBool(false); } else if (opcode is "VCmpTruF32" or "VCmpxTruF32" or "VCmpTI32" or "VCmpTU32") { condition = _module.ConstantBool(true); } else if (opcode is "VCmpOF32" or "VCmpxOF32" or "VCmpUF32" or "VCmpxUF32") { // The ordered/unordered predicates only test whether either // operand is NaN. SPIR-V's OpOrdered/OpUnordered are Kernel-only, // so build the same result from OpIsNan, which needs no extra // capability: unordered = isnan(a) || isnan(b), ordered = !that. var left = GetFloatSource(instruction, 0); var right = GetFloatSource(instruction, 1); var nanLeft = _module.AddInstruction(SpirvOp.IsNan, _boolType, left); var nanRight = _module.AddInstruction(SpirvOp.IsNan, _boolType, right); var unordered = _module.AddInstruction( SpirvOp.LogicalOr, _boolType, nanLeft, nanRight); condition = opcode is "VCmpUF32" or "VCmpxUF32" ? unordered : _module.AddInstruction(SpirvOp.LogicalNot, _boolType, unordered); } else if (opcode is not ("VCmpClassF32" or "VCmpxClassF32") && opcode.EndsWith("F32", StringComparison.Ordinal)) { var left = GetFloatSource(instruction, 0); var right = GetFloatSource(instruction, 1); var operation = opcode switch { "VCmpLtF32" or "VCmpxLtF32" => SpirvOp.FOrdLessThan, "VCmpEqF32" or "VCmpxEqF32" => SpirvOp.FOrdEqual, "VCmpLeF32" or "VCmpxLeF32" => SpirvOp.FOrdLessThanEqual, "VCmpGtF32" or "VCmpxGtF32" => SpirvOp.FOrdGreaterThan, "VCmpLgF32" or "VCmpxLgF32" => SpirvOp.FOrdNotEqual, "VCmpGeF32" or "VCmpxGeF32" => SpirvOp.FOrdGreaterThanEqual, "VCmpNeqF32" or "VCmpxNeqF32" => SpirvOp.FUnordNotEqual, "VCmpNlgF32" or "VCmpxNlgF32" => SpirvOp.FUnordEqual, "VCmpNltF32" or "VCmpxNltF32" => SpirvOp.FUnordGreaterThanEqual, "VCmpNleF32" or "VCmpxNleF32" => SpirvOp.FUnordGreaterThan, "VCmpNgtF32" or "VCmpxNgtF32" => SpirvOp.FUnordLessThanEqual, "VCmpNgeF32" or "VCmpxNgeF32" => SpirvOp.FUnordLessThan, _ => SpirvOp.Nop, }; if (operation == SpirvOp.Nop) { error = $"unsupported float compare {opcode}"; return false; } condition = _module.AddInstruction(operation, _boolType, left, right); } else if (opcode is not ("VCmpClassF32" or "VCmpxClassF32")) { var left = GetRawSource(instruction, 0); var right = GetRawSource(instruction, 1); var signed = opcode.EndsWith("I32", StringComparison.Ordinal); if (signed) { left = Bitcast(_intType, left); right = Bitcast(_intType, right); } var operation = opcode switch { "VCmpEqI32" or "VCmpxEqI32" or "VCmpEqU32" or "VCmpxEqU32" => SpirvOp.IEqual, "VCmpNeI32" or "VCmpxNeI32" or "VCmpNeU32" or "VCmpxNeU32" => SpirvOp.INotEqual, "VCmpLtI32" or "VCmpxLtI32" => SpirvOp.SLessThan, "VCmpLeI32" or "VCmpxLeI32" => SpirvOp.SLessThanEqual, "VCmpGtI32" or "VCmpxGtI32" => SpirvOp.SGreaterThan, "VCmpGeI32" or "VCmpxGeI32" => SpirvOp.SGreaterThanEqual, "VCmpLtU32" or "VCmpxLtU32" => SpirvOp.ULessThan, "VCmpLeU32" or "VCmpxLeU32" => SpirvOp.ULessThanEqual, "VCmpGtU32" or "VCmpxGtU32" => SpirvOp.UGreaterThan, "VCmpGeU32" or "VCmpxGeU32" => SpirvOp.UGreaterThanEqual, _ => SpirvOp.Nop, }; if (operation == SpirvOp.Nop) { error = $"unsupported integer compare {opcode}"; return false; } condition = _module.AddInstruction(operation, _boolType, left, right); } // On gfx10, VCmpx writes EXEC only and preserves VCC; the sdst // operand was removed from the cmpx encodings on this generation. if (opcode.StartsWith("VCmpx", StringComparison.Ordinal)) { var active = _module.AddInstruction( SpirvOp.LogicalAnd, _boolType, Load(_boolType, _exec), condition); StoreWaveMask(126, active); } else { StoreWaveMask(106, condition); } return true; } private bool TryEmitScalarAlu( Gen5ShaderInstruction instruction, out string error) { error = string.Empty; if (instruction.Encoding == Gen5ShaderEncoding.Sopc) { return TryEmitScalarCompare(instruction, out error); } if (instruction.Destinations.Count == 0 || instruction.Destinations[0].Kind != Gen5OperandKind.ScalarRegister) { error = "missing scalar destination"; return false; } var destination = instruction.Destinations[0].Value; if (instruction.Encoding == Gen5ShaderEncoding.Sopk) { var immediate = unchecked((uint)(short)(instruction.Words[0] & 0xFFFF)); if (instruction.Opcode.StartsWith("SCmpk", StringComparison.Ordinal)) { return TryEmitScalarCompareK(instruction, destination, immediate, out error); } var current = LoadS(destination); var value = instruction.Opcode switch { "SMovkI32" => UInt(immediate), "SAddkI32" => IAdd(current, UInt(immediate)), "SMulkI32" => _module.AddInstruction( SpirvOp.IMul, _uintType, current, UInt(immediate)), _ => 0u, }; if (value == 0) { error = $"unsupported scalar immediate {instruction.Opcode}"; return false; } StoreS(destination, value); return true; } if (instruction.Opcode == "SGetpcB64") { var pc = _state.Program.Address + instruction.Pc + (ulong)(instruction.Words.Count * sizeof(uint)); StoreS(destination, UInt((uint)pc)); StoreS(destination + 1, UInt((uint)(pc >> 32))); return true; } if (instruction.Opcode.EndsWith("B64", StringComparison.Ordinal) || instruction.Opcode is "SWqmB64" or "SBfeU64" or "SBfeI64") { return TryEmitScalar64(instruction, destination, out error); } var left = GetRawSource(instruction, 0); uint result; switch (instruction.Opcode) { case "SMovB32": result = left; break; case "SNotB32": result = _module.AddInstruction(SpirvOp.Not, _uintType, left); StoreS(destination, result); Store(_scc, IsNotZero(result)); return true; case "SBrevB32": result = _module.AddInstruction(SpirvOp.BitReverse, _uintType, left); StoreS(destination, result); Store(_scc, IsNotZero(result)); return true; case "SBcnt1I32B32": result = _module.AddInstruction(SpirvOp.BitCount, _uintType, left); StoreS(destination, result); Store(_scc, IsNotZero(result)); return true; case "SFF1I32B32": result = Ext(73, _uintType, left); StoreS(destination, result); Store(_scc, IsNotZero(result)); return true; case "SBitset1B32": result = _module.AddInstruction( SpirvOp.BitFieldInsert, _uintType, LoadS(destination), UInt(1), BitwiseAnd(left, UInt(31)), UInt(1)); StoreS(destination, result); return true; default: { if (instruction.Sources.Count < 2) { error = $"missing scalar source for {instruction.Opcode}"; return false; } var right = GetRawSource(instruction, 1); switch (instruction.Opcode) { case "SAddU32": result = IAdd(left, right); Store(_scc, _module.AddInstruction( SpirvOp.ULessThan, _boolType, result, left)); break; case "SSubU32": result = _module.AddInstruction( SpirvOp.ISub, _uintType, left, right); Store(_scc, _module.AddInstruction( SpirvOp.UGreaterThan, _boolType, right, left)); break; case "SAddI32": result = IAdd(left, right); Store(_scc, SignedAddOverflow(left, right, result)); break; case "SSubI32": result = _module.AddInstruction( SpirvOp.ISub, _uintType, left, right); Store(_scc, SignedSubOverflow(left, right, result)); break; case "SAddcU32": { var carryIn = _module.AddInstruction( SpirvOp.Select, _uintType, Load(_boolType, _scc), UInt(1), UInt(0)); var partial = IAdd(left, right); result = IAdd(partial, carryIn); var firstCarry = _module.AddInstruction( SpirvOp.ULessThan, _boolType, partial, left); var secondCarry = _module.AddInstruction( SpirvOp.ULessThan, _boolType, result, partial); Store( _scc, _module.AddInstruction( SpirvOp.LogicalOr, _boolType, firstCarry, secondCarry)); break; } case "SSubbU32": { var borrow = _module.AddInstruction( SpirvOp.Select, _uintType, Load(_boolType, _scc), UInt(1), UInt(0)); var partial = _module.AddInstruction( SpirvOp.ISub, _uintType, left, right); result = _module.AddInstruction( SpirvOp.ISub, _uintType, partial, borrow); var firstBorrow = _module.AddInstruction( SpirvOp.UGreaterThan, _boolType, right, left); var secondBorrow = _module.AddInstruction( SpirvOp.LogicalAnd, _boolType, _module.AddInstruction( SpirvOp.IEqual, _boolType, borrow, UInt(1)), _module.AddInstruction( SpirvOp.IEqual, _boolType, right, left)); Store( _scc, _module.AddInstruction( SpirvOp.LogicalOr, _boolType, firstBorrow, secondBorrow)); break; } case "SMulI32": result = _module.AddInstruction( SpirvOp.IMul, _uintType, left, right); break; case "SMulHiU32": { var wideLeft = _module.AddInstruction( SpirvOp.UConvert, _ulongType, left); var wideRight = _module.AddInstruction( SpirvOp.UConvert, _ulongType, right); var product = _module.AddInstruction( SpirvOp.IMul, _ulongType, wideLeft, wideRight); result = _module.AddInstruction( SpirvOp.UConvert, _uintType, ShiftRightLogical64( product, _module.Constant64(_ulongType, 32))); break; } case "SMulHiI32": { var wideLeft = _module.AddInstruction( SpirvOp.SConvert, _longType, Bitcast(_intType, left)); var wideRight = _module.AddInstruction( SpirvOp.SConvert, _longType, Bitcast(_intType, right)); var product = _module.AddInstruction( SpirvOp.IMul, _longType, wideLeft, wideRight); result = _module.AddInstruction( SpirvOp.UConvert, _uintType, ShiftRightLogical64( Bitcast(_ulongType, product), _module.Constant64(_ulongType, 32))); break; } case "SAndB32": result = BitwiseAnd(left, right); Store(_scc, IsNotZero(result)); break; case "SOrB32": result = _module.AddInstruction( SpirvOp.BitwiseOr, _uintType, left, right); Store(_scc, IsNotZero(result)); break; case "SXorB32": result = _module.AddInstruction( SpirvOp.BitwiseXor, _uintType, left, right); Store(_scc, IsNotZero(result)); break; case "SAndn2B32": result = BitwiseAnd( left, _module.AddInstruction(SpirvOp.Not, _uintType, right)); Store(_scc, IsNotZero(result)); break; case "SOrn2B32": result = _module.AddInstruction( SpirvOp.BitwiseOr, _uintType, left, _module.AddInstruction(SpirvOp.Not, _uintType, right)); Store(_scc, IsNotZero(result)); break; case "SNandB32": result = _module.AddInstruction( SpirvOp.Not, _uintType, BitwiseAnd(left, right)); Store(_scc, IsNotZero(result)); break; case "SNorB32": result = _module.AddInstruction( SpirvOp.Not, _uintType, _module.AddInstruction( SpirvOp.BitwiseOr, _uintType, left, right)); Store(_scc, IsNotZero(result)); break; case "SXnorB32": result = _module.AddInstruction( SpirvOp.Not, _uintType, _module.AddInstruction( SpirvOp.BitwiseXor, _uintType, left, right)); Store(_scc, IsNotZero(result)); break; case "SLshlB32": result = ShiftLeftLogical(left, right); Store(_scc, IsNotZero(result)); break; case "SLshrB32": result = ShiftRightLogical( left, BitwiseAnd(right, UInt(31))); Store(_scc, IsNotZero(result)); break; case "SAshrI32": result = ShiftRightArithmetic(left, right); Store(_scc, IsNotZero(result)); break; case "SBfmB32": result = _module.AddInstruction( SpirvOp.BitFieldInsert, _uintType, UInt(0), UInt(uint.MaxValue), BitwiseAnd(right, UInt(31)), BitwiseAnd(left, UInt(31))); break; case "SBfeU32": case "SBfeI32": { var offset = BitwiseAnd(right, UInt(31)); var requestedWidth = BitwiseAnd( ShiftRightLogical(right, UInt(16)), UInt(0x7F)); var remaining = _module.AddInstruction( SpirvOp.ISub, _uintType, UInt(32), offset); var width = Ext( 38, _uintType, requestedWidth, remaining); result = instruction.Opcode == "SBfeI32" ? Bitcast( _uintType, _module.AddInstruction( SpirvOp.BitFieldSExtract, _intType, Bitcast(_intType, left), offset, width)) : _module.AddInstruction( SpirvOp.BitFieldUExtract, _uintType, left, offset, width); Store(_scc, IsNotZero(result)); break; } case "SAbsdiffI32": { var wideLeft = _module.AddInstruction( SpirvOp.SConvert, _longType, Bitcast(_intType, left)); var wideRight = _module.AddInstruction( SpirvOp.SConvert, _longType, Bitcast(_intType, right)); var difference = _module.AddInstruction( SpirvOp.ISub, _longType, wideLeft, wideRight); result = _module.AddInstruction( SpirvOp.UConvert, _uintType, Ext(5, _longType, difference)); Store(_scc, IsNotZero(result)); break; } case "SPackLlB32B16": result = BitwiseOr( BitwiseAnd(left, UInt(0xFFFF)), ShiftLeftLogical(right, UInt(16))); break; case "SPackLhB32B16": result = BitwiseOr( BitwiseAnd(left, UInt(0xFFFF)), BitwiseAnd(right, UInt(0xFFFF0000))); break; case "SPackHhB32B16": result = BitwiseOr( ShiftRightLogical(left, UInt(16)), BitwiseAnd(right, UInt(0xFFFF0000))); break; case "SCselectB32": result = _module.AddInstruction( SpirvOp.Select, _uintType, Load(_boolType, _scc), left, right); break; case "SMinU32": result = Ext(38, _uintType, left, right); Store( _scc, _module.AddInstruction( SpirvOp.ULessThan, _boolType, left, right)); break; case "SMinI32": result = Bitcast( _uintType, Ext(39, _intType, Bitcast(_intType, left), Bitcast(_intType, right))); Store( _scc, _module.AddInstruction( SpirvOp.SLessThan, _boolType, Bitcast(_intType, left), Bitcast(_intType, right))); break; case "SMaxU32": result = Ext(41, _uintType, left, right); Store( _scc, _module.AddInstruction( SpirvOp.UGreaterThan, _boolType, left, right)); break; case "SMaxI32": result = Bitcast( _uintType, Ext(42, _intType, Bitcast(_intType, left), Bitcast(_intType, right))); Store( _scc, _module.AddInstruction( SpirvOp.SGreaterThan, _boolType, Bitcast(_intType, left), Bitcast(_intType, right))); break; case "SLshl1AddU32": case "SLshl2AddU32": case "SLshl3AddU32": case "SLshl4AddU32": { var shift = (uint)(instruction.Opcode[5] - '0'); result = IAdd( ShiftLeftLogical(left, UInt(shift)), right); break; } default: error = $"unsupported scalar opcode {instruction.Opcode}"; return false; } break; } } StoreS(destination, result); return true; } private bool TryEmitScalarCompare( Gen5ShaderInstruction instruction, out string error) { error = string.Empty; if (instruction.Sources.Count < 2) { error = "missing scalar compare source"; return false; } var left = GetRawSource(instruction, 0); var right = GetRawSource(instruction, 1); if (instruction.Opcode is "SBitcmp0B32" or "SBitcmp1B32") { var shifted = ShiftRightLogical( left, BitwiseAnd(right, UInt(31))); var isSet = IsNotZero(BitwiseAnd(shifted, UInt(1))); Store( _scc, instruction.Opcode == "SBitcmp1B32" ? isSet : _module.AddInstruction( SpirvOp.LogicalNot, _boolType, isSet)); return true; } var operation = instruction.Opcode switch { "SCmpEqI32" or "SCmpEqU32" => SpirvOp.IEqual, "SCmpLgI32" or "SCmpLgU32" => SpirvOp.INotEqual, "SCmpGtI32" => SpirvOp.SGreaterThan, "SCmpGeI32" => SpirvOp.SGreaterThanEqual, "SCmpLtI32" => SpirvOp.SLessThan, "SCmpLeI32" => SpirvOp.SLessThanEqual, "SCmpGtU32" => SpirvOp.UGreaterThan, "SCmpGeU32" => SpirvOp.UGreaterThanEqual, "SCmpLtU32" => SpirvOp.ULessThan, "SCmpLeU32" => SpirvOp.ULessThanEqual, _ => SpirvOp.Nop, }; if (operation == SpirvOp.Nop) { error = $"unsupported scalar compare {instruction.Opcode}"; return false; } if (instruction.Opcode.EndsWith("I32", StringComparison.Ordinal)) { left = Bitcast(_intType, left); right = Bitcast(_intType, right); } Store(_scc, _module.AddInstruction(operation, _boolType, left, right)); return true; } private bool TryEmitScalarCompareK( Gen5ShaderInstruction instruction, uint destination, uint immediate, out string error) { error = string.Empty; var left = LoadS(destination); var right = UInt(immediate); var operation = instruction.Opcode switch { "SCmpkEqI32" or "SCmpkEqU32" => SpirvOp.IEqual, "SCmpkLgI32" or "SCmpkLgU32" => SpirvOp.INotEqual, "SCmpkGtI32" => SpirvOp.SGreaterThan, "SCmpkGeI32" => SpirvOp.SGreaterThanEqual, "SCmpkLtI32" => SpirvOp.SLessThan, "SCmpkLeI32" => SpirvOp.SLessThanEqual, "SCmpkGtU32" => SpirvOp.UGreaterThan, "SCmpkGeU32" => SpirvOp.UGreaterThanEqual, "SCmpkLtU32" => SpirvOp.ULessThan, "SCmpkLeU32" => SpirvOp.ULessThanEqual, _ => SpirvOp.Nop, }; if (operation == SpirvOp.Nop) { error = $"unsupported scalar immediate compare {instruction.Opcode}"; return false; } if (instruction.Opcode.EndsWith("I32", StringComparison.Ordinal)) { left = Bitcast(_intType, left); right = Bitcast(_intType, right); } Store(_scc, _module.AddInstruction(operation, _boolType, left, right)); return true; } private bool TryEmitScalar64( Gen5ShaderInstruction instruction, uint destination, out string error) { error = string.Empty; var left = GetRawSource64(instruction, 0); if (instruction.Opcode.EndsWith("SaveexecB64", StringComparison.Ordinal)) { var oldExec = BooleanToLaneMask(Load(_boolType, _exec)); var notLeft = _module.AddInstruction(SpirvOp.Not, _ulongType, left); var newExec = instruction.Opcode switch { "SAndSaveexecB64" => _module.AddInstruction( SpirvOp.BitwiseAnd, _ulongType, oldExec, left), "SOrSaveexecB64" => _module.AddInstruction( SpirvOp.BitwiseOr, _ulongType, oldExec, left), "SXorSaveexecB64" => _module.AddInstruction( SpirvOp.BitwiseXor, _ulongType, oldExec, left), "SAndn2SaveexecB64" => _module.AddInstruction( SpirvOp.BitwiseAnd, _ulongType, left, _module.AddInstruction( SpirvOp.Not, _ulongType, oldExec)), "SAndn1SaveexecB64" => _module.AddInstruction( SpirvOp.BitwiseAnd, _ulongType, notLeft, oldExec), "SOrn1SaveexecB64" => _module.AddInstruction( SpirvOp.BitwiseOr, _ulongType, notLeft, oldExec), "SOrn2SaveexecB64" => _module.AddInstruction( SpirvOp.BitwiseOr, _ulongType, left, _module.AddInstruction( SpirvOp.Not, _ulongType, oldExec)), "SNandSaveexecB64" => _module.AddInstruction( SpirvOp.Not, _ulongType, _module.AddInstruction( SpirvOp.BitwiseAnd, _ulongType, left, oldExec)), "SNorSaveexecB64" => _module.AddInstruction( SpirvOp.Not, _ulongType, _module.AddInstruction( SpirvOp.BitwiseOr, _ulongType, left, oldExec)), "SXnorSaveexecB64" => _module.AddInstruction( SpirvOp.Not, _ulongType, _module.AddInstruction( SpirvOp.BitwiseXor, _ulongType, left, oldExec)), _ => 0u, }; if (newExec == 0) { error = $"unsupported scalar 64-bit opcode {instruction.Opcode}"; return false; } StoreS64(destination, oldExec); StoreS64(126, newExec); Store(_scc, IsNotZero64(newExec)); return true; } if (instruction.Opcode is "SLshlB64" or "SLshrB64") { if (instruction.Sources.Count < 2) { error = "missing scalar 64-bit shift source"; return false; } var shift = _module.AddInstruction( SpirvOp.UConvert, _ulongType, GetRawSource(instruction, 1)); var shiftedValue = instruction.Opcode == "SLshlB64" ? ShiftLeftLogical64(left, shift) : ShiftRightLogical64(left, shift); StoreS64(destination, shiftedValue); Store(_scc, IsNotZero64(shiftedValue)); return true; } if (instruction.Opcode is "SBfeU64" or "SBfeI64") { if (instruction.Sources.Count < 2) { error = "missing scalar 64-bit bitfield source"; return false; } var control = GetRawSource(instruction, 1); var offset = BitwiseAnd(control, UInt(63)); var requestedWidth = BitwiseAnd( ShiftRightLogical(control, UInt(16)), UInt(0x7F)); var remaining = _module.AddInstruction( SpirvOp.ISub, _uintType, UInt(64), offset); var width = Ext( 38, _uintType, requestedWidth, remaining); var offset64 = _module.AddInstruction( SpirvOp.UConvert, _ulongType, offset); var width64 = _module.AddInstruction( SpirvOp.UConvert, _ulongType, width); var one64 = _module.Constant64(_ulongType, 1); var shifted = ShiftRightLogical64(left, offset64); var partialMask = _module.AddInstruction( SpirvOp.ISub, _ulongType, ShiftLeftLogical64(one64, width64), one64); var fullWidth = _module.AddInstruction( SpirvOp.IEqual, _boolType, width, UInt(64)); var mask = _module.AddInstruction( SpirvOp.Select, _ulongType, fullWidth, _module.Constant64(_ulongType, ulong.MaxValue), partialMask); var extracted = _module.AddInstruction( SpirvOp.BitwiseAnd, _ulongType, shifted, mask); if (instruction.Opcode == "SBfeI64") { var signShift = _module.AddInstruction( SpirvOp.ISub, _uintType, width, UInt(1)); var signBit = ShiftLeftLogical64( one64, _module.AddInstruction( SpirvOp.UConvert, _ulongType, signShift)); var signExtended = _module.AddInstruction( SpirvOp.ISub, _ulongType, _module.AddInstruction( SpirvOp.BitwiseXor, _ulongType, extracted, signBit), signBit); extracted = _module.AddInstruction( SpirvOp.Select, _ulongType, _module.AddInstruction( SpirvOp.IEqual, _boolType, width, UInt(0)), _module.Constant64(_ulongType, 0), signExtended); } StoreS64(destination, extracted); Store(_scc, IsNotZero64(extracted)); return true; } uint value; if (instruction.Opcode is "SMovB64" or "SWqmB64") { value = left; } else if (instruction.Opcode == "SNotB64") { value = _module.AddInstruction(SpirvOp.Not, _ulongType, left); } else { if (instruction.Sources.Count < 2) { error = "missing scalar 64-bit source"; return false; } var right = GetRawSource64(instruction, 1); value = instruction.Opcode switch { "SAndB64" => _module.AddInstruction( SpirvOp.BitwiseAnd, _ulongType, left, right), "SOrB64" => _module.AddInstruction( SpirvOp.BitwiseOr, _ulongType, left, right), "SXorB64" => _module.AddInstruction( SpirvOp.BitwiseXor, _ulongType, left, right), "SNandB64" => _module.AddInstruction( SpirvOp.Not, _ulongType, _module.AddInstruction( SpirvOp.BitwiseAnd, _ulongType, left, right)), "SNorB64" => _module.AddInstruction( SpirvOp.Not, _ulongType, _module.AddInstruction( SpirvOp.BitwiseOr, _ulongType, left, right)), "SXnorB64" => _module.AddInstruction( SpirvOp.Not, _ulongType, _module.AddInstruction( SpirvOp.BitwiseXor, _ulongType, left, right)), "SAndn1B64" => _module.AddInstruction( SpirvOp.BitwiseAnd, _ulongType, _module.AddInstruction(SpirvOp.Not, _ulongType, left), right), "SAndn2B64" => _module.AddInstruction( SpirvOp.BitwiseAnd, _ulongType, left, _module.AddInstruction(SpirvOp.Not, _ulongType, right)), "SOrn1B64" => _module.AddInstruction( SpirvOp.BitwiseOr, _ulongType, _module.AddInstruction(SpirvOp.Not, _ulongType, left), right), "SOrn2B64" => _module.AddInstruction( SpirvOp.BitwiseOr, _ulongType, left, _module.AddInstruction(SpirvOp.Not, _ulongType, right)), "SCselectB64" => _module.AddInstruction( SpirvOp.Select, _ulongType, Load(_boolType, _scc), left, right), _ => 0, }; if (value == 0) { error = $"unsupported scalar 64-bit opcode {instruction.Opcode}"; return false; } } StoreS64(destination, value); if (instruction.Opcode is "SNotB64" or "SAndB64" or "SOrB64" or "SXorB64" or "SAndn1B64" or "SAndn2B64" or "SOrn1B64" or "SOrn2B64" or "SNandB64" or "SNorB64" or "SXnorB64") { Store(_scc, IsNotZero64(value)); } return true; } private uint GetRawSource( Gen5ShaderInstruction instruction, int sourceIndex) { if ((uint)sourceIndex >= instruction.Sources.Count) { throw new InvalidOperationException($"missing source {sourceIndex}"); } var operand = instruction.Sources[sourceIndex]; uint value = operand.Kind switch { Gen5OperandKind.VectorRegister => LoadV(operand.Value), Gen5OperandKind.ScalarRegister => LoadS(operand.Value), Gen5OperandKind.LiteralConstant => UInt(operand.Value), Gen5OperandKind.EncodedConstant when TryDecodeInlineConstant( operand.Value, out var inline) => UInt(inline), _ => throw new InvalidOperationException($"unsupported source {operand}"), }; if (instruction.Control is Gen5SdwaControl sdwa) { var selector = sourceIndex switch { 0 => sdwa.Source0Select, 1 => sdwa.Source1Select, _ => 6u, }; value = selector switch { 0 => BitwiseAnd(value, UInt(0xFF)), 1 => BitwiseAnd(ShiftRightLogical(value, UInt(8)), UInt(0xFF)), 2 => BitwiseAnd(ShiftRightLogical(value, UInt(16)), UInt(0xFF)), 3 => BitwiseAnd(ShiftRightLogical(value, UInt(24)), UInt(0xFF)), 4 => BitwiseAnd(value, UInt(0xFFFF)), 5 => BitwiseAnd(ShiftRightLogical(value, UInt(16)), UInt(0xFFFF)), _ => value, }; } return value; } private uint GetFloatSource( Gen5ShaderInstruction instruction, int sourceIndex) { var operand = instruction.Sources[sourceIndex]; uint value; if (operand.Kind == Gen5OperandKind.EncodedConstant && operand.Value is >= 128 and <= 192) { value = Float(operand.Value - 128); } else if (operand.Kind == Gen5OperandKind.EncodedConstant && operand.Value is >= 193 and <= 208) { value = Float(-(operand.Value - 192)); } else { value = Bitcast(_floatType, GetRawSource(instruction, sourceIndex)); } uint absoluteMask = 0; uint negateMask = 0; switch (instruction.Control) { case Gen5Vop3Control control: absoluteMask = control.AbsoluteMask; negateMask = control.NegateMask; break; case Gen5SdwaControl control: absoluteMask = control.AbsoluteMask; negateMask = control.NegateMask; break; case Gen5DppControl control: absoluteMask = control.AbsoluteMask; negateMask = control.NegateMask; break; } if ((absoluteMask & (1u << sourceIndex)) != 0) { value = Ext(4, _floatType, value); } if ((negateMask & (1u << sourceIndex)) != 0) { value = _module.AddInstruction(SpirvOp.FNegate, _floatType, value); } return value; } private uint GetRawSource64( Gen5ShaderInstruction instruction, int sourceIndex) { var operand = instruction.Sources[sourceIndex]; if (operand.Kind == Gen5OperandKind.ScalarRegister) { return LoadS64(operand.Value); } var low = GetRawSource(instruction, sourceIndex); return _module.AddInstruction(SpirvOp.UConvert, _ulongType, low); } private uint LoadS64(uint register) { var low = _module.AddInstruction(SpirvOp.UConvert, _ulongType, LoadS(register)); var high = _module.AddInstruction( SpirvOp.UConvert, _ulongType, LoadS(register + 1)); high = ShiftLeftLogical64(high, _module.Constant64(_ulongType, 32)); return _module.AddInstruction(SpirvOp.BitwiseOr, _ulongType, low, high); } private void StoreS64(uint register, uint value) { StoreS( register, _module.AddInstruction(SpirvOp.UConvert, _uintType, value)); var high = ShiftRightLogical64( value, _module.Constant64(_ulongType, 32)); StoreS( register + 1, _module.AddInstruction(SpirvOp.UConvert, _uintType, high)); } private uint EmitFloatBinary( Gen5ShaderInstruction instruction, SpirvOp operation, bool reverse = false) { var left = GetFloatSource(instruction, reverse ? 1 : 0); var right = GetFloatSource(instruction, reverse ? 0 : 1); return EmitFloatResult( instruction, _module.AddInstruction(operation, _floatType, left, right)); } private uint EmitFloatExtBinary( Gen5ShaderInstruction instruction, uint operation) => EmitFloatResult( instruction, Ext( operation, _floatType, GetFloatSource(instruction, 0), GetFloatSource(instruction, 1))); private uint EmitFloatTernaryExt( Gen5ShaderInstruction instruction, uint operation) { var first = Ext( operation, _floatType, GetFloatSource(instruction, 0), GetFloatSource(instruction, 1)); return EmitFloatResult( instruction, Ext(operation, _floatType, first, GetFloatSource(instruction, 2))); } private uint EmitIntegerBinary( Gen5ShaderInstruction instruction, SpirvOp operation, bool reverse = false) { var left = GetRawSource(instruction, reverse ? 1 : 0); var right = GetRawSource(instruction, reverse ? 0 : 1); if (operation == SpirvOp.ShiftLeftLogical) { return ShiftLeftLogical(left, right); } if (operation == SpirvOp.ShiftRightLogical) { return ShiftRightLogical(left, right); } if (operation == SpirvOp.ShiftRightArithmetic) { return ShiftRightArithmetic(left, right); } return _module.AddInstruction(operation, _uintType, left, right); } private enum CubeCoordinate { Id, Sc, Tc, Ma, } private uint EmitCvtOffF32I4(Gen5ShaderInstruction instruction) { var index = BitwiseAnd(GetRawSource(instruction, 0), UInt(15)); ReadOnlySpan table = [ 0.0f, 0.0625f, 0.1250f, 0.1875f, 0.2500f, 0.3125f, 0.3750f, 0.4375f, -0.5000f, -0.4375f, -0.3750f, -0.3125f, -0.2500f, -0.1875f, -0.1250f, -0.0625f, ]; var result = UInt(BitConverter.SingleToUInt32Bits(table[^1])); for (var tableIndex = table.Length - 2; tableIndex >= 0; tableIndex--) { var matches = _module.AddInstruction( SpirvOp.IEqual, _boolType, index, UInt((uint)tableIndex)); result = _module.AddInstruction( SpirvOp.Select, _uintType, matches, UInt(BitConverter.SingleToUInt32Bits(table[tableIndex])), result); } return result; } private uint EmitCubeCoordinate( Gen5ShaderInstruction instruction, CubeCoordinate coordinate) { var x = GetFloatSource(instruction, 0); var y = GetFloatSource(instruction, 1); var z = GetFloatSource(instruction, 2); var nx = _module.AddInstruction(SpirvOp.FNegate, _floatType, x); var ny = _module.AddInstruction(SpirvOp.FNegate, _floatType, y); var nz = _module.AddInstruction(SpirvOp.FNegate, _floatType, z); var ax = Ext(4, _floatType, x); var ay = Ext(4, _floatType, y); var az = Ext(4, _floatType, z); var amaxXY = Ext(40, _floatType, ax, ay); var amax = Ext(40, _floatType, az, amaxXY); var ma = _module.AddInstruction( SpirvOp.FMul, _floatType, Float(2), amax); if (coordinate == CubeCoordinate.Ma) { return EmitFloatResult(instruction, ma); } var isZMax = _module.AddInstruction( SpirvOp.FOrdGreaterThanEqual, _boolType, az, amaxXY); var yGreaterOrEqualX = _module.AddInstruction( SpirvOp.FOrdGreaterThanEqual, _boolType, ay, ax); var isYMax = _module.AddInstruction( SpirvOp.LogicalAnd, _boolType, _module.AddInstruction(SpirvOp.LogicalNot, _boolType, isZMax), yGreaterOrEqualX); if (coordinate == CubeCoordinate.Id) { var isZNeg = _module.AddInstruction( SpirvOp.FOrdLessThan, _boolType, z, Float(0)); var isYNeg = _module.AddInstruction( SpirvOp.FOrdLessThan, _boolType, y, Float(0)); var isXNeg = _module.AddInstruction( SpirvOp.FOrdLessThan, _boolType, x, Float(0)); var zCase = _module.AddInstruction( SpirvOp.Select, _floatType, isZNeg, Float(5), Float(4)); var yCase = _module.AddInstruction( SpirvOp.Select, _floatType, isYNeg, Float(3), Float(2)); var xCase = _module.AddInstruction( SpirvOp.Select, _floatType, isXNeg, Float(1), Float(0)); var xyCase = _module.AddInstruction( SpirvOp.Select, _floatType, yGreaterOrEqualX, yCase, xCase); return EmitFloatResult( instruction, _module.AddInstruction( SpirvOp.Select, _floatType, isZMax, zCase, xyCase)); } if (coordinate == CubeCoordinate.Sc) { var isZNeg = _module.AddInstruction( SpirvOp.FOrdLessThan, _boolType, z, Float(0)); var isXNeg = _module.AddInstruction( SpirvOp.FOrdLessThan, _boolType, x, Float(0)); var zCase = _module.AddInstruction( SpirvOp.Select, _floatType, isZNeg, nx, x); var xCase = _module.AddInstruction( SpirvOp.Select, _floatType, isXNeg, z, nz); var nonZCase = _module.AddInstruction( SpirvOp.Select, _floatType, isYMax, x, xCase); return EmitFloatResult( instruction, _module.AddInstruction( SpirvOp.Select, _floatType, isZMax, zCase, nonZCase)); } var tcIsYNeg = _module.AddInstruction( SpirvOp.FOrdLessThan, _boolType, y, Float(0)); var tcYCase = _module.AddInstruction( SpirvOp.Select, _floatType, tcIsYNeg, nz, z); return EmitFloatResult( instruction, _module.AddInstruction( SpirvOp.Select, _floatType, isYMax, tcYCase, ny)); } private uint EmitAddWithCarry(Gen5ShaderInstruction instruction) { var left = GetRawSource(instruction, 0); var right = GetRawSource(instruction, 1); var carryIn = _module.AddInstruction( SpirvOp.Select, _uintType, Load(_boolType, _vcc), UInt(1), UInt(0)); var partial = IAdd(left, right); var result = IAdd(partial, carryIn); var carry = _module.AddInstruction( SpirvOp.LogicalOr, _boolType, _module.AddInstruction(SpirvOp.ULessThan, _boolType, partial, left), _module.AddInstruction(SpirvOp.ULessThan, _boolType, result, partial)); StoreWaveMask(106, carry); return result; } private uint EmitSubtractWithBorrow( Gen5ShaderInstruction instruction, bool reverse) { var left = GetRawSource(instruction, reverse ? 1 : 0); var right = GetRawSource(instruction, reverse ? 0 : 1); var borrowIn = _module.AddInstruction( SpirvOp.Select, _uintType, Load(_boolType, _vcc), UInt(1), UInt(0)); var partial = _module.AddInstruction(SpirvOp.ISub, _uintType, left, right); var result = _module.AddInstruction( SpirvOp.ISub, _uintType, partial, borrowIn); var borrow = _module.AddInstruction( SpirvOp.LogicalOr, _boolType, _module.AddInstruction(SpirvOp.ULessThan, _boolType, left, right), _module.AddInstruction( SpirvOp.ULessThan, _boolType, partial, borrowIn)); StoreWaveMask(106, borrow); return result; } private void StoreCarryOut( Gen5ShaderInstruction instruction, uint carry) { if (instruction.Control is Gen5Vop3Control { ScalarDestination: { } register }) { StoreS( register, _module.AddInstruction( SpirvOp.Select, _uintType, carry, UInt(1), UInt(0))); if (register == 106) { StoreWaveMask(106, carry); } return; } StoreWaveMask(106, carry); } private uint EmitPermlane16( Gen5ShaderInstruction instruction, bool exchangeRows) { var value = GetRawSource(instruction, 0); var selectorLow = GetRawSource(instruction, 1); var selectorHigh = GetRawSource(instruction, 2); var lane = Load(_uintType, _subgroupInvocationIdInput); var localLane = BitwiseAnd(lane, UInt(15)); var lowHalf = _module.AddInstruction( SpirvOp.ULessThan, _boolType, localLane, UInt(8)); var lowShift = ShiftLeftLogical(localLane, UInt(2)); var highLane = _module.AddInstruction( SpirvOp.ISub, _uintType, localLane, UInt(8)); var highShift = ShiftLeftLogical(highLane, UInt(2)); var lowSelector = BitwiseAnd( ShiftRightLogical(selectorLow, lowShift), UInt(15)); var highSelector = BitwiseAnd( ShiftRightLogical(selectorHigh, highShift), UInt(15)); var selector = _module.AddInstruction( SpirvOp.Select, _uintType, lowHalf, lowSelector, highSelector); var rowBase = BitwiseAnd(lane, UInt(0xFFFF_FFF0)); if (exchangeRows) { rowBase = BitwiseXor(rowBase, UInt(16)); } var targetLane = IAdd(rowBase, selector); return _module.AddInstruction( SpirvOp.GroupNonUniformShuffle, _uintType, UInt(3), value, targetLane); } private uint EmitFloatResult( Gen5ShaderInstruction instruction, uint value) { uint outputModifier = 0; var clamp = false; switch (instruction.Control) { case Gen5Vop3Control control: outputModifier = control.OutputModifier; clamp = control.Clamp; break; case Gen5SdwaControl control: outputModifier = control.OutputModifier; clamp = control.Clamp; break; } value = outputModifier switch { 1 => _module.AddInstruction(SpirvOp.FMul, _floatType, value, Float(2)), 2 => _module.AddInstruction(SpirvOp.FMul, _floatType, value, Float(4)), 3 => _module.AddInstruction(SpirvOp.FMul, _floatType, value, Float(0.5f)), _ => value, }; if (clamp) { value = Ext(43, _floatType, value, Float(0), Float(1)); } return Bitcast(_uintType, value); } private uint TruncateFloat32ForPack(uint value) { var raw = BitwiseAnd( Bitcast(_uintType, value), UInt(0xFFFF_E000)); return Bitcast(_floatType, raw); } private uint Ext(uint operation, uint resultType, params uint[] operands) { var values = new uint[2 + operands.Length]; values[0] = _glsl; values[1] = operation; operands.CopyTo(values, 2); return _module.AddInstruction(SpirvOp.ExtInst, resultType, values); } private uint IsNotZero(uint value) => _module.AddInstruction(SpirvOp.INotEqual, _boolType, value, UInt(0)); private uint IsNotZero64(uint value) => _module.AddInstruction( SpirvOp.INotEqual, _boolType, value, _module.Constant64(_ulongType, 0)); private uint SignBit(uint value) => ShiftRightLogical(value, UInt(31)); private uint SignedAddOverflow(uint left, uint right, uint result) { var leftSign = SignBit(left); var rightSign = SignBit(right); var resultSign = SignBit(result); var sameSourceSign = _module.AddInstruction( SpirvOp.IEqual, _boolType, leftSign, rightSign); var resultSignChanged = _module.AddInstruction( SpirvOp.INotEqual, _boolType, leftSign, resultSign); return _module.AddInstruction( SpirvOp.LogicalAnd, _boolType, sameSourceSign, resultSignChanged); } private uint SignedSubOverflow(uint left, uint right, uint result) { var leftSign = SignBit(left); var rightSign = SignBit(right); var resultSign = SignBit(result); var differentSourceSign = _module.AddInstruction( SpirvOp.INotEqual, _boolType, leftSign, rightSign); var resultSignChanged = _module.AddInstruction( SpirvOp.INotEqual, _boolType, leftSign, resultSign); return _module.AddInstruction( SpirvOp.LogicalAnd, _boolType, differentSourceSign, resultSignChanged); } private static bool TryDecodeInlineConstant(uint encoded, out uint value) { if (encoded == 125) { value = 0; return true; } if (encoded is >= 128 and <= 192) { value = encoded - 128; return true; } if (encoded is >= 193 and <= 208) { value = unchecked((uint)-(int)(encoded - 192)); return true; } var floatingPoint = encoded switch { 240 => 0.5f, 241 => -0.5f, 242 => 1.0f, 243 => -1.0f, 244 => 2.0f, 245 => -2.0f, 246 => 4.0f, 247 => -4.0f, 248 => 1.0f / (2.0f * MathF.PI), _ => float.NaN, }; if (float.IsNaN(floatingPoint)) { value = 0; return false; } value = BitConverter.SingleToUInt32Bits(floatingPoint); return true; } } }