mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-30 13:24:19 +08:00
shader: add compact f16 arithmetic and compare lowering (#840)
Co-authored-by: Foued Attar <attar.foued@gmail.com>
This commit is contained in:
@@ -144,11 +144,35 @@ public static partial class Gen5MslTranslator
|
|||||||
|
|
||||||
// ---- float arithmetic ----
|
// ---- float arithmetic ----
|
||||||
"VAddF32" => FloatResult(instruction, $"{F(instruction, 0)} + {F(instruction, 1)}"),
|
"VAddF32" => FloatResult(instruction, $"{F(instruction, 0)} + {F(instruction, 1)}"),
|
||||||
|
"VAddF16" => Float16Result(
|
||||||
|
instruction,
|
||||||
|
destination,
|
||||||
|
$"{F16(instruction, 0)} + {F16(instruction, 1)}"),
|
||||||
"VSubF32" => FloatResult(instruction, $"{F(instruction, 0)} - {F(instruction, 1)}"),
|
"VSubF32" => FloatResult(instruction, $"{F(instruction, 0)} - {F(instruction, 1)}"),
|
||||||
"VSubrevF32" => FloatResult(instruction, $"{F(instruction, 1)} - {F(instruction, 0)}"),
|
"VSubrevF32" => FloatResult(instruction, $"{F(instruction, 1)} - {F(instruction, 0)}"),
|
||||||
|
"VSubF16" => Float16Result(
|
||||||
|
instruction,
|
||||||
|
destination,
|
||||||
|
$"{F16(instruction, 0)} - {F16(instruction, 1)}"),
|
||||||
|
"VSubrevF16" => Float16Result(
|
||||||
|
instruction,
|
||||||
|
destination,
|
||||||
|
$"{F16(instruction, 1)} - {F16(instruction, 0)}"),
|
||||||
"VMulF32" => FloatResult(instruction, $"{F(instruction, 0)} * {F(instruction, 1)}"),
|
"VMulF32" => FloatResult(instruction, $"{F(instruction, 0)} * {F(instruction, 1)}"),
|
||||||
|
"VMulF16" => Float16Result(
|
||||||
|
instruction,
|
||||||
|
destination,
|
||||||
|
$"{F16(instruction, 0)} * {F16(instruction, 1)}"),
|
||||||
"VMinF32" => FloatResult(instruction, $"fmin({F(instruction, 0)}, {F(instruction, 1)})"),
|
"VMinF32" => FloatResult(instruction, $"fmin({F(instruction, 0)}, {F(instruction, 1)})"),
|
||||||
"VMaxF32" => FloatResult(instruction, $"fmax({F(instruction, 0)}, {F(instruction, 1)})"),
|
"VMaxF32" => FloatResult(instruction, $"fmax({F(instruction, 0)}, {F(instruction, 1)})"),
|
||||||
|
"VMinF16" => Float16Result(
|
||||||
|
instruction,
|
||||||
|
destination,
|
||||||
|
$"fmin({F16(instruction, 0)}, {F16(instruction, 1)})"),
|
||||||
|
"VMaxF16" => Float16Result(
|
||||||
|
instruction,
|
||||||
|
destination,
|
||||||
|
$"fmax({F16(instruction, 0)}, {F16(instruction, 1)})"),
|
||||||
// The decoder normalizes mk/ak literal placement, so every MAD/FMA
|
// The decoder normalizes mk/ak literal placement, so every MAD/FMA
|
||||||
// form is fma(src0, src1, src2) exactly like the SPIR-V translator.
|
// form is fma(src0, src1, src2) exactly like the SPIR-V translator.
|
||||||
"VFmaF32" or "VMadF32" or "VMadAkF32" or "VMadMkF32" or "VFmaAkF32" or "VFmaMkF32" =>
|
"VFmaF32" or "VMadF32" or "VMadAkF32" or "VMadMkF32" or "VFmaAkF32" or "VFmaMkF32" =>
|
||||||
@@ -578,23 +602,46 @@ public static partial class Gen5MslTranslator
|
|||||||
{
|
{
|
||||||
condition = EmitCompareClass(instruction);
|
condition = EmitCompareClass(instruction);
|
||||||
}
|
}
|
||||||
else if (opcode is "VCmpTruF32" or "VCmpxTruF32" or "VCmpTI32" or "VCmpTU32")
|
else if (opcode is
|
||||||
|
"VCmpTruF32" or "VCmpxTruF32" or
|
||||||
|
"VCmpTruF16" or "VCmpxTruF16" or
|
||||||
|
"VCmpTI32" or "VCmpTU32")
|
||||||
{
|
{
|
||||||
condition = "true";
|
condition = "true";
|
||||||
}
|
}
|
||||||
else if (opcode is "VCmpFF32" or "VCmpxFF32" or "VCmpFI32" or "VCmpFU32")
|
else if (opcode is
|
||||||
|
"VCmpFF32" or "VCmpxFF32" or
|
||||||
|
"VCmpFF16" or "VCmpxFF16" or
|
||||||
|
"VCmpFI32" or "VCmpFU32")
|
||||||
{
|
{
|
||||||
condition = "false";
|
condition = "false";
|
||||||
}
|
}
|
||||||
else if (opcode is "VCmpOF32" or "VCmpxOF32")
|
else if (opcode is
|
||||||
|
"VCmpOF32" or "VCmpxOF32" or
|
||||||
|
"VCmpOF16" or "VCmpxOF16")
|
||||||
{
|
{
|
||||||
condition = $"(!isnan({F(instruction, 0)}) && !isnan({F(instruction, 1)}))";
|
var left = opcode.EndsWith("F16", StringComparison.Ordinal)
|
||||||
|
? F16(instruction, 0)
|
||||||
|
: F(instruction, 0);
|
||||||
|
var right = opcode.EndsWith("F16", StringComparison.Ordinal)
|
||||||
|
? F16(instruction, 1)
|
||||||
|
: F(instruction, 1);
|
||||||
|
condition = $"(!isnan({left}) && !isnan({right}))";
|
||||||
}
|
}
|
||||||
else if (opcode is "VCmpUF32" or "VCmpxUF32")
|
else if (opcode is
|
||||||
|
"VCmpUF32" or "VCmpxUF32" or
|
||||||
|
"VCmpUF16" or "VCmpxUF16")
|
||||||
{
|
{
|
||||||
condition = $"(isnan({F(instruction, 0)}) || isnan({F(instruction, 1)}))";
|
var left = opcode.EndsWith("F16", StringComparison.Ordinal)
|
||||||
|
? F16(instruction, 0)
|
||||||
|
: F(instruction, 0);
|
||||||
|
var right = opcode.EndsWith("F16", StringComparison.Ordinal)
|
||||||
|
? F16(instruction, 1)
|
||||||
|
: F(instruction, 1);
|
||||||
|
condition = $"(isnan({left}) || isnan({right}))";
|
||||||
}
|
}
|
||||||
else if (opcode.EndsWith("F32", StringComparison.Ordinal))
|
else if (opcode.EndsWith("F32", StringComparison.Ordinal) ||
|
||||||
|
opcode.EndsWith("F16", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
// Ordered compares are the plain C operators (false on NaN);
|
// Ordered compares are the plain C operators (false on NaN);
|
||||||
// the Nxx forms are their unordered negations (true on NaN).
|
// the Nxx forms are their unordered negations (true on NaN).
|
||||||
@@ -620,7 +667,13 @@ public static partial class Gen5MslTranslator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var comparison = $"({F(instruction, 0)} {op} {F(instruction, 1)})";
|
var left = opcode.EndsWith("F16", StringComparison.Ordinal)
|
||||||
|
? F16(instruction, 0)
|
||||||
|
: F(instruction, 0);
|
||||||
|
var right = opcode.EndsWith("F16", StringComparison.Ordinal)
|
||||||
|
? F16(instruction, 1)
|
||||||
|
: F(instruction, 1);
|
||||||
|
var comparison = $"({left} {op} {right})";
|
||||||
condition = unordered ? $"(!{comparison})" : comparison;
|
condition = unordered ? $"(!{comparison})" : comparison;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1570,6 +1623,80 @@ public static partial class Gen5MslTranslator
|
|||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads the selected 16-bit half as a widened float.</summary>
|
||||||
|
private string F16(Gen5ShaderInstruction instruction, int sourceIndex)
|
||||||
|
{
|
||||||
|
var operand = instruction.Sources[sourceIndex];
|
||||||
|
string expression;
|
||||||
|
if (operand.Kind == Gen5OperandKind.EncodedConstant &&
|
||||||
|
Gen5InlineConstants.TryDecode(operand.Value, out var inline))
|
||||||
|
{
|
||||||
|
expression = operand.Value switch
|
||||||
|
{
|
||||||
|
>= 128 and <= 192 => $"{operand.Value - 128}.0f",
|
||||||
|
>= 193 and <= 208 => $"(-{operand.Value - 192}.0f)",
|
||||||
|
_ => AsFloat(FormatUInt(inline)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var raw = RawSource(
|
||||||
|
instruction,
|
||||||
|
sourceIndex,
|
||||||
|
applySdwaIntegerModifiers: false);
|
||||||
|
var shift = instruction.Control is Gen5Vop3Control control &&
|
||||||
|
(control.OperandSelect & (1u << sourceIndex)) != 0
|
||||||
|
? 16
|
||||||
|
: 0;
|
||||||
|
expression =
|
||||||
|
$"(float)as_type<half>((ushort)((({raw}) >> {shift}) & 0xFFFFu))";
|
||||||
|
}
|
||||||
|
|
||||||
|
var (absoluteMask, negateMask) = instruction.Control switch
|
||||||
|
{
|
||||||
|
Gen5Vop3Control control => (control.AbsoluteMask, control.NegateMask),
|
||||||
|
Gen5SdwaControl control => (control.AbsoluteMask, control.NegateMask),
|
||||||
|
Gen5DppControl control => (control.AbsoluteMask, control.NegateMask),
|
||||||
|
_ => (0u, 0u),
|
||||||
|
};
|
||||||
|
if ((absoluteMask & (1u << sourceIndex)) != 0)
|
||||||
|
{
|
||||||
|
expression = $"fabs({expression})";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((negateMask & (1u << sourceIndex)) != 0)
|
||||||
|
{
|
||||||
|
expression = $"(-{expression})";
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Rounds to f16 and preserves the unselected VGPR half.</summary>
|
||||||
|
private string Float16Result(
|
||||||
|
Gen5ShaderInstruction instruction,
|
||||||
|
uint destination,
|
||||||
|
string expression)
|
||||||
|
{
|
||||||
|
var control = instruction.Control as Gen5Vop3Control;
|
||||||
|
expression = (control?.OutputModifier ?? 0) switch
|
||||||
|
{
|
||||||
|
1 => $"(({expression}) * 2.0f)",
|
||||||
|
2 => $"(({expression}) * 4.0f)",
|
||||||
|
3 => $"(({expression}) * 0.5f)",
|
||||||
|
_ => expression,
|
||||||
|
};
|
||||||
|
if (control?.Clamp == true)
|
||||||
|
{
|
||||||
|
expression = $"clamp({expression}, 0.0f, 1.0f)";
|
||||||
|
}
|
||||||
|
|
||||||
|
var packed = $"(uint)as_type<ushort>(half({expression}))";
|
||||||
|
return ((control?.OperandSelect ?? 0) & 8) != 0
|
||||||
|
? $"((v[{destination}] & 0x0000FFFFu) | (({packed}) << 16))"
|
||||||
|
: $"((v[{destination}] & 0xFFFF0000u) | ({packed}))";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wraps a float expression with VOP3/SDWA output modifiers and clamp,
|
/// Wraps a float expression with VOP3/SDWA output modifiers and clamp,
|
||||||
/// then bitcasts back to the register file's uint domain.
|
/// then bitcasts back to the register file's uint domain.
|
||||||
|
|||||||
@@ -340,21 +340,43 @@ public static partial class Gen5SpirvTranslator
|
|||||||
case "VAddF32":
|
case "VAddF32":
|
||||||
result = EmitFloatBinary(instruction, SpirvOp.FAdd);
|
result = EmitFloatBinary(instruction, SpirvOp.FAdd);
|
||||||
break;
|
break;
|
||||||
|
case "VAddF16":
|
||||||
|
result = EmitFloat16Binary(instruction, destination, SpirvOp.FAdd);
|
||||||
|
break;
|
||||||
case "VSubF32":
|
case "VSubF32":
|
||||||
result = EmitFloatBinary(instruction, SpirvOp.FSub);
|
result = EmitFloatBinary(instruction, SpirvOp.FSub);
|
||||||
break;
|
break;
|
||||||
case "VSubrevF32":
|
case "VSubrevF32":
|
||||||
result = EmitFloatBinary(instruction, SpirvOp.FSub, reverse: true);
|
result = EmitFloatBinary(instruction, SpirvOp.FSub, reverse: true);
|
||||||
break;
|
break;
|
||||||
|
case "VSubF16":
|
||||||
|
result = EmitFloat16Binary(instruction, destination, SpirvOp.FSub);
|
||||||
|
break;
|
||||||
|
case "VSubrevF16":
|
||||||
|
result = EmitFloat16Binary(
|
||||||
|
instruction,
|
||||||
|
destination,
|
||||||
|
SpirvOp.FSub,
|
||||||
|
reverse: true);
|
||||||
|
break;
|
||||||
case "VMulF32":
|
case "VMulF32":
|
||||||
result = EmitFloatBinary(instruction, SpirvOp.FMul);
|
result = EmitFloatBinary(instruction, SpirvOp.FMul);
|
||||||
break;
|
break;
|
||||||
|
case "VMulF16":
|
||||||
|
result = EmitFloat16Binary(instruction, destination, SpirvOp.FMul);
|
||||||
|
break;
|
||||||
case "VMinF32":
|
case "VMinF32":
|
||||||
result = EmitFloatExtBinary(instruction, 37);
|
result = EmitFloatExtBinary(instruction, 37);
|
||||||
break;
|
break;
|
||||||
case "VMaxF32":
|
case "VMaxF32":
|
||||||
result = EmitFloatExtBinary(instruction, 40);
|
result = EmitFloatExtBinary(instruction, 40);
|
||||||
break;
|
break;
|
||||||
|
case "VMinF16":
|
||||||
|
result = EmitFloat16ExtBinary(instruction, destination, 37);
|
||||||
|
break;
|
||||||
|
case "VMaxF16":
|
||||||
|
result = EmitFloat16ExtBinary(instruction, destination, 40);
|
||||||
|
break;
|
||||||
case "VMadF32":
|
case "VMadF32":
|
||||||
case "VFmaF32":
|
case "VFmaF32":
|
||||||
case "VMadMkF32":
|
case "VMadMkF32":
|
||||||
@@ -1609,29 +1631,72 @@ public static partial class Gen5SpirvTranslator
|
|||||||
condition,
|
condition,
|
||||||
SignedClass(0x020, 0x040, zero));
|
SignedClass(0x020, 0x040, zero));
|
||||||
}
|
}
|
||||||
else if (opcode is "VCmpFF32" or "VCmpxFF32" or "VCmpFI32" or "VCmpFU32")
|
else if (opcode is
|
||||||
|
"VCmpFF32" or "VCmpxFF32" or
|
||||||
|
"VCmpFF16" or "VCmpxFF16" or
|
||||||
|
"VCmpFI32" or "VCmpFU32")
|
||||||
{
|
{
|
||||||
condition = _module.ConstantBool(false);
|
condition = _module.ConstantBool(false);
|
||||||
}
|
}
|
||||||
else if (opcode is "VCmpTruF32" or "VCmpxTruF32" or "VCmpTI32" or "VCmpTU32")
|
else if (opcode is
|
||||||
|
"VCmpTruF32" or "VCmpxTruF32" or
|
||||||
|
"VCmpTruF16" or "VCmpxTruF16" or
|
||||||
|
"VCmpTI32" or "VCmpTU32")
|
||||||
{
|
{
|
||||||
condition = _module.ConstantBool(true);
|
condition = _module.ConstantBool(true);
|
||||||
}
|
}
|
||||||
else if (opcode is
|
else if (opcode is
|
||||||
"VCmpOF32" or "VCmpxOF32" or
|
"VCmpOF32" or "VCmpxOF32" or
|
||||||
"VCmpUF32" or "VCmpxUF32")
|
"VCmpUF32" or "VCmpxUF32" or
|
||||||
|
"VCmpOF16" or "VCmpxOF16" or
|
||||||
|
"VCmpUF16" or "VCmpxUF16")
|
||||||
{
|
{
|
||||||
var left = GetFloatSource(instruction, 0);
|
var isHalf = opcode.EndsWith("F16", StringComparison.Ordinal);
|
||||||
var right = GetFloatSource(instruction, 1);
|
var left = isHalf
|
||||||
|
? GetFloat16Source(instruction, 0)
|
||||||
|
: GetFloatSource(instruction, 0);
|
||||||
|
var right = isHalf
|
||||||
|
? GetFloat16Source(instruction, 1)
|
||||||
|
: GetFloatSource(instruction, 1);
|
||||||
var unordered = _module.AddInstruction(
|
var unordered = _module.AddInstruction(
|
||||||
SpirvOp.LogicalOr,
|
SpirvOp.LogicalOr,
|
||||||
_boolType,
|
_boolType,
|
||||||
_module.AddInstruction(SpirvOp.IsNan, _boolType, left),
|
_module.AddInstruction(SpirvOp.IsNan, _boolType, left),
|
||||||
_module.AddInstruction(SpirvOp.IsNan, _boolType, right));
|
_module.AddInstruction(SpirvOp.IsNan, _boolType, right));
|
||||||
condition = opcode is "VCmpUF32" or "VCmpxUF32"
|
condition = opcode is
|
||||||
|
"VCmpUF32" or "VCmpxUF32" or
|
||||||
|
"VCmpUF16" or "VCmpxUF16"
|
||||||
? unordered
|
? unordered
|
||||||
: _module.AddInstruction(SpirvOp.LogicalNot, _boolType, unordered);
|
: _module.AddInstruction(SpirvOp.LogicalNot, _boolType, unordered);
|
||||||
}
|
}
|
||||||
|
else if (opcode.EndsWith("F16", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
var left = GetFloat16Source(instruction, 0);
|
||||||
|
var right = GetFloat16Source(instruction, 1);
|
||||||
|
var operation = opcode switch
|
||||||
|
{
|
||||||
|
"VCmpLtF16" or "VCmpxLtF16" => SpirvOp.FOrdLessThan,
|
||||||
|
"VCmpEqF16" or "VCmpxEqF16" => SpirvOp.FOrdEqual,
|
||||||
|
"VCmpLeF16" or "VCmpxLeF16" => SpirvOp.FOrdLessThanEqual,
|
||||||
|
"VCmpGtF16" or "VCmpxGtF16" => SpirvOp.FOrdGreaterThan,
|
||||||
|
"VCmpLgF16" or "VCmpxLgF16" => SpirvOp.FOrdNotEqual,
|
||||||
|
"VCmpGeF16" or "VCmpxGeF16" => SpirvOp.FOrdGreaterThanEqual,
|
||||||
|
"VCmpNeqF16" or "VCmpxNeqF16" => SpirvOp.FUnordNotEqual,
|
||||||
|
"VCmpNltF16" or "VCmpxNltF16" => SpirvOp.FUnordGreaterThanEqual,
|
||||||
|
"VCmpNleF16" or "VCmpxNleF16" => SpirvOp.FUnordGreaterThan,
|
||||||
|
"VCmpNgtF16" or "VCmpxNgtF16" => SpirvOp.FUnordLessThanEqual,
|
||||||
|
"VCmpNgeF16" or "VCmpxNgeF16" => SpirvOp.FUnordLessThan,
|
||||||
|
"VCmpNlgF16" or "VCmpxNlgF16" => SpirvOp.FUnordEqual,
|
||||||
|
_ => SpirvOp.Nop,
|
||||||
|
};
|
||||||
|
if (operation == SpirvOp.Nop)
|
||||||
|
{
|
||||||
|
error = $"unsupported half compare {opcode}";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
condition = _module.AddInstruction(operation, _boolType, left, right);
|
||||||
|
}
|
||||||
else if (opcode is not ("VCmpClassF32" or "VCmpxClassF32") &&
|
else if (opcode is not ("VCmpClassF32" or "VCmpxClassF32") &&
|
||||||
opcode.EndsWith("F32", StringComparison.Ordinal))
|
opcode.EndsWith("F32", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
@@ -3108,6 +3173,70 @@ public static partial class Gen5SpirvTranslator
|
|||||||
sourceAllowsWrite));
|
sourceAllowsWrite));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private uint GetFloat16Source(
|
||||||
|
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 if (operand.Kind == Gen5OperandKind.EncodedConstant &&
|
||||||
|
Gen5InlineConstants.TryDecode(operand.Value, out var inline))
|
||||||
|
{
|
||||||
|
value = Bitcast(_floatType, UInt(inline));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var raw = GetRawSource(
|
||||||
|
instruction,
|
||||||
|
sourceIndex,
|
||||||
|
applySdwaIntegerModifiers: false);
|
||||||
|
if (instruction.Control is Gen5Vop3Control control &&
|
||||||
|
(control.OperandSelect & (1u << sourceIndex)) != 0)
|
||||||
|
{
|
||||||
|
raw = ShiftRightLogical(raw, UInt(16));
|
||||||
|
}
|
||||||
|
|
||||||
|
value = Bitcast(_floatType, EmitHalfToFloat(raw));
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (negateMask & (1u << sourceIndex)) != 0
|
||||||
|
? _module.AddInstruction(SpirvOp.FNegate, _floatType, value)
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
|
||||||
private uint GetFloatSource(
|
private uint GetFloatSource(
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
int sourceIndex)
|
int sourceIndex)
|
||||||
@@ -3232,6 +3361,33 @@ public static partial class Gen5SpirvTranslator
|
|||||||
_module.AddInstruction(SpirvOp.UConvert, _uintType, high));
|
_module.AddInstruction(SpirvOp.UConvert, _uintType, high));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private uint EmitFloat16Binary(
|
||||||
|
Gen5ShaderInstruction instruction,
|
||||||
|
uint destination,
|
||||||
|
SpirvOp operation,
|
||||||
|
bool reverse = false)
|
||||||
|
{
|
||||||
|
var left = GetFloat16Source(instruction, reverse ? 1 : 0);
|
||||||
|
var right = GetFloat16Source(instruction, reverse ? 0 : 1);
|
||||||
|
return EmitFloat16Result(
|
||||||
|
instruction,
|
||||||
|
destination,
|
||||||
|
_module.AddInstruction(operation, _floatType, left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint EmitFloat16ExtBinary(
|
||||||
|
Gen5ShaderInstruction instruction,
|
||||||
|
uint destination,
|
||||||
|
uint operation) =>
|
||||||
|
EmitFloat16Result(
|
||||||
|
instruction,
|
||||||
|
destination,
|
||||||
|
Ext(
|
||||||
|
operation,
|
||||||
|
_floatType,
|
||||||
|
GetFloat16Source(instruction, 0),
|
||||||
|
GetFloat16Source(instruction, 1)));
|
||||||
|
|
||||||
private uint EmitFloatBinary(
|
private uint EmitFloatBinary(
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
SpirvOp operation,
|
SpirvOp operation,
|
||||||
@@ -3753,6 +3909,35 @@ public static partial class Gen5SpirvTranslator
|
|||||||
UInt(0));
|
UInt(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private uint EmitFloat16Result(
|
||||||
|
Gen5ShaderInstruction instruction,
|
||||||
|
uint destination,
|
||||||
|
uint value)
|
||||||
|
{
|
||||||
|
var control = instruction.Control as Gen5Vop3Control;
|
||||||
|
value = (control?.OutputModifier ?? 0) 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 (control?.Clamp == true)
|
||||||
|
{
|
||||||
|
value = Ext(43, _floatType, value, Float(0), Float(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
var half = EmitFloatToHalf(Bitcast(_uintType, value));
|
||||||
|
var current = LoadV(destination);
|
||||||
|
return ((control?.OperandSelect ?? 0) & 8) != 0
|
||||||
|
? BitwiseOr(
|
||||||
|
BitwiseAnd(current, UInt(0x0000_FFFF)),
|
||||||
|
ShiftLeftLogical(half, UInt(16)))
|
||||||
|
: BitwiseOr(
|
||||||
|
BitwiseAnd(current, UInt(0xFFFF_0000)),
|
||||||
|
half);
|
||||||
|
}
|
||||||
|
|
||||||
private uint EmitFloatResult(
|
private uint EmitFloatResult(
|
||||||
Gen5ShaderInstruction instruction,
|
Gen5ShaderInstruction instruction,
|
||||||
uint value)
|
uint value)
|
||||||
|
|||||||
@@ -1015,6 +1015,12 @@ public static class Gen5ShaderTranslator
|
|||||||
0x2F => "VCvtPkrtzF16F32",
|
0x2F => "VCvtPkrtzF16F32",
|
||||||
0x30 => "VCvtPkU16U32",
|
0x30 => "VCvtPkU16U32",
|
||||||
0x31 => "VCvtPkI16I32",
|
0x31 => "VCvtPkI16I32",
|
||||||
|
0x32 => "VAddF16",
|
||||||
|
0x33 => "VSubF16",
|
||||||
|
0x34 => "VSubrevF16",
|
||||||
|
0x35 => "VMulF16",
|
||||||
|
0x39 => "VMaxF16",
|
||||||
|
0x3A => "VMinF16",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1086,6 +1092,14 @@ public static class Gen5ShaderTranslator
|
|||||||
0xC5 => "VCmpNeU32",
|
0xC5 => "VCmpNeU32",
|
||||||
0xC6 => "VCmpGeU32",
|
0xC6 => "VCmpGeU32",
|
||||||
0xC7 => "VCmpTU32",
|
0xC7 => "VCmpTU32",
|
||||||
|
0xC8 => "VCmpFF16",
|
||||||
|
0xC9 => "VCmpLtF16",
|
||||||
|
0xCA => "VCmpEqF16",
|
||||||
|
0xCB => "VCmpLeF16",
|
||||||
|
0xCC => "VCmpGtF16",
|
||||||
|
0xCD => "VCmpLgF16",
|
||||||
|
0xCE => "VCmpGeF16",
|
||||||
|
0xCF => "VCmpOF16",
|
||||||
0xD0 => "VCmpxFU32",
|
0xD0 => "VCmpxFU32",
|
||||||
0xD1 => "VCmpxLtU32",
|
0xD1 => "VCmpxLtU32",
|
||||||
0xD2 => "VCmpxEqU32",
|
0xD2 => "VCmpxEqU32",
|
||||||
@@ -1094,6 +1108,30 @@ public static class Gen5ShaderTranslator
|
|||||||
0xD5 => "VCmpxNeU32",
|
0xD5 => "VCmpxNeU32",
|
||||||
0xD6 => "VCmpxGeU32",
|
0xD6 => "VCmpxGeU32",
|
||||||
0xD7 => "VCmpxTU32",
|
0xD7 => "VCmpxTU32",
|
||||||
|
0xD8 => "VCmpxFF16",
|
||||||
|
0xD9 => "VCmpxLtF16",
|
||||||
|
0xDA => "VCmpxEqF16",
|
||||||
|
0xDB => "VCmpxLeF16",
|
||||||
|
0xDC => "VCmpxGtF16",
|
||||||
|
0xDD => "VCmpxLgF16",
|
||||||
|
0xDE => "VCmpxGeF16",
|
||||||
|
0xDF => "VCmpxOF16",
|
||||||
|
0xE8 => "VCmpUF16",
|
||||||
|
0xE9 => "VCmpNgeF16",
|
||||||
|
0xEA => "VCmpNlgF16",
|
||||||
|
0xEB => "VCmpNgtF16",
|
||||||
|
0xEC => "VCmpNleF16",
|
||||||
|
0xED => "VCmpNeqF16",
|
||||||
|
0xEE => "VCmpNltF16",
|
||||||
|
0xEF => "VCmpTruF16",
|
||||||
|
0xF8 => "VCmpxUF16",
|
||||||
|
0xF9 => "VCmpxNgeF16",
|
||||||
|
0xFA => "VCmpxNlgF16",
|
||||||
|
0xFB => "VCmpxNgtF16",
|
||||||
|
0xFC => "VCmpxNleF16",
|
||||||
|
0xFD => "VCmpxNeqF16",
|
||||||
|
0xFE => "VCmpxNltF16",
|
||||||
|
0xFF => "VCmpxTruF16",
|
||||||
_ => string.Empty,
|
_ => string.Empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.ShaderCompiler;
|
||||||
|
using SharpEmu.ShaderCompiler.Metal;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.ShaderCompiler.Metal.Tests;
|
||||||
|
|
||||||
|
public sealed class Gen5MslF16CompareTests
|
||||||
|
{
|
||||||
|
public static TheoryData<string> Opcodes = new()
|
||||||
|
{
|
||||||
|
"VCmpFF16",
|
||||||
|
"VCmpLtF16",
|
||||||
|
"VCmpEqF16",
|
||||||
|
"VCmpLeF16",
|
||||||
|
"VCmpGtF16",
|
||||||
|
"VCmpLgF16",
|
||||||
|
"VCmpGeF16",
|
||||||
|
"VCmpOF16",
|
||||||
|
"VCmpxFF16",
|
||||||
|
"VCmpxLtF16",
|
||||||
|
"VCmpxEqF16",
|
||||||
|
"VCmpxLeF16",
|
||||||
|
"VCmpxGtF16",
|
||||||
|
"VCmpxLgF16",
|
||||||
|
"VCmpxGeF16",
|
||||||
|
"VCmpxOF16",
|
||||||
|
"VCmpUF16",
|
||||||
|
"VCmpNgeF16",
|
||||||
|
"VCmpNlgF16",
|
||||||
|
"VCmpNgtF16",
|
||||||
|
"VCmpNleF16",
|
||||||
|
"VCmpNeqF16",
|
||||||
|
"VCmpNltF16",
|
||||||
|
"VCmpTruF16",
|
||||||
|
"VCmpxUF16",
|
||||||
|
"VCmpxNgeF16",
|
||||||
|
"VCmpxNlgF16",
|
||||||
|
"VCmpxNgtF16",
|
||||||
|
"VCmpxNleF16",
|
||||||
|
"VCmpxNeqF16",
|
||||||
|
"VCmpxNltF16",
|
||||||
|
"VCmpxTruF16",
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Opcodes))]
|
||||||
|
public void F16CompareOpcodeLowersToMsl(string opcode)
|
||||||
|
{
|
||||||
|
var compare = new Gen5ShaderInstruction(
|
||||||
|
0,
|
||||||
|
Gen5ShaderEncoding.Vopc,
|
||||||
|
opcode,
|
||||||
|
[0u],
|
||||||
|
[Gen5Operand.Vector(0), Gen5Operand.Vector(1)],
|
||||||
|
[],
|
||||||
|
null);
|
||||||
|
var state = new Gen5ShaderState(
|
||||||
|
new Gen5ShaderProgram(0x1000, [compare]),
|
||||||
|
[],
|
||||||
|
null);
|
||||||
|
var scalars = new uint[256];
|
||||||
|
var evaluation = new Gen5ShaderEvaluation(scalars, scalars, [], []);
|
||||||
|
|
||||||
|
Assert.True(
|
||||||
|
Gen5MslTranslator.TryCompileComputeShader(
|
||||||
|
state,
|
||||||
|
evaluation,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
out var shader,
|
||||||
|
out var error),
|
||||||
|
error);
|
||||||
|
Assert.NotEmpty(shader.Source);
|
||||||
|
if (opcode is not (
|
||||||
|
"VCmpFF16" or "VCmpxFF16" or
|
||||||
|
"VCmpTruF16" or "VCmpxTruF16"))
|
||||||
|
{
|
||||||
|
Assert.Contains("half", shader.Source, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.ShaderCompiler.Metal.Tests;
|
||||||
|
|
||||||
|
public sealed class MslFloat16ArithmeticTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void CompactFloat16ArithmeticUsesHalfOperandsAndPreservesRegisterShape()
|
||||||
|
{
|
||||||
|
var fixture = new Gen5ComputeFixture(
|
||||||
|
"compact-f16-arithmetic",
|
||||||
|
[
|
||||||
|
0x64000501,
|
||||||
|
0x66060B04,
|
||||||
|
0x680C1107,
|
||||||
|
0x6A12170A,
|
||||||
|
0x72181D0D,
|
||||||
|
0x741E2310,
|
||||||
|
0xBF810000,
|
||||||
|
],
|
||||||
|
StoreScalarResourceBase: 0,
|
||||||
|
StoreBackingBytes: 0);
|
||||||
|
|
||||||
|
var shader = Gen5ComputeFixtures.CompileOrThrow(fixture);
|
||||||
|
|
||||||
|
Assert.Contains("as_type<half>", shader.Source, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("fmin(", shader.Source, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("fmax(", shader.Source, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("& 0xFFFF0000u", shader.Source, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.ShaderCompiler.Vulkan;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.ShaderCompiler.Tests;
|
||||||
|
|
||||||
|
public sealed class Gen5Float16ArithmeticTests
|
||||||
|
{
|
||||||
|
private const ulong ShaderAddress = 0x1_0000_0000;
|
||||||
|
private const uint SEndpgm = 0xBF810000;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CompactFloat16ArithmeticDecodesAndCompilesWithoutNativeFloat16()
|
||||||
|
{
|
||||||
|
var program = Decode(
|
||||||
|
[
|
||||||
|
0x64000501, // v_add_f16 v0, v1, v2
|
||||||
|
0x66060B04, // v_sub_f16 v3, v4, v5
|
||||||
|
0x680C1107, // v_subrev_f16 v6, v7, v8
|
||||||
|
0x6A12170A, // v_mul_f16 v9, v10, v11
|
||||||
|
0x72181D0D, // v_max_f16 v12, v13, v14
|
||||||
|
0x741E2310, // v_min_f16 v15, v16, v17
|
||||||
|
SEndpgm,
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assert.Equal(
|
||||||
|
["VAddF16", "VSubF16", "VSubrevF16", "VMulF16", "VMaxF16", "VMinF16", "SEndpgm"],
|
||||||
|
program.Instructions.Select(instruction => instruction.Opcode));
|
||||||
|
|
||||||
|
var state = new Gen5ShaderState(program, [], null);
|
||||||
|
var scalarRegisters = new uint[256];
|
||||||
|
var evaluation = new Gen5ShaderEvaluation(
|
||||||
|
scalarRegisters,
|
||||||
|
scalarRegisters,
|
||||||
|
[],
|
||||||
|
[]);
|
||||||
|
|
||||||
|
Assert.True(
|
||||||
|
Gen5SpirvTranslator.TryCompileComputeShader(
|
||||||
|
state,
|
||||||
|
evaluation,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
out var shader,
|
||||||
|
out var error),
|
||||||
|
error);
|
||||||
|
|
||||||
|
var opcodes = ReadOpcodes(shader.Spirv);
|
||||||
|
Assert.Contains((ushort)SpirvOp.FAdd, opcodes);
|
||||||
|
Assert.Contains((ushort)SpirvOp.FSub, opcodes);
|
||||||
|
Assert.Contains((ushort)SpirvOp.FMul, opcodes);
|
||||||
|
Assert.True(opcodes.Count(opcode => opcode == (ushort)SpirvOp.ExtInst) >= 2);
|
||||||
|
Assert.DoesNotContain((ushort)SpirvCapability.Float16, ReadCapabilities(shader.Spirv));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Gen5ShaderProgram Decode(IReadOnlyList<uint> words)
|
||||||
|
{
|
||||||
|
var memory = new TestCpuMemory(ShaderAddress, words.Count * sizeof(uint));
|
||||||
|
var bytes = new byte[words.Count * sizeof(uint)];
|
||||||
|
for (var index = 0; index < words.Count; index++)
|
||||||
|
{
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(
|
||||||
|
bytes.AsSpan(index * sizeof(uint)),
|
||||||
|
words[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(memory.TryWrite(ShaderAddress, bytes));
|
||||||
|
var context = new CpuContext(memory, Generation.Gen5);
|
||||||
|
Assert.True(
|
||||||
|
Gen5ShaderTranslator.TryDecodeProgram(
|
||||||
|
context,
|
||||||
|
ShaderAddress,
|
||||||
|
out var program,
|
||||||
|
out var error),
|
||||||
|
error);
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<ushort> ReadOpcodes(byte[] spirv) =>
|
||||||
|
ReadInstructions(spirv)
|
||||||
|
.Select(instruction => instruction.Opcode)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
private static IReadOnlyList<ushort> ReadCapabilities(byte[] spirv) =>
|
||||||
|
ReadInstructions(spirv)
|
||||||
|
.Where(instruction => instruction.Opcode == (ushort)SpirvOp.Capability)
|
||||||
|
.Select(instruction => (ushort)instruction.FirstOperand)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
private static IReadOnlyList<(ushort Opcode, uint FirstOperand)> ReadInstructions(
|
||||||
|
byte[] spirv)
|
||||||
|
{
|
||||||
|
Assert.Equal(0x07230203u, BinaryPrimitives.ReadUInt32LittleEndian(spirv));
|
||||||
|
var instructions = new List<(ushort Opcode, uint FirstOperand)>();
|
||||||
|
for (var offset = 5 * sizeof(uint); offset < spirv.Length;)
|
||||||
|
{
|
||||||
|
var header = BinaryPrimitives.ReadUInt32LittleEndian(spirv.AsSpan(offset));
|
||||||
|
var wordCount = checked((int)(header >> 16));
|
||||||
|
Assert.InRange(wordCount, 1, (spirv.Length - offset) / sizeof(uint));
|
||||||
|
var firstOperand = wordCount > 1
|
||||||
|
? BinaryPrimitives.ReadUInt32LittleEndian(spirv.AsSpan(offset + sizeof(uint)))
|
||||||
|
: 0;
|
||||||
|
instructions.Add(((ushort)header, firstOperand));
|
||||||
|
offset += wordCount * sizeof(uint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestCpuMemory(ulong baseAddress, int size) : ICpuMemory
|
||||||
|
{
|
||||||
|
private readonly byte[] _storage = new byte[size];
|
||||||
|
|
||||||
|
public bool TryRead(ulong virtualAddress, Span<byte> destination)
|
||||||
|
{
|
||||||
|
if (!TryResolve(virtualAddress, destination.Length, out var offset))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_storage.AsSpan(offset, destination.Length).CopyTo(destination);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source)
|
||||||
|
{
|
||||||
|
if (!TryResolve(virtualAddress, source.Length, out var offset))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.CopyTo(_storage.AsSpan(offset, source.Length));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryResolve(ulong address, int length, out int offset)
|
||||||
|
{
|
||||||
|
offset = 0;
|
||||||
|
if (address < baseAddress || address - baseAddress > int.MaxValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = (int)(address - baseAddress);
|
||||||
|
return offset <= _storage.Length - length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using SharpEmu.HLE;
|
||||||
|
using SharpEmu.ShaderCompiler;
|
||||||
|
using SharpEmu.ShaderCompiler.Vulkan;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.ShaderCompiler.Tests;
|
||||||
|
|
||||||
|
public sealed class Gen5VopcF16Tests
|
||||||
|
{
|
||||||
|
private const ulong ShaderAddress = 0x1_0000_0000;
|
||||||
|
private const uint SEndpgm = 0xBF810000;
|
||||||
|
|
||||||
|
public static TheoryData<uint, string> Opcodes = new()
|
||||||
|
{
|
||||||
|
{ 0xC8, "VCmpFF16" },
|
||||||
|
{ 0xC9, "VCmpLtF16" },
|
||||||
|
{ 0xCA, "VCmpEqF16" },
|
||||||
|
{ 0xCB, "VCmpLeF16" },
|
||||||
|
{ 0xCC, "VCmpGtF16" },
|
||||||
|
{ 0xCD, "VCmpLgF16" },
|
||||||
|
{ 0xCE, "VCmpGeF16" },
|
||||||
|
{ 0xCF, "VCmpOF16" },
|
||||||
|
{ 0xD8, "VCmpxFF16" },
|
||||||
|
{ 0xD9, "VCmpxLtF16" },
|
||||||
|
{ 0xDA, "VCmpxEqF16" },
|
||||||
|
{ 0xDB, "VCmpxLeF16" },
|
||||||
|
{ 0xDC, "VCmpxGtF16" },
|
||||||
|
{ 0xDD, "VCmpxLgF16" },
|
||||||
|
{ 0xDE, "VCmpxGeF16" },
|
||||||
|
{ 0xDF, "VCmpxOF16" },
|
||||||
|
{ 0xE8, "VCmpUF16" },
|
||||||
|
{ 0xE9, "VCmpNgeF16" },
|
||||||
|
{ 0xEA, "VCmpNlgF16" },
|
||||||
|
{ 0xEB, "VCmpNgtF16" },
|
||||||
|
{ 0xEC, "VCmpNleF16" },
|
||||||
|
{ 0xED, "VCmpNeqF16" },
|
||||||
|
{ 0xEE, "VCmpNltF16" },
|
||||||
|
{ 0xEF, "VCmpTruF16" },
|
||||||
|
{ 0xF8, "VCmpxUF16" },
|
||||||
|
{ 0xF9, "VCmpxNgeF16" },
|
||||||
|
{ 0xFA, "VCmpxNlgF16" },
|
||||||
|
{ 0xFB, "VCmpxNgtF16" },
|
||||||
|
{ 0xFC, "VCmpxNleF16" },
|
||||||
|
{ 0xFD, "VCmpxNeqF16" },
|
||||||
|
{ 0xFE, "VCmpxNltF16" },
|
||||||
|
{ 0xFF, "VCmpxTruF16" },
|
||||||
|
};
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Opcodes))]
|
||||||
|
public void F16CompareOpcodeDecodes(uint opcode, string expectedName)
|
||||||
|
{
|
||||||
|
var memory = new TestCpuMemory(ShaderAddress, 0x100);
|
||||||
|
Span<byte> shader = stackalloc byte[2 * sizeof(uint)];
|
||||||
|
var word = (0x3Eu << 25) | (opcode << 17) | (1u << 9);
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(shader, word);
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(shader[sizeof(uint)..], SEndpgm);
|
||||||
|
Assert.True(memory.TryWrite(ShaderAddress, shader));
|
||||||
|
|
||||||
|
var ctx = new CpuContext(memory, Generation.Gen5);
|
||||||
|
Assert.True(
|
||||||
|
Gen5ShaderTranslator.TryDecodeProgram(
|
||||||
|
ctx,
|
||||||
|
ShaderAddress,
|
||||||
|
out var program,
|
||||||
|
out var error),
|
||||||
|
error);
|
||||||
|
var instruction = Assert.Single(
|
||||||
|
program.Instructions,
|
||||||
|
candidate => candidate.Encoding == Gen5ShaderEncoding.Vopc);
|
||||||
|
Assert.Equal(expectedName, instruction.Opcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Opcodes))]
|
||||||
|
public void F16CompareOpcodeLowersToSpirv(uint _, string opcode)
|
||||||
|
{
|
||||||
|
var compare = new Gen5ShaderInstruction(
|
||||||
|
0,
|
||||||
|
Gen5ShaderEncoding.Vopc,
|
||||||
|
opcode,
|
||||||
|
[0u],
|
||||||
|
[Gen5Operand.Vector(0), Gen5Operand.Vector(1)],
|
||||||
|
[],
|
||||||
|
null);
|
||||||
|
var state = new Gen5ShaderState(
|
||||||
|
new Gen5ShaderProgram(ShaderAddress, [compare]),
|
||||||
|
[],
|
||||||
|
null);
|
||||||
|
var scalars = new uint[256];
|
||||||
|
var evaluation = new Gen5ShaderEvaluation(scalars, scalars, [], []);
|
||||||
|
|
||||||
|
Assert.True(
|
||||||
|
Gen5SpirvTranslator.TryCompileComputeShader(
|
||||||
|
state,
|
||||||
|
evaluation,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
out var shader,
|
||||||
|
out var error),
|
||||||
|
error);
|
||||||
|
Assert.NotEmpty(shader.Spirv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestCpuMemory(ulong baseAddress, int size) : ICpuMemory
|
||||||
|
{
|
||||||
|
private readonly byte[] _storage = new byte[size];
|
||||||
|
|
||||||
|
public bool TryRead(ulong virtualAddress, Span<byte> destination)
|
||||||
|
{
|
||||||
|
if (!TryResolve(virtualAddress, destination.Length, out var offset))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_storage.AsSpan(offset, destination.Length).CopyTo(destination);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryWrite(ulong virtualAddress, ReadOnlySpan<byte> source)
|
||||||
|
{
|
||||||
|
if (!TryResolve(virtualAddress, source.Length, out var offset))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.CopyTo(_storage.AsSpan(offset, source.Length));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryResolve(ulong virtualAddress, int length, out int offset)
|
||||||
|
{
|
||||||
|
offset = 0;
|
||||||
|
if (virtualAddress < baseAddress)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relative = virtualAddress - baseAddress;
|
||||||
|
if (relative + (ulong)length > (ulong)_storage.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = (int)relative;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user