[ShaderCompiler/Vulkan] Match vertex input numeric types (#351)

Declare UINT and SINT vertex attributes with integer SPIR-V component types so shader interfaces match the Vulkan pipeline formats. Keep normalized, scaled, and floating-point formats on float inputs.

Signed-off-by: missatjuhvdk1 <177474143+missatjuhvdk1@users.noreply.github.com>
Co-authored-by: missatjuhvdk1 <177474143+missatjuhvdk1@users.noreply.github.com>
This commit is contained in:
Mees van den Kieboom
2026-07-18 02:01:01 +02:00
committed by GitHub
parent 0b1dea43e8
commit 743fe5cc26
2 changed files with 175 additions and 8 deletions
@@ -299,6 +299,13 @@ public static partial class Gen5SpirvTranslator
Uint,
}
private enum VertexInputComponentKind
{
Float,
Sint,
Uint,
}
private readonly record struct SpirvImageResource(
uint Variable,
uint ImageType,
@@ -311,7 +318,9 @@ public static partial class Gen5SpirvTranslator
private readonly record struct SpirvVertexInput(
uint Variable,
uint Type,
uint ComponentCount);
uint ComponentType,
uint ComponentCount,
VertexInputComponentKind ComponentKind);
private readonly record struct SpirvPixelOutput(
uint Variable,
@@ -1289,12 +1298,23 @@ public static partial class Gen5SpirvTranslator
{
foreach (var input in _evaluation.VertexInputs ?? [])
{
var componentKind = input.NumberFormat switch
{
4 => VertexInputComponentKind.Uint,
5 => VertexInputComponentKind.Sint,
_ => VertexInputComponentKind.Float,
};
var componentType = componentKind switch
{
VertexInputComponentKind.Uint => _uintType,
VertexInputComponentKind.Sint => _intType,
_ => _floatType,
};
var type = input.ComponentCount switch
{
1u => _floatType,
2u => _vec2Type,
3u => _vec3Type,
4u => _vec4Type,
1u => componentType,
>= 2u and <= 4u =>
_module.TypeVector(componentType, input.ComponentCount),
_ => 0u,
};
if (type == 0)
@@ -1316,7 +1336,9 @@ public static partial class Gen5SpirvTranslator
new SpirvVertexInput(
variable,
type,
input.ComponentCount));
componentType,
input.ComponentCount,
componentKind));
_interfaces.Add(variable);
}
}
@@ -3198,10 +3220,13 @@ public static partial class Gen5SpirvTranslator
? loaded
: _module.AddInstruction(
SpirvOp.CompositeExtract,
_floatType,
input.ComponentType,
loaded,
component);
StoreV(control.VectorData + component, Bitcast(_uintType, value));
var raw = input.ComponentKind == VertexInputComponentKind.Uint
? value
: Bitcast(_uintType, value);
StoreV(control.VectorData + component, raw);
}
return true;
@@ -0,0 +1,142 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.ShaderCompiler;
using SharpEmu.ShaderCompiler.Vulkan;
using Xunit;
namespace SharpEmu.Libs.Tests.Agc;
public sealed class Gen5VertexInputSpirvTests
{
[Theory]
[InlineData(0u, null)]
[InlineData(4u, 0u)]
[InlineData(5u, 1u)]
public void VertexInputTypeMatchesGuestNumberFormat(
uint numberFormat,
uint? expectedIntegerSignedness)
{
var instruction = new Gen5ShaderInstruction(
0,
Gen5ShaderEncoding.Mubuf,
"BufferLoadFormatXyzw",
[],
[],
[],
new Gen5BufferMemoryControl(
4,
5,
0,
0,
0,
IndexEnabled: true,
OffsetEnabled: false,
Glc: false,
Slc: false));
var end = new Gen5ShaderInstruction(
4,
Gen5ShaderEncoding.Sopp,
"SEndpgm",
[],
[],
[],
null);
var state = new Gen5ShaderState(
new Gen5ShaderProgram(0, [instruction, end]),
[],
null);
var registers = new uint[256];
var data = new byte[16];
var evaluation = new Gen5ShaderEvaluation(
registers,
registers,
[],
[],
VertexInputs:
[
new Gen5VertexInputBinding(
0,
0,
4,
10,
numberFormat,
0x1000,
4,
0,
data,
data.Length,
DataPooled: false),
]);
Assert.True(
Gen5SpirvTranslator.TryCompileVertexShader(
state,
evaluation,
out var shader,
out var error),
error);
var module = ParseModule(shader.Spirv);
var inputVariable = module.Single(candidate =>
candidate.Opcode == SpirvOp.Decorate &&
candidate.Operands.Length >= 3 &&
candidate.Operands[1] == (uint)SpirvDecoration.Location &&
candidate.Operands[2] == 0).Operands[0];
var pointerType = module.Single(candidate =>
candidate.Opcode == SpirvOp.Variable &&
candidate.Operands[1] == inputVariable).Operands[0];
var vectorType = module.Single(candidate =>
candidate.Opcode == SpirvOp.TypePointer &&
candidate.Operands[0] == pointerType).Operands[2];
var componentType = module.Single(candidate =>
candidate.Opcode == SpirvOp.TypeVector &&
candidate.Operands[0] == vectorType).Operands[1];
if (expectedIntegerSignedness is { } signedness)
{
Assert.Contains(
module,
candidate =>
candidate.Opcode == SpirvOp.TypeInt &&
candidate.Operands[0] == componentType &&
candidate.Operands[1] == 32 &&
candidate.Operands[2] == signedness);
}
else
{
Assert.Contains(
module,
candidate =>
candidate.Opcode == SpirvOp.TypeFloat &&
candidate.Operands[0] == componentType &&
candidate.Operands[1] == 32);
}
}
private static IReadOnlyList<ParsedInstruction> ParseModule(byte[] spirv)
{
var instructions = new List<ParsedInstruction>();
for (var offset = 5; offset < spirv.Length / sizeof(uint);)
{
var header = BitConverter.ToUInt32(spirv, offset * sizeof(uint));
var wordCount = (int)(header >> 16);
Assert.True(wordCount > 0);
var operands = new uint[wordCount - 1];
for (var index = 0; index < operands.Length; index++)
{
operands[index] = BitConverter.ToUInt32(
spirv,
(offset + index + 1) * sizeof(uint));
}
instructions.Add(
new ParsedInstruction((SpirvOp)(ushort)header, operands));
offset += wordCount;
}
return instructions;
}
private sealed record ParsedInstruction(SpirvOp Opcode, uint[] Operands);
}