From 13aecb4f7cd4f0d2dcafef6b97b035693e1d2094 Mon Sep 17 00:00:00 2001 From: Dean Galvin Date: Thu, 1 Oct 2026 00:27:41 +1300 Subject: [PATCH] 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. --- .../prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp | 4 ++-- core/libs/prx/libSceAgcDriver/tests/Mesh.cpp | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index 76b3f374d..413860f9a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -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"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp index 129df2015..4808e4e13 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp @@ -106,6 +106,7 @@ struct MeshDraw { ShaderRecompiler::MeshConfiguration mesh; AgcDriver::Pm4::DrawParameters draw; std::array 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(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(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(Ordered.size()), 0, 1, 0, false}, VertexBufferDescriptor(Ordered.data(), static_cast(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(Strip.size()), 0, 1, 0, false}, VertexBufferDescriptor(Strip.data(), static_cast(Strip.size()))});