mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-05 01:19:44 +08:00
[AGC] Decode VOP3P and emit packed f16 arithmetic (first slice) (#145)
* [AGC] Decode VOP3P and emit packed f16 arithmetic (first slice) On gfx10 the VOP3P family lives under its own 0b110011000 prefix (word0 top byte 0xCC), which the major-opcode switch currently routes to the SMEM branch, so packed instructions were decoded as scalar memory ops and emitted as silent no-ops. Intercept the exact 9-bit prefix ahead of the switch, decode the five packed-f16 arithmetic opcodes with their op_sel/op_sel_hi/neg_lo/neg_hi/clamp modifiers, and emit them as UnpackHalf2x16 -> component-wise f32 vec2 ops -> PackHalf2x16 so no Float16 capability is needed. Bit layout and opcode numbers pinned to LLVM MC test encodings (vop3p.s, gfx10_vop3p_literalv216.txt) and VOP3PInstructions.td. Unsupported modifiers, packed constants and out-of-scope packed opcodes fail with a clear error instead of emitting wrong results. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * [AGC] Make packed f16 exact and drop v_pk_fma_f16 (review response) Address the FP16 correctness review on the VOP3P slice. Replace GLSL UnpackHalf2x16/PackHalf2x16 with explicit integer f16<->f32 conversions (EmitHalfToFloat/EmitFloatToHalf): exact widening with subnormal normalisation, and narrowing with round-to-nearest-even, overflow-to-Inf and NaN/Inf handling. Their subnormal and rounding behaviour no longer depends on implementation-defined float-controls modes. With exact conversions, v_pk_add_f16 and v_pk_mul_f16 are bit-exact to a true f16 op (f32 result rounds losslessly to f16; a f16 product fits in f32). Emit v_pk_min_f16/v_pk_max_f16 as fminnum_like/fmaxnum_like (NaN operand returns the other; ordered numeric compare) instead of GLSL FMin/FMax. v_pk_fma_f16 now fails emission loudly: a fused f16 FMA rounds once, an f32 multiply-add then pack double-rounds (fma(0x4100,0x7522,0x04EA) is 0x7A6B fused vs 0x7A6A via f32). Exact fused emulation is a planned follow-up slice. ShaderDump gains an Expect model (Translates/DecodeFails/EmitFails) and packed regressions: arith, non-default modifiers, and loud-failure pins for the fma case above and for clamp. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: tensorcrush <tensorcrush@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Antigravity AI <antigravity@gemini.com>
This commit is contained in:
@@ -948,6 +948,27 @@ public static partial class Gen5SpirvTranslator
|
|||||||
vector);
|
vector);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "VPkAddF16":
|
||||||
|
case "VPkMulF16":
|
||||||
|
case "VPkMinF16":
|
||||||
|
case "VPkMaxF16":
|
||||||
|
if (!TryEmitPackedF16(instruction, out result, out error))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "VPkFmaF16":
|
||||||
|
// Deliberately loud: a fused f16 FMA rounds the product+add once,
|
||||||
|
// whereas doing the multiply-add in f32 and rounding to f16 at the
|
||||||
|
// end double-rounds. Concrete miss: fma(0x4100, 0x7522, 0x04EA) is
|
||||||
|
// 0x7A6B fused but 0x7A6A via f32. Exact emulation (round-to-odd
|
||||||
|
// f32 product then RNE pack) is a planned follow-up slice.
|
||||||
|
error =
|
||||||
|
$"unsupported vop3p opcode {instruction.Opcode} " +
|
||||||
|
"(fused f16 FMA requires single-rounding; deferred to a later slice)";
|
||||||
|
return false;
|
||||||
default:
|
default:
|
||||||
error = $"unsupported vector opcode {instruction.Opcode}";
|
error = $"unsupported vector opcode {instruction.Opcode}";
|
||||||
return false;
|
return false;
|
||||||
@@ -976,6 +997,213 @@ public static partial class Gen5SpirvTranslator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Packed f16 (VOP3P) arithmetic. Each source register holds two f16 values,
|
||||||
|
// one per result lane. Every f16<->f32 conversion is done with the explicit
|
||||||
|
// integer sequences below (EmitHalfToFloat / EmitFloatToHalf) instead of
|
||||||
|
// GLSL UnpackHalf2x16 / PackHalf2x16, whose subnormal and rounding behaviour
|
||||||
|
// is implementation-defined without float-controls execution modes. The two
|
||||||
|
// lanes are computed independently: each operand half is widened exactly to
|
||||||
|
// f32, op_sel/op_sel_hi pick the source half and neg_lo/neg_hi negate it, the
|
||||||
|
// op runs in f32, and the result is rounded back to f16 with round-to-nearest-
|
||||||
|
// even. For add and mul this is bit-exact to a true f16 op (the f32 result
|
||||||
|
// rounds losslessly to f16 by the double-rounding theorem; a f16 product even
|
||||||
|
// fits in f32 exactly). min/max carry no rounding, so they are exact once the
|
||||||
|
// conversions are. v_pk_fma_f16 is intentionally not routed here because a
|
||||||
|
// fused f16 FMA cannot be reproduced by an f32 multiply-add plus a pack.
|
||||||
|
private bool TryEmitPackedF16(
|
||||||
|
Gen5ShaderInstruction instruction,
|
||||||
|
out uint result,
|
||||||
|
out string error)
|
||||||
|
{
|
||||||
|
result = 0;
|
||||||
|
error = string.Empty;
|
||||||
|
if (instruction.Control is not Gen5Vop3pControl control)
|
||||||
|
{
|
||||||
|
error = $"missing vop3p control for {instruction.Opcode}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (control.Clamp)
|
||||||
|
{
|
||||||
|
error = $"unsupported vop3p modifiers (clamp) for {instruction.Opcode}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var index = 0; index < 2; index++)
|
||||||
|
{
|
||||||
|
var source = instruction.Sources[index];
|
||||||
|
if (source.Kind is not (Gen5OperandKind.VectorRegister or Gen5OperandKind.ScalarRegister))
|
||||||
|
{
|
||||||
|
error =
|
||||||
|
$"unsupported vop3p operand {source} for {instruction.Opcode} (first slice: registers only)";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var low = EmitPackedF16Lane(instruction, control, highLane: false);
|
||||||
|
var high = EmitPackedF16Lane(instruction, control, highLane: true);
|
||||||
|
result = BitwiseOr(low, ShiftLeftLogical(high, UInt(16)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computes one result lane (low or high) as a packed 16-bit f16 value.
|
||||||
|
private uint EmitPackedF16Lane(
|
||||||
|
Gen5ShaderInstruction instruction,
|
||||||
|
Gen5Vop3pControl control,
|
||||||
|
bool highLane)
|
||||||
|
{
|
||||||
|
var left = EmitPackedF16Operand(instruction, control, 0, highLane);
|
||||||
|
var right = EmitPackedF16Operand(instruction, control, 1, highLane);
|
||||||
|
var value = instruction.Opcode switch
|
||||||
|
{
|
||||||
|
"VPkAddF16" => _module.AddInstruction(SpirvOp.FAdd, _floatType, left, right),
|
||||||
|
"VPkMulF16" => _module.AddInstruction(SpirvOp.FMul, _floatType, left, right),
|
||||||
|
"VPkMinF16" => EmitPackedF16MinMax(left, right, isMax: false),
|
||||||
|
"VPkMaxF16" => EmitPackedF16MinMax(left, right, isMax: true),
|
||||||
|
_ => left,
|
||||||
|
};
|
||||||
|
return EmitFloatToHalf(Bitcast(_uintType, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads source `index`, selects the half feeding this lane (op_sel / op_sel_hi),
|
||||||
|
// widens it exactly to f32 and applies the lane's negate modifier (neg_lo / neg_hi).
|
||||||
|
private uint EmitPackedF16Operand(
|
||||||
|
Gen5ShaderInstruction instruction,
|
||||||
|
Gen5Vop3pControl control,
|
||||||
|
int index,
|
||||||
|
bool highLane)
|
||||||
|
{
|
||||||
|
var raw = GetRawSource(instruction, index);
|
||||||
|
var selectMask = highLane ? control.OpSelHiMask : control.OpSelMask;
|
||||||
|
var half = ((selectMask >> index) & 1) != 0
|
||||||
|
? ShiftRightLogical(raw, UInt(16))
|
||||||
|
: raw;
|
||||||
|
var value = Bitcast(_floatType, EmitHalfToFloat(half));
|
||||||
|
var negateMask = highLane ? control.NegHiMask : control.NegLoMask;
|
||||||
|
if (((negateMask >> index) & 1) != 0)
|
||||||
|
{
|
||||||
|
value = _module.AddInstruction(SpirvOp.FNegate, _floatType, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fminnum_like / fmaxnum_like: if one operand is NaN return the other; if both
|
||||||
|
// are NaN return a NaN; otherwise the ordered smaller/larger. The ordering of
|
||||||
|
// -0/+0 is unspecified under these opcodes, so the ordered compare is enough.
|
||||||
|
private uint EmitPackedF16MinMax(uint left, uint right, bool isMax)
|
||||||
|
{
|
||||||
|
var compare = _module.AddInstruction(
|
||||||
|
isMax ? SpirvOp.FOrdGreaterThan : SpirvOp.FOrdLessThan,
|
||||||
|
_boolType,
|
||||||
|
left,
|
||||||
|
right);
|
||||||
|
var numeric = _module.AddInstruction(
|
||||||
|
SpirvOp.Select, _floatType, compare, left, right);
|
||||||
|
var leftNan = _module.AddInstruction(SpirvOp.IsNan, _boolType, left);
|
||||||
|
var rightNan = _module.AddInstruction(SpirvOp.IsNan, _boolType, right);
|
||||||
|
var withRight = _module.AddInstruction(
|
||||||
|
SpirvOp.Select, _floatType, rightNan, left, numeric);
|
||||||
|
return _module.AddInstruction(
|
||||||
|
SpirvOp.Select, _floatType, leftNan, right, withRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widens an f16 value held in the low 16 bits of `halfBits` to an f32 bit
|
||||||
|
// pattern, exactly (subnormals normalised, Inf/NaN and signed zero preserved).
|
||||||
|
// Mirrors the branchless HalfToFloat reference validated against System.Half.
|
||||||
|
private uint EmitHalfToFloat(uint halfBits)
|
||||||
|
{
|
||||||
|
var sign = ShiftLeftLogical(BitwiseAnd(halfBits, UInt(0x8000)), UInt(16));
|
||||||
|
var exponent = BitwiseAnd(ShiftRightLogical(halfBits, UInt(10)), UInt(0x1F));
|
||||||
|
var mantissa = BitwiseAnd(halfBits, UInt(0x3FF));
|
||||||
|
|
||||||
|
var normal = BitwiseOr(
|
||||||
|
ShiftLeftLogical(IAdd(exponent, UInt(112)), UInt(23)),
|
||||||
|
ShiftLeftLogical(mantissa, UInt(13)));
|
||||||
|
var infinityNan = BitwiseOr(UInt(0x7F80_0000), ShiftLeftLogical(mantissa, UInt(13)));
|
||||||
|
|
||||||
|
// Subnormal: normalise the mantissa. FindUMsb of (mantissa | 1) keeps the
|
||||||
|
// op defined when mantissa is 0; that lane is discarded by the select below.
|
||||||
|
var highBit = Ext(75, _uintType, BitwiseOr(mantissa, UInt(1)));
|
||||||
|
var shift = ISubU(UInt(23), highBit);
|
||||||
|
var subFraction = BitwiseAnd(ShiftLeftLogical(mantissa, shift), UInt(0x7F_FFFF));
|
||||||
|
var subnormal = SelectU(
|
||||||
|
IsNotZero(mantissa),
|
||||||
|
BitwiseOr(ShiftLeftLogical(IAdd(highBit, UInt(103)), UInt(23)), subFraction),
|
||||||
|
UInt(0));
|
||||||
|
|
||||||
|
var magnitude = SelectU(
|
||||||
|
Equal(exponent, 0),
|
||||||
|
subnormal,
|
||||||
|
SelectU(Equal(exponent, 31), infinityNan, normal));
|
||||||
|
return BitwiseOr(sign, magnitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Narrows an f32 bit pattern to an f16 value in the low 16 bits, rounding to
|
||||||
|
// nearest even (subnormals, overflow-to-Inf and NaN/Inf handled). Mirrors the
|
||||||
|
// branchless FloatToHalf reference validated exhaustively against System.Half.
|
||||||
|
private uint EmitFloatToHalf(uint bits)
|
||||||
|
{
|
||||||
|
var sign = BitwiseAnd(ShiftRightLogical(bits, UInt(16)), UInt(0x8000));
|
||||||
|
var absolute = BitwiseAnd(bits, UInt(0x7FFF_FFFF));
|
||||||
|
|
||||||
|
var isInfinityNan = UCmp(SpirvOp.UGreaterThanEqual, absolute, UInt(0x7F80_0000));
|
||||||
|
var isNan = UCmp(SpirvOp.UGreaterThan, absolute, UInt(0x7F80_0000));
|
||||||
|
var infinityNan = BitwiseOr(
|
||||||
|
BitwiseOr(sign, UInt(0x7C00)),
|
||||||
|
SelectU(isNan, UInt(0x200), UInt(0)));
|
||||||
|
|
||||||
|
var exponent = ShiftRightLogical(absolute, UInt(23));
|
||||||
|
var mantissa = BitwiseAnd(absolute, UInt(0x7F_FFFF));
|
||||||
|
var significand = BitwiseOr(mantissa, UInt(0x80_0000));
|
||||||
|
|
||||||
|
// Normal path: round the 24-bit significand down to 11 bits (>> 13) with
|
||||||
|
// round-to-nearest-even; the carry folds naturally into the exponent.
|
||||||
|
var roundBit = BitwiseAnd(ShiftRightLogical(significand, UInt(13)), UInt(1));
|
||||||
|
var rounded = ShiftRightLogical(IAdd(IAdd(significand, UInt(0xFFF)), roundBit), UInt(13));
|
||||||
|
var halfExponent = ISubU(exponent, UInt(112));
|
||||||
|
var normalBits = IAdd(ShiftLeftLogical(halfExponent, UInt(10)), ISubU(rounded, UInt(0x400)));
|
||||||
|
var normal = SelectU(
|
||||||
|
UCmp(SpirvOp.UGreaterThanEqual, exponent, UInt(113)),
|
||||||
|
SelectU(UCmp(SpirvOp.UGreaterThanEqual, normalBits, UInt(0x7C00)), UInt(0x7C00), normalBits),
|
||||||
|
UInt(0));
|
||||||
|
|
||||||
|
// Subnormal path: value = round(significand >> (126 - exponent)) with RNE.
|
||||||
|
// The shift is clamped to 25 so it stays defined; on this path it is >= 14.
|
||||||
|
var distance = ISubU(UInt(126), exponent);
|
||||||
|
var shift = SelectU(UCmp(SpirvOp.UGreaterThan, distance, UInt(25)), UInt(25), distance);
|
||||||
|
var shiftMask = ISubU(ShiftLeftLogical(UInt(1), shift), UInt(1));
|
||||||
|
var halfWay = ShiftLeftLogical(UInt(1), ISubU(shift, UInt(1)));
|
||||||
|
var lowBits = BitwiseAnd(significand, shiftMask);
|
||||||
|
var quotient = ShiftRightLogical(significand, shift);
|
||||||
|
var roundUp = _module.AddInstruction(
|
||||||
|
SpirvOp.LogicalOr,
|
||||||
|
_boolType,
|
||||||
|
UCmp(SpirvOp.UGreaterThan, lowBits, halfWay),
|
||||||
|
_module.AddInstruction(
|
||||||
|
SpirvOp.LogicalAnd,
|
||||||
|
_boolType,
|
||||||
|
Equal(lowBits, halfWay),
|
||||||
|
IsNotZero(BitwiseAnd(quotient, UInt(1)))));
|
||||||
|
var subnormal = IAdd(quotient, SelectU(roundUp, UInt(1), UInt(0)));
|
||||||
|
|
||||||
|
var isSubnormal = UCmp(SpirvOp.ULessThanEqual, exponent, UInt(112));
|
||||||
|
var finite = SelectU(isSubnormal, subnormal, normal);
|
||||||
|
return SelectU(isInfinityNan, infinityNan, BitwiseOr(sign, finite));
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint SelectU(uint condition, uint whenTrue, uint whenFalse) =>
|
||||||
|
_module.AddInstruction(SpirvOp.Select, _uintType, condition, whenTrue, whenFalse);
|
||||||
|
|
||||||
|
private uint UCmp(SpirvOp operation, uint left, uint right) =>
|
||||||
|
_module.AddInstruction(operation, _boolType, left, right);
|
||||||
|
|
||||||
|
private uint Equal(uint value, uint constant) =>
|
||||||
|
_module.AddInstruction(SpirvOp.IEqual, _boolType, value, UInt(constant));
|
||||||
|
|
||||||
|
private uint ISubU(uint left, uint right) =>
|
||||||
|
_module.AddInstruction(SpirvOp.ISub, _uintType, left, right);
|
||||||
|
|
||||||
private bool TryEmitVectorCompare(
|
private bool TryEmitVectorCompare(
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
out string error)
|
out string error)
|
||||||
|
|||||||
@@ -237,6 +237,17 @@ public sealed record Gen5SdwaControl(
|
|||||||
bool Clamp,
|
bool Clamp,
|
||||||
uint? ScalarDestination) : Gen5InstructionControl;
|
uint? ScalarDestination) : Gen5InstructionControl;
|
||||||
|
|
||||||
|
// Packed (VOP3P) source and destination modifiers. Each mask holds one bit per
|
||||||
|
// source operand. OpSel/OpSelHi pick which 16-bit half of a source feeds the low
|
||||||
|
// and high result lanes respectively; NegLo/NegHi negate the value routed to each
|
||||||
|
// lane. Clamp saturates each output half to [0, 1].
|
||||||
|
public sealed record Gen5Vop3pControl(
|
||||||
|
uint OpSelMask,
|
||||||
|
uint OpSelHiMask,
|
||||||
|
uint NegLoMask,
|
||||||
|
uint NegHiMask,
|
||||||
|
bool Clamp) : Gen5InstructionControl;
|
||||||
|
|
||||||
public sealed record Gen5DppControl(
|
public sealed record Gen5DppControl(
|
||||||
uint Control,
|
uint Control,
|
||||||
bool FetchInactive,
|
bool FetchInactive,
|
||||||
|
|||||||
@@ -562,6 +562,22 @@ public static class Gen5ShaderTranslator
|
|||||||
return DecodeSop(word, out name, out sizeDwords, out error);
|
return DecodeSop(word, out name, out sizeDwords, out error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gfx10 moved VOP3P (packed 16-bit math) to its own 0b110011000 prefix
|
||||||
|
// (word0 top byte 0xCC), separate from the VOP3 block. Match the full
|
||||||
|
// 9-bit prefix here, before the coarse major-opcode switch, so packed
|
||||||
|
// instructions are not misread as one of the neighbouring encodings.
|
||||||
|
if ((word & 0xFF800000u) == 0xCC000000u)
|
||||||
|
{
|
||||||
|
encoding = Gen5ShaderEncoding.Vop3p;
|
||||||
|
if (!ctx.TryReadUInt32(baseAddress + pc + sizeof(uint), out var vop3pExtra))
|
||||||
|
{
|
||||||
|
error = $"vop3p-extra-read-failed pc=0x{pc:X}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodeVop3p(word, vop3pExtra, out name, out sizeDwords, out error);
|
||||||
|
}
|
||||||
|
|
||||||
switch (word >> 26)
|
switch (word >> 26)
|
||||||
{
|
{
|
||||||
case 0x33:
|
case 0x33:
|
||||||
@@ -1160,6 +1176,37 @@ public static class Gen5ShaderTranslator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool DecodeVop3p(
|
||||||
|
uint word,
|
||||||
|
uint extra,
|
||||||
|
out string name,
|
||||||
|
out uint sizeDwords,
|
||||||
|
out string error)
|
||||||
|
{
|
||||||
|
var opcode = (word >> 16) & 0x7F;
|
||||||
|
var src0 = extra & 0x1FF;
|
||||||
|
var src1 = (extra >> 9) & 0x1FF;
|
||||||
|
var src2 = (extra >> 18) & 0x1FF;
|
||||||
|
sizeDwords = src0 == 0xFF || src1 == 0xFF || src2 == 0xFF ? 3u : 2u;
|
||||||
|
error = string.Empty;
|
||||||
|
|
||||||
|
// Opcode numbers taken from LLVM's AMDGPU VOP3PInstructions.td and the
|
||||||
|
// gfx9/gfx10 MC test encodings; they are unchanged across gfx9 and gfx10.
|
||||||
|
// Unhandled packed opcodes (integer, fma_mix, ...) stay opaque here and
|
||||||
|
// fail loudly at emission rather than being silently mis-emitted.
|
||||||
|
name = opcode switch
|
||||||
|
{
|
||||||
|
0x0E => "VPkFmaF16",
|
||||||
|
0x0F => "VPkAddF16",
|
||||||
|
0x10 => "VPkMulF16",
|
||||||
|
0x11 => "VPkMinF16",
|
||||||
|
0x12 => "VPkMaxF16",
|
||||||
|
_ => $"Vop3pRaw{opcode:X2}",
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool DecodeDs(
|
private static bool DecodeDs(
|
||||||
uint word,
|
uint word,
|
||||||
out string name,
|
out string name,
|
||||||
@@ -1839,6 +1886,28 @@ public static class Gen5ShaderTranslator
|
|||||||
isVop3B ? (word >> 8) & 0x7F : null);
|
isVop3B ? (word >> 8) & 0x7F : null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Gen5ShaderEncoding.Vop3p:
|
||||||
|
{
|
||||||
|
var extra = words[1];
|
||||||
|
sources =
|
||||||
|
[
|
||||||
|
Gen5Operand.Source(extra & 0x1FF, literal),
|
||||||
|
Gen5Operand.Source((extra >> 9) & 0x1FF, literal),
|
||||||
|
Gen5Operand.Source((extra >> 18) & 0x1FF, literal),
|
||||||
|
];
|
||||||
|
destinations = [Gen5Operand.Vector(word & 0xFF)];
|
||||||
|
|
||||||
|
// op_sel_hi is split across both dwords: bits [1:0] live in word1
|
||||||
|
// [28:27], bit [2] in word0 [14].
|
||||||
|
var opSelHi = ((extra >> 27) & 0x3) | (((word >> 14) & 0x1) << 2);
|
||||||
|
control = new Gen5Vop3pControl(
|
||||||
|
(word >> 11) & 0x7,
|
||||||
|
opSelHi,
|
||||||
|
(extra >> 29) & 0x7,
|
||||||
|
(word >> 8) & 0x7,
|
||||||
|
((word >> 15) & 1) != 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case Gen5ShaderEncoding.Ds:
|
case Gen5ShaderEncoding.Ds:
|
||||||
{
|
{
|
||||||
var extra = words[1];
|
var extra = words[1];
|
||||||
|
|||||||
Reference in New Issue
Block a user