Merge #935 (pr/pixel-system-inputs)

This commit is contained in:
kuba 2026-09-30 00:12:20 +02:00
commit 2f3ee0935c
4 changed files with 107 additions and 6 deletions

View File

@ -13,6 +13,9 @@ public static partial class Gen5SpirvTranslator
private readonly HashSet<uint> _flatParameterAttributes = [];
private readonly Dictionary<int, uint> _barycentricInputs = [];
private uint _interpolationSampleId;
private uint _frontFacingInput;
private uint _ancillaryLayerInput;
private uint _sampleMaskInput;
private const uint InterpolateAtCentroid = 76;
private const uint InterpolateAtSample = 77;
private const uint InterpolateAtOffset = 78;
@ -90,6 +93,86 @@ public static partial class Gen5SpirvTranslator
}
}
// FRONT_FACE and ANCILLARY are VGPRs the hardware fills for pixel shaders that enable them.
// UE writes volume textures one slice per instance and has the pixel shader read the slice
// back out of ANCILLARY[28:16] (the render target array index); leaving that register zero
// bakes every slice of a color grading LUT from the blue=0 slice.
private void DeclarePixelSystemInputs()
{
var enabledInputs = _pixelInputAddress & _pixelInputEnable;
if ((enabledInputs & (1u << 12)) != 0)
{
_frontFacingInput = _module.AddGlobalVariable(
_module.TypePointer(SpirvStorageClass.Input, _boolType), SpirvStorageClass.Input);
_module.AddDecoration(_frontFacingInput, SpirvDecoration.BuiltIn, (uint)SpirvBuiltIn.FrontFacing);
_interfaces.Add(_frontFacingInput);
}
if ((enabledInputs & (1u << 13)) != 0)
{
_module.AddExtension("SPV_EXT_shader_viewport_index_layer");
_module.AddCapability(SpirvCapability.ShaderViewportIndexLayerExt);
_ancillaryLayerInput = _module.AddGlobalVariable(
_module.TypePointer(SpirvStorageClass.Input, _uintType), SpirvStorageClass.Input);
_module.AddDecoration(_ancillaryLayerInput, SpirvDecoration.BuiltIn, (uint)SpirvBuiltIn.Layer);
_module.AddDecoration(_ancillaryLayerInput, SpirvDecoration.Flat);
_interfaces.Add(_ancillaryLayerInput);
}
if ((enabledInputs & (1u << 14)) != 0)
{
var maskArray = _module.TypeArray(_intType, 1);
_sampleMaskInput = _module.AddGlobalVariable(
_module.TypePointer(SpirvStorageClass.Input, maskArray), SpirvStorageClass.Input);
_module.AddDecoration(_sampleMaskInput, SpirvDecoration.BuiltIn, (uint)SpirvBuiltIn.SampleMask);
_module.AddDecoration(_sampleMaskInput, SpirvDecoration.Flat);
_interfaces.Add(_sampleMaskInput);
}
}
// Front facing reads as 1.0f and back facing as 0, which satisfies both the float greater-than
// and the integer not-equal tests compilers emit for it.
private uint LoadFrontFaceInput() => _module.AddInstruction(
SpirvOp.Select, _uintType, Load(_boolType, _frontFacingInput), UInt(0x3F800000u), UInt(0));
private uint LoadAncillaryInput() =>
ShiftLeftLogical(Load(_uintType, _ancillaryLayerInput), UInt(16));
private uint LoadSampleCoverageInput()
{
var element = _module.AddInstruction(SpirvOp.AccessChain,
_module.TypePointer(SpirvStorageClass.Input, _intType), _sampleMaskInput, UInt(0));
return Bitcast(_uintType, Load(_intType, element));
}
// POS_FIXED_PT holds the integer pixel position: x in bits [15:0] and y in bits [31:16].
private uint LoadFixedPointPositionInput(uint fragCoord)
{
var x = _module.AddInstruction(SpirvOp.ConvertFToU, _uintType,
_module.AddInstruction(SpirvOp.CompositeExtract, _floatType, fragCoord, 0));
var y = _module.AddInstruction(SpirvOp.ConvertFToU, _uintType,
_module.AddInstruction(SpirvOp.CompositeExtract, _floatType, fragCoord, 1));
return _module.AddInstruction(SpirvOp.BitwiseOr, _uintType,
_module.AddInstruction(SpirvOp.BitwiseAnd, _uintType, x, UInt(0xFFFF)),
ShiftLeftLogical(y, UInt(16)));
}
private void EmitPixelSystemInput(int bit, uint value, ref uint vgpr)
{
var mask = 1u << bit;
if ((_pixelInputAddress & mask) == 0)
{
return;
}
if ((_pixelInputEnable & mask) != 0 && value != 0)
{
StoreV(vgpr, value, guardWithExec: false);
}
vgpr++;
}
private uint LoadBarycentricCoordinates(int bit, uint variable) => bit switch
{
0 or 4 => _module.AddInstruction(SpirvOp.ExtInst, _vec3Type, _glsl, InterpolateAtSample,

View File

@ -962,6 +962,7 @@ public static partial class Gen5SpirvTranslator
SpirvDecoration.BuiltIn,
(uint)SpirvBuiltIn.FragCoord);
_interfaces.Add(_fragCoordInput);
DeclarePixelSystemInputs();
var declaredPixelOutputs =
Environment.GetEnvironmentVariable(
@ -1237,12 +1238,11 @@ public static partial class Gen5SpirvTranslator
EmitPixelPositionInput(11, 3, fragCoord, ref vgpr); // POS_W_FLOAT
// FRONT_FACE, ANCILLARY, SAMPLE_COVERAGE and POS_FIXED_PT follow
// position inputs. Reserve their compact slots until their SPIR-V
// builtins are needed by a guest shader.
AdvancePixelInput(12, 1, ref vgpr);
AdvancePixelInput(13, 1, ref vgpr);
AdvancePixelInput(14, 1, ref vgpr);
AdvancePixelInput(15, 1, ref vgpr);
// position inputs.
EmitPixelSystemInput(12, _frontFacingInput == 0 ? 0 : LoadFrontFaceInput(), ref vgpr);
EmitPixelSystemInput(13, _ancillaryLayerInput == 0 ? 0 : LoadAncillaryInput(), ref vgpr);
EmitPixelSystemInput(14, _sampleMaskInput == 0 ? 0 : LoadSampleCoverageInput(), ref vgpr);
EmitPixelSystemInput(15, (_pixelInputAddress & _pixelInputEnable & 0x8000u) == 0 ? 0 : LoadFixedPointPositionInput(fragCoord), ref vgpr);
}
private void AdvancePixelInput(int bit, uint dwordCount, ref uint vgpr)

View File

@ -281,6 +281,7 @@ public enum SpirvBuiltIn : uint
SubgroupSize = 36,
SubgroupLocalInvocationId = 41,
SampleId = 18,
SampleMask = 20,
FragDepth = 22,
}

View File

@ -107,6 +107,23 @@ public sealed class Gen5InterpolationParameterTests
instruction.Operands[1] == (uint)SpirvDecoration.PerVertexKhr);
}
[Fact]
public void PixelSystemInputs_ReadTheirBuiltIns()
{
Assert.True(Gen5SpirvTranslator.TryCompileProgram(
Request(0, false, inputs: 0xF002, opcode: "VInterpP2F32"), out var shader, out var error), error);
var instructions = Instructions(shader.Spirv);
var builtIns = instructions
.Where(instruction => instruction.Opcode == SpirvOp.Decorate &&
instruction.Operands[1] == (uint)SpirvDecoration.BuiltIn)
.Select(instruction => instruction.Operands[2]).ToArray();
Assert.Contains((uint)SpirvBuiltIn.FrontFacing, builtIns);
Assert.Contains((uint)SpirvBuiltIn.Layer, builtIns);
Assert.Contains((uint)SpirvBuiltIn.SampleMask, builtIns);
Assert.Contains(instructions, instruction => instruction.Opcode == SpirvOp.ShiftLeftLogical);
ValidateWhenAvailable(shader.Spirv);
}
private static ShaderCompileRequest Request(
uint selector, bool custom, uint inputs = 2, string opcode = "VInterpMovF32",
uint inputCntl = 0x401, bool supportsPerVertex = true)