fix(shader): preserve NGG passthrough launch and primitive inputs

This commit is contained in:
SamG-Coder 2026-09-29 13:03:58 +10:00
parent 785c24e652
commit 4cd8e35366
13 changed files with 49 additions and 7 deletions

View File

@ -22,6 +22,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
if (graphics.stages.mesh) {
const auto& mesh = *graphics.stages.mesh;
for (const auto value : {mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize}) mix(value);
mix(mesh.passthrough);
}
mix(graphics.stages.tessellation.has_value());
if (graphics.stages.tessellation) {

View File

@ -62,7 +62,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
const auto sameMesh = [](const std::optional<ShaderRecompiler::MeshConfiguration>& x, const std::optional<ShaderRecompiler::MeshConfiguration>& y) {
if (x.has_value() != y.has_value()) return false;
if (!x) return true;
return x->inputPrimitive == y->inputPrimitive && x->primitivesPerGroup == y->primitivesPerGroup && x->verticesPerGroup == y->verticesPerGroup && x->maxVertices == y->maxVertices && x->maxPrimitives == y->maxPrimitives && x->threadsPerGroup == y->threadsPerGroup && x->ldsSizeDwords == y->ldsSizeDwords && x->provokingVertex == y->provokingVertex && x->esgsItemSize == y->esgsItemSize;
return x->inputPrimitive == y->inputPrimitive && x->primitivesPerGroup == y->primitivesPerGroup && x->verticesPerGroup == y->verticesPerGroup && x->maxVertices == y->maxVertices && x->maxPrimitives == y->maxPrimitives && x->threadsPerGroup == y->threadsPerGroup && x->ldsSizeDwords == y->ldsSizeDwords && x->provokingVertex == y->provokingVertex && x->esgsItemSize == y->esgsItemSize && x->passthrough == y->passthrough;
};
const auto sameTess = [](const std::optional<ShaderRecompiler::TessellationConfiguration>& x, const std::optional<ShaderRecompiler::TessellationConfiguration>& y) {
if (x.has_value() != y.has_value()) return false;

View File

@ -467,6 +467,8 @@ bool ValidationKey(const Context& context, std::span<const CompiledShader> shade
add(mesh.threadsPerGroup);
add(mesh.ldsSizeDwords);
add(mesh.provokingVertex);
add(mesh.esgsItemSize);
add(mesh.passthrough);
}
add(state.stages.tessellation.has_value());
if (state.stages.tessellation) {

View File

@ -362,6 +362,8 @@ std::vector<std::byte> pipelineKey(const Context& context, const State& state, c
append(key, mesh.threadsPerGroup);
append(key, mesh.ldsSizeDwords);
append(key, mesh.provokingVertex);
append(key, mesh.esgsItemSize);
append(key, mesh.passthrough);
}
append(key, state.stages.tessellation.has_value());
if (state.stages.tessellation) {

View File

@ -341,7 +341,9 @@ ShaderStages DecodeShaderStages(const QueueState& queue) {
validate((value & 3u) != 3u && ((value >> 3u) & 3u) != 3u && ((value >> 6u) & 3u) != 3u, "reserved LS_EN, ES_EN or VS_EN encoding");
const auto primitive = read(queue.userConfig, 0x242, RegisterBank::UserConfig);
const bool tessellation = primitive == 9;
const bool geometry = (value & 0x20u) != 0;
const bool passthrough = (value & 0x02000000u) != 0;
const bool geometry = (value & 0x20u) != 0 || passthrough;
validate(!passthrough || (value & 0x2000u) != 0, "passthrough routing without PRIMGEN_EN is unsupported");
validate(tessellation == ((value & 4u) != 0), "Patch topology and HS_EN disagree");
validate(!tessellation || !geometry, "combined tessellation and geometry is unsupported by the reference path");
const auto path = tessellation ? ShaderPath::Tessellation : geometry ? ShaderPath::Geometry : ShaderPath::Vertex;
@ -360,17 +362,17 @@ ShaderStages DecodeShaderStages(const QueueState& queue) {
validate(tess.domain == 1 && tess.partitioning == 2 && tess.outputTopology == 2, "only triangular, fractional-odd, clockwise tessellation is supported by the reference path");
result.tessellation = tess;
} else {
validate((value & ~0x0047ec30u) == 0, "unsupported geometry routing, fast launch or wave-ID state");
validate((value & ~(passthrough ? 0x02402010u : 0x0047ec30u)) == 0, "unsupported geometry routing, fast launch or wave-ID state");
const auto group = read(queue.userConfig, 0x25b, RegisterBank::UserConfig);
const auto vertices = (group >> 9u) & 0x1ffu;
const auto primitives = group & 0x1ffu;
const auto maxVertices = read(queue.context, 0x1ff);
const auto verticesPerPrimitive = read(queue.context, 0x2ce);
validate((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 6) && read(queue.context, 0x29b) == 2 && verticesPerPrimitive >= 3, "unsupported geometry input or output assembly");
const auto verticesPerPrimitive = passthrough ? 3u : read(queue.context, 0x2ce);
validate(passthrough ? (primitive == 4 || primitive == 6) : ((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 6) && read(queue.context, 0x29b) == 2 && verticesPerPrimitive >= 3), "unsupported geometry input or output assembly");
const auto inputSize = primitive == 1 ? 1u : primitive == 2 ? 2u : 3u;
validate(vertices >= inputSize && maxVertices != 0 && maxVertices <= 256 && verticesPerPrimitive <= 256, "invalid geometry subgroup output");
const auto inputStep = primitive == 6 ? 1u : inputSize;
const auto groupPrimitives = std::min({primitives, (vertices - inputSize) / inputStep + 1u, maxVertices / verticesPerPrimitive});
const auto groupPrimitives = std::min({primitives, (vertices - inputSize) / inputStep + 1u, passthrough ? (maxVertices >= inputSize ? (maxVertices - inputSize) / inputStep + 1u : 0u) : maxVertices / verticesPerPrimitive});
validate(groupPrimitives != 0, "geometry subgroup contains no primitives");
const auto resources = read(queue.shader, 0x8b, RegisterBank::Shader);
validate(((read(queue.shader, 0x8a, RegisterBank::Shader) >> 29u) & 3u) == 3 && ((resources >> 16u) & 3u) == 3, "unsupported geometry VGPR allocation");
@ -378,6 +380,7 @@ ShaderStages DecodeShaderStages(const QueueState& queue) {
validate(esgsItemSize != 0 && esgsItemSize * vertices <= 0xffffu, "invalid VGT_ESGS_RING_ITEMSIZE");
const auto threads = std::max({(groupPrimitives - 1u) * inputStep + inputSize, primitives, maxVertices, primitives * (verticesPerPrimitive - 2u)});
result.mesh = ShaderRecompiler::MeshConfiguration{primitive, groupPrimitives, (groupPrimitives - 1u) * inputStep + inputSize, maxVertices, primitives * (verticesPerPrimitive - 2u), ((threads + result.vertexWaveSize - 1u) / result.vertexWaveSize) * result.vertexWaveSize, ((resources >> 19u) & 0xffu) * 128u, 0, esgsItemSize};
result.mesh->passthrough = passthrough;
}
return result;
}

View File

@ -186,7 +186,7 @@ void hardwareScreenOffsetTests() {
void ShaderStageTests() {
auto queue = makeState();
for (const auto routing : {0x2000u, 0x2010u, 0x02002000u, 0x02002010u}) {
for (const auto routing : {0x2000u, 0x2010u}) {
for (const auto vertexWave32 : {false, true}) {
for (const auto fragmentWave32 : {false, true}) {
queue.context[0x2d5] = routing | (vertexWave32 ? 0x00400000u : 0u);
@ -210,6 +210,17 @@ void ShaderStageTests() {
auto stages = AgcDriver::Graphics::DecodeState(queue).stages;
Require(stages.path == AgcDriver::Graphics::ShaderPath::Geometry && stages.mesh && stages.mesh->primitivesPerGroup == 21 && stages.mesh->verticesPerGroup == 63, "geometry assembly changed");
Require(stages.mesh->maxVertices == 64 && stages.mesh->maxPrimitives == 21 && stages.mesh->threadsPerGroup == 64 && stages.mesh->esgsItemSize == 4, "geometry subgroup outputs changed");
for (const auto routing : {0x02002000u, 0x02002010u, 0x02402000u, 0x02402010u}) {
queue.context[0x2d5] = routing;
queue.context[0x2ce] = 0;
queue.context[0x29b] = 0;
const auto pass = AgcDriver::Graphics::DecodeState(queue).stages;
Require(pass.path == AgcDriver::Graphics::ShaderPath::Geometry && pass.mesh && pass.mesh->passthrough && pass.mesh->verticesPerGroup == 63 && pass.mesh->primitivesPerGroup == 21, "passthrough subgroup assembly changed");
Require(pass.vertexWaveSize == ((routing & 0x00400000u) ? 32u : 64u), "passthrough wave size changed");
}
queue.context[0x2d5] = 0x2020;
queue.context[0x2ce] = 3;
queue.context[0x29b] = 2;
queue.context[0x2ab] = 0;
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "invalid VGT_ESGS_RING_ITEMSIZE");
queue.context[0x2ab] = 4;

View File

@ -449,6 +449,13 @@ void verifyMeshConfiguration() {
other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}};
RecompileCacheKey::Build(other, key);
require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh configuration");
otherMesh = mesh;
otherMesh.passthrough = true;
other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}};
RecompileCacheKey::Build(other, key);
require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore passthrough input layout");
const auto passReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(other));
require(passReplay.request.graphics->mesh->passthrough && !replay.request.graphics->mesh->passthrough, "passthrough input layout was lost in serialization");
}
ShaderRecompiler::ShaderPixelStageInfo twoParameterPixel() {

View File

@ -78,6 +78,7 @@ private:
const auto* mesh = request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr;
append(key, mesh != nullptr);
if (mesh == nullptr) return;
append(key, mesh->passthrough);
for (const auto value : {mesh->inputPrimitive, mesh->primitivesPerGroup, mesh->verticesPerGroup, mesh->maxVertices, mesh->maxPrimitives, mesh->threadsPerGroup, mesh->ldsSizeDwords, mesh->provokingVertex, mesh->esgsItemSize}) append(key, value);
}

View File

@ -442,6 +442,7 @@ void writeMeshConfiguration(Writer& writer, const MeshConfiguration& configurati
writer.WriteU32(configuration.ldsSizeDwords);
writer.WriteU32(configuration.provokingVertex);
writer.WriteU32(configuration.esgsItemSize);
writer.WriteBool(configuration.passthrough);
}
MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) {
@ -455,6 +456,7 @@ MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) {
configuration.ldsSizeDwords = reader.ReadU32();
configuration.provokingVertex = reader.ReadU32();
configuration.esgsItemSize = version >= 4u ? reader.ReadU32() : 4u;
configuration.passthrough = version >= 5u ? reader.ReadBool() : false;
return configuration;
}

View File

@ -102,6 +102,7 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo {
std::uint32_t maxPrimitives = 0;
std::uint32_t provokingVertex = 0;
std::uint32_t esgsItemSize = 0;
bool passthrough = false;
[[nodiscard]] std::uint32_t InputPrimitiveSize() const {
return inputPrimitive == 1u ? 1u : inputPrimitive == 2u ? 2u : 3u;

View File

@ -225,6 +225,7 @@ struct MeshConfiguration {
std::uint32_t ldsSizeDwords;
std::uint32_t provokingVertex;
std::uint32_t esgsItemSize = 0;
bool passthrough = false;
};
struct TessellationConfiguration {

View File

@ -238,6 +238,9 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
const std::uint32_t size = mesh.InputPrimitiveSize();
const std::uint32_t stepCount = mesh.InputPrimitiveStep();
const std::uint32_t waveSize = options.waveSize;
if (mesh.passthrough && mesh.inputPrimitive != 4u && mesh.inputPrimitive != 6u) {
throw std::runtime_error("passthrough mesh translation requires triangle list or strip topology");
}
if (mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup != mesh.InputVertexCount(mesh.primitivesPerGroup) || mesh.verticesPerGroup > totalThreads || mesh.primitivesPerGroup > totalThreads || totalThreads % waveSize != 0u || totalThreads > 15u * waveSize || mesh.esgsItemSize == 0u || mesh.esgsItemSize * mesh.verticesPerGroup > 0xffffu) {
throw std::runtime_error("mesh shader translation configuration is not supported (wave " + std::to_string(options.waveSize) + ", primitives per group " + std::to_string(mesh.primitivesPerGroup) + ", vertices per group " + std::to_string(mesh.verticesPerGroup) + ", threads " + std::to_string(totalThreads) + ", ESGS item size " + std::to_string(mesh.esgsItemSize) + ")");
}
@ -275,6 +278,13 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
IrValue& third = size == 3u ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : u32(0u);
entryIr.SetVectorReg(static_cast<VectorReg>(0), entryIr.BitwiseOr(entryIr.BitwiseAnd(first, u32(0xffffu)), entryIr.ShiftLeftLogical(second, u32(16u))));
entryIr.SetVectorReg(static_cast<VectorReg>(1), entryIr.BitwiseAnd(third, u32(0xffffu)));
if (mesh.passthrough) {
IrValue& firstIndex = entryIr.IAdd(vertex, parity);
IrValue& secondIndex = entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity);
IrValue& thirdIndex = entryIr.IAdd(vertex, u32(2u));
entryIr.SetVectorReg(static_cast<VectorReg>(0), entryIr.BitwiseOr(firstIndex, entryIr.BitwiseOr(entryIr.ShiftLeftLogical(secondIndex, u32(10u)), entryIr.ShiftLeftLogical(thirdIndex, u32(20u)))));
entryIr.SetVectorReg(static_cast<VectorReg>(1), u32(0u));
}
entryIr.SetVectorReg(static_cast<VectorReg>(2), entryIr.IAdd(firstPrimitive, local));
entryIr.SetVectorReg(static_cast<VectorReg>(3), u32(0u));
entryIr.SetVectorReg(static_cast<VectorReg>(4), u32(0u));

View File

@ -184,6 +184,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
target.maxPrimitives = mesh->maxPrimitives;
target.provokingVertex = mesh->provokingVertex;
target.esgsItemSize = mesh->esgsItemSize;
target.passthrough = mesh->passthrough;
if (target.threadsNum[0] == 0u || target.maxVertices == 0u || target.maxPrimitives == 0u || target.provokingVertex > 2u) throw std::runtime_error("ShaderInputInfoBuilder: invalid mesh configuration");
}
ShaderStageInputInfo result;