fix(agc): validate a mesh stage's push block when it has no push data of its own

A mesh-stage program reads its draw parameters from the end of the
pipeline's push block (MeshDrawPushOffsetBytes), so its SPIR-V always
declares the push-constant variable. When its user data do not fit the
push range it has no push data of its own and
RecompileResult::pushConstants is empty, and validation refused the draw
with "invalid push constant interface".

Validation now expects the push block of every mesh-stage program and
still requires it to match the recompiler's metadata for the other
stages. agc_driver_mesh_tests draws the triangle list with a geometry
program given no push range for its own data.
This commit is contained in:
Dean Galvin 2026-10-01 00:27:41 +13:00
parent 33f9109999
commit 13aecb4f7c
2 changed files with 9 additions and 3 deletions

View File

@ -421,7 +421,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
}
}
} else if (variable.storage == spv::StorageClassPushConstant) {
Require(!push && !shader.pushConstants.empty() && (type[0] & 0xffffu) == spv::OpTypeStruct, "invalid push constant interface");
Require(!push && (!shader.pushConstants.empty() || mesh) && (type[0] & 0xffffu) == spv::OpTypeStruct, "invalid push constant interface");
push = true;
Require(type.size() == 3 && module.decorations[typeId].block, "push constant variable must be a Block struct with exactly one member");
const auto offset = module.offsets.find({typeId, 0});
@ -480,7 +480,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
Require(shader.vertexAttributes.empty(), "vertex attribute metadata is invalid for this stage");
}
Require(descriptors.size() == shader.bindings.size(), "recompiler binding metadata contains undeclared resources");
Require(push == !shader.pushConstants.empty(), "recompiler push constant metadata disagrees with SPIR-V");
Require(push == (!shader.pushConstants.empty() || mesh), "recompiler push constant metadata disagrees with SPIR-V");
for (const auto id : module.interface) Require(module.variables.contains(id), "entry point interface contains an unknown variable");
if (mesh) Require(module.primitiveIndices, "mesh shader does not export primitive indices");
if (stage == Stage::Vertex || mesh || evaluation) Require(module.position, "vertex shader does not export position");

View File

@ -106,6 +106,7 @@ struct MeshDraw {
ShaderRecompiler::MeshConfiguration mesh;
AgcDriver::Pm4::DrawParameters draw;
std::array<std::uint32_t, 4> vertexBuffer;
std::uint32_t geometryPushBytes = ShaderRecompiler::MeshDrawPushOffsetBytes;
};
void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) {
@ -119,11 +120,12 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) {
{ShaderStage::Mesh, reinterpret_cast<std::uintptr_t>(GeometryCode.data()), GeometryCode, 0, {}},
{64, 0, userData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, geometryMemory},
target,
{0, 0, 0, ShaderRecompiler::MeshDrawPushOffsetBytes},
{0, 0, 0, setup.geometryPushBytes},
ShaderRecompiler::GraphicsCompileContext{0, {}, setup.mesh, std::nullopt, {setup.draw.indexAddress, setup.draw.indexCount, setup.draw.indexSize, setup.draw.instanceCount}}
};
const auto meshResult = ShaderRecompiler::Recompile(geometry);
const auto meshPush = static_cast<std::uint32_t>(meshResult.pushConstants.size());
Require((meshPush == 0) == (setup.geometryPushBytes == 0), "the geometry program's push data does not follow its layout");
ShaderRecompiler::ShaderPixelStageInfo pixel{};
pixel.interpolatorCount = 1;
@ -212,6 +214,10 @@ int main() {
CheckTriangles((std::string("non-indexed triangle list, ") + name).c_str());
}
ClearPixels();
DrawMesh(device, {SmallSubgroup, {0, static_cast<std::uint32_t>(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast<std::uint32_t>(Ordered.size())), 0});
CheckTriangles("mesh program without push data");
ClearPixels();
const ShaderRecompiler::MeshConfiguration strip{6u, 3u, 5u, 9u, 3u, 64u, 1024u, 0u, 4u};
DrawMesh(device, {strip, {0, static_cast<std::uint32_t>(Strip.size()), 0, 1, 0, false}, VertexBufferDescriptor(Strip.data(), static_cast<std::uint32_t>(Strip.size()))});