From f7a2903edfd4ab9e94e59bc0160262119f1802fb Mon Sep 17 00:00:00 2001 From: Sasha Papo Date: Tue, 13 Oct 2026 10:21:11 -0400 Subject: [PATCH] feat(agc): clip and cull distances on the mesh path Co-Authored-By: Claude Opus 5.5 --- .../Graphics/src/ShaderValidation.cpp | 10 ++-- .../execution/VertexClipCullDistances.cpp | 57 ++++++++++++++----- .../SpirvBackend/src/SpirvModuleEmitter.cpp | 50 ++++++++++++++-- 3 files changed, 94 insertions(+), 23 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index 25148b44ecd..a4d8c14bbcc 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -115,7 +115,7 @@ struct Module { return; } // One gl_ClipDistance array holds the guest clip distances and the recompiler's all-zero position distance. - if (!input && ((value == spv::BuiltInClipDistance && (vertex || stage == Stage::TessellationEvaluation)) || (value == spv::BuiltInCullDistance && vertex))) { + if (!input && ((value == spv::BuiltInClipDistance && (vertex || stage == Stage::TessellationEvaluation || stage == Stage::Mesh)) || (value == spv::BuiltInCullDistance && (vertex || stage == Stage::Mesh)))) { const auto signature = Signature(type); auto& count = value == spv::BuiltInClipDistance ? clipDistances : cullDistances; Require(count == 0 && signature.size() == 6 && signature.starts_with("f32[") && signature[4] >= '1' && signature[4] <= '8' && signature[5] == ']', "invalid or duplicate clip or cull distance output"); @@ -250,10 +250,10 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi fragment && capability == spv::CapabilityStencilExportEXT; - if ((vertex || evaluation) && (capability == spv::CapabilityClipDistance || capability == spv::CapabilityCullDistance)) { + if ((vertex || evaluation || mesh) && (capability == spv::CapabilityClipDistance || capability == spv::CapabilityCullDistance)) { char word[16]; std::snprintf(word, sizeof(word), "0x%08x", state.paClVsOutCntl); - Require(capability == spv::CapabilityClipDistance ? shaderClipDistance : vertex && shaderCullDistance, std::string("vertex clip or cull distance needs shaderClipDistance or shaderCullDistance, which the device lacks (PA_CL_VS_OUT_CNTL=") + word + ")"); + Require(capability == spv::CapabilityClipDistance ? shaderClipDistance : (vertex || mesh) && shaderCullDistance, std::string("vertex clip or cull distance needs shaderClipDistance or shaderCullDistance, which the device lacks (PA_CL_VS_OUT_CNTL=") + word + ")"); } // Enabled unconditionally or by the device setup in VulkanDevice. @@ -262,12 +262,12 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi capability == spv::CapabilityImage1D || capability == spv::CapabilityImageGatherExtended || capability == spv::CapabilityImageQuery || - ((vertex || evaluation) && shaderClipDistance && capability == spv::CapabilityClipDistance) || + ((vertex || evaluation || mesh) && shaderClipDistance && capability == spv::CapabilityClipDistance) || (fragment && geometryShader && capability == spv::CapabilityGeometry) || (fragment && sampleRateShading && capability == spv::CapabilitySampleRateShading) || capability == spv::CapabilityImageMSArray || capability == spv::CapabilityStorageImageMultisample || - (vertex && shaderCullDistance && capability == spv::CapabilityCullDistance) || + ((vertex || mesh) && shaderCullDistance && capability == spv::CapabilityCullDistance) || capability == spv::CapabilityStorageImageWriteWithoutFormat || capability == spv::CapabilityStorageImageReadWithoutFormat || capability == spv::CapabilityInt64 || diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDistances.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDistances.cpp index fe7d3b448f8..5b5a66615f3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDistances.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VertexClipCullDistances.cpp @@ -25,6 +25,25 @@ alignas(256) constexpr std::array VertexCode{ 0xf80000cf, 0x03020100, 0xf80008df, 0x07060504, 0xbf810000, }; +alignas(256) constexpr std::array MeshCode{ + 0x8f6a9003, 0x94fe6ac1, 0xbf88000b, 0xd7650006, 0x000100c1, 0xd7660006, 0x00020cc1, 0x93ebff03, + 0x00040018, 0xd7460006, 0x04190c6b, 0x340c0c82, 0xd8340000, 0x00000506, 0xbf8cc07f, 0xbefe04c1, + 0xbf8a0000, 0x938dff02, 0x00090016, 0x9382ff03, 0x00040018, 0x938cff03, 0x00080008, 0xd7650009, + 0x000100c1, 0xd7660009, 0x000212c1, 0xd746000a, 0x04250c02, 0x7da8120c, 0xbf88002e, 0x361600ff, + 0x0000ffff, 0x2c180090, 0x361a02ff, 0x0000ffff, 0xd8d80000, 0x0b00000b, 0xd8d80000, 0x0c00000c, + 0xd8d80000, 0x0d00000d, 0xbf8cc07f, 0xe0382000, 0x8002100b, 0xe0382010, 0x8002140b, 0xe0382000, + 0x8002180c, 0xe0382010, 0x80021c0c, 0xe0382000, 0x8002200d, 0xe0382010, 0x8002240d, 0x161c1483, + 0x161e14ff, 0x00000060, 0xbf8c3f70, 0xdb7c0400, 0x0000100f, 0xdb7c0410, 0x0000140f, 0xdb7c0420, + 0x0000180f, 0xdb7c0430, 0x00001c0f, 0xdb7c0440, 0x0000200f, 0xdb7c0450, 0x0000240f, 0x4a501c81, + 0x4a521c82, 0x3450508a, 0x34525294, 0xd772002a, 0x04a6510e, 0xbf8cc07f, 0xbefe04c1, 0xbf8a0000, + 0x930e830d, 0xbf078002, 0xbf850003, 0x8f0f8c0d, 0x887c0f0e, 0xbf900009, 0x7da8140d, 0xbf880002, + 0xf8000941, 0x0000002a, 0xbefe04c1, 0x7da8140e, 0xbf88000c, 0x34561485, 0xdbfc0400, 0x2c00002b, + 0xdbfc0410, 0x3000002b, 0xbf8cc07f, 0xf80000cf, 0x2f2e2d2c, 0xf80008df, 0x33323130, 0xf800020f, + 0x33323130, 0xbf810000, +}; + +constexpr ShaderRecompiler::MeshConfiguration Subgroup{4u, 4u, 12u, 12u, 4u, 64u, 1024u, 0u, 4u}; + alignas(256) constexpr std::array PixelCode{ 0x7e0002f2, 0x7e0202f2, 0x7e0402f2, 0x7e0602f2, 0xf800080f, 0x03020100, 0xbf810000, }; @@ -78,25 +97,29 @@ bool Expected(Visible visible, std::uint32_t pixel) { return false; } -void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const Row& row) { +void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const Row& row, bool mesh) { Pixels.fill(std::byte{0}); std::array vertices{}; for (std::size_t i = 0; i < vertices.size(); ++i) { vertices[i] = {{PositionX[i], PositionY[i], 0.5f, 1.0f}, {row.component0[i], row.component1[i], row.component2[i], 0.0f}}; } - std::vector vertexUserData(4, 0u); + std::vector vertexUserData(mesh ? 12u : 4u, 0u); const auto vertexBuffer = BufferDescriptor(vertices.data(), sizeof(Vertex), static_cast(vertices.size())); - std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin()); - const std::array vertexMemory{{{reinterpret_cast(VertexCode.data()), std::as_bytes(std::span(VertexCode))}}}; + std::copy(vertexBuffer.begin(), vertexBuffer.end(), vertexUserData.begin() + (mesh ? 8 : 0)); + const AgcDriver::Pm4::DrawParameters draw{0, static_cast(vertices.size()), 0, 1, 0, false}; + const std::span code = mesh ? std::span(MeshCode) : std::span(VertexCode); + const auto stage = mesh ? ShaderStage::Mesh : ShaderStage::Vertex; + const std::array vertexMemory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; ShaderRecompiler::ShaderVertexStageInfo vertexInfo{}; vertexInfo.paClVsOutCntl = row.control; ShaderRecompiler::RecompileRequest vertex{ - {ShaderStage::Vertex, reinterpret_cast(VertexCode.data()), VertexCode, 0, {}}, + {stage, reinterpret_cast(code.data()), code, 0, {}}, {waveSize, 0, vertexUserData, std::nullopt, std::nullopt, vertexInfo, vertexMemory}, device.Target(), - {0, 0, 0, 64} + {0, 0, 0, mesh ? ShaderRecompiler::MeshDrawPushOffsetBytes : 64u} }; + if (mesh) vertex.graphics = ShaderRecompiler::GraphicsCompileContext{0, {}, Subgroup, std::nullopt, {draw.indexAddress, draw.indexCount, draw.indexSize, draw.instanceCount}}; vertex.useCache = false; const auto vertexResult = ShaderRecompiler::Recompile(vertex); const auto vertexPush = static_cast(vertexResult.pushConstants.size()); @@ -115,12 +138,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const Row& ro fragment.useCache = false; const auto pixelResult = ShaderRecompiler::Recompile(fragment); const std::array shaders{{ - {ShaderStage::Vertex, &vertexResult, 0}, + {stage, &vertexResult, 0}, {ShaderStage::Fragment, &pixelResult, PixelPushOffset(vertexPush, device.Target())} }}; AgcDriver::Graphics::State state{}; - state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; + if (mesh) state.stages = {AgcDriver::Graphics::ShaderPath::Geometry, 0x20u, waveSize, waveSize, Subgroup, std::nullopt}; + else state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt}; state.paClVsOutCntl = row.control; state.color = {reinterpret_cast(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u}; state.colors = {state.color}; @@ -135,13 +159,12 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, const Row& ro state.blend.colorWriteMask = 15; state.blends = {state.blend}; state.blendConstants = {}; - const AgcDriver::Pm4::DrawParameters draw{0, static_cast(vertices.size()), 0, 1, 0, false}; device.Draw(state, draw, shaders); device.WaitIdle(); } -void Check(const Row& row, std::uint32_t waveSize) { - const auto what = std::string("vertex clip and cull distances, ") + row.name + ", wave" + std::to_string(waveSize); +void Check(const Row& row, std::uint32_t waveSize, bool mesh) { + const auto what = std::string(mesh ? "mesh" : "vertex") + " clip and cull distances, " + row.name + ", wave" + std::to_string(waveSize); for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) { const auto value = std::to_integer(Pixels[pixel * 4u]); Require(value == 0u || value == 255u, what + ": pixel " + std::to_string(pixel) + " stored " + std::to_string(value)); @@ -163,9 +186,17 @@ int main() { } for (const auto waveSize : {64u, 32u}) { for (const auto& row : Rows) { - Draw(*device, waveSize, row); - Check(row, waveSize); + Draw(*device, waveSize, row, false); + Check(row, waveSize, false); + } + } + if (device->Target().mesh.has_value()) { + for (const auto& row : Rows) { + Draw(*device, 64u, row, true); + Check(row, 64u, true); } + } else { + std::puts("mesh case skipped, the device has no VK_EXT_mesh_shader"); } std::puts("vertex clip and cull distance tests passed"); return 0; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index debd3304b65..04480c0caa8 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -410,16 +410,20 @@ void EmitAuxPositionExport(SpirvValueEmitContext& ctx, std::uint32_t data, const state.module.AddFunction(spv::OpStore, state.pointSizeVariable, f32); continue; } - const auto storeDistance = [&](std::uint32_t variable, std::uint32_t index) { + const auto storeDistance = [&](StageOutputKind kind, std::uint32_t variable, std::uint32_t index) { if (index == std::numeric_limits::max()) { return; } + if (state.program.Resources().stage == IrShaderStage::Mesh) { + state.module.AddFunction(spv::OpStore, MeshOutputPointer(state, kind, index), f32); + return; + } const auto pointer = state.module.AllocateId(); state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeF32(state)), pointer, variable, ConstantU32(state, index)); state.module.AddFunction(spv::OpStore, pointer, f32); }; - storeDistance(state.clipDistanceVariable, output.clipDistance); - storeDistance(state.cullDistanceVariable, output.cullDistance); + storeDistance(StageOutputKind::ClipDistance, state.clipDistanceVariable, output.clipDistance); + storeDistance(StageOutputKind::CullDistance, state.cullDistanceVariable, output.cullDistance); } } @@ -500,8 +504,21 @@ std::uint32_t MeshLoad(SpirvEmitterState& state, std::uint32_t variable, std::ui return value; } +bool IsMeshDistance(StageOutputKind kind) { + return kind == StageOutputKind::ClipDistance || kind == StageOutputKind::CullDistance; +} + std::uint32_t MeshOutputType(SpirvEmitterState& state, StageOutputKind kind) { - return kind == StageOutputKind::Layer ? TypeU32(state) : TypeF32Vector(state, 4u); + if (kind == StageOutputKind::Layer) { + return TypeU32(state); + } + return IsMeshDistance(kind) ? TypeF32(state) : TypeF32Vector(state, 4u); +} + +std::uint32_t MeshDistancePointer(SpirvEmitterState& state, const SpirvOutputBinding& output, std::uint32_t vertex) { + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeF32(state)), pointer, output.variableId, vertex, ConstantU32(state, output.index)); + return pointer; } } @@ -642,7 +659,30 @@ void DefineMeshOutputs(SpirvEmitterState& state) { throw std::runtime_error("vertex input info is missing for mesh output definition"); } const auto& mesh = state.inputInfo.vertex->mesh; + std::uint32_t clipDistanceCount = 0u; + std::uint32_t cullDistanceCount = 0u; + for (const auto& output : state.outputs) { + if (output.kind == StageOutputKind::ClipDistance) { + clipDistanceCount = std::max(clipDistanceCount, output.index + 1u); + } else if (output.kind == StageOutputKind::CullDistance) { + cullDistanceCount = std::max(cullDistanceCount, output.index + 1u); + } + } for (auto& output : state.outputs) { + if (IsMeshDistance(output.kind)) { + const bool clip = output.kind == StageOutputKind::ClipDistance; + auto& variable = clip ? state.clipDistanceVariable : state.cullDistanceVariable; + if (variable == 0u) { + const auto distances = state.module.Type(spv::OpTypeArray, TypeF32(state), ConstantU32(state, clip ? clipDistanceCount : cullDistanceCount)); + variable = MeshArray(state, spv::StorageClassOutput, distances, mesh.maxVertices); + state.interfaceVariables.push_back(variable); + state.module.AddName(variable, clip ? "gl_ClipDistance" : "gl_CullDistance"); + state.module.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn, clip ? spv::BuiltInClipDistance : spv::BuiltInCullDistance); + } + output.variableId = variable; + output.meshDataVariable = MeshArray(state, spv::StorageClassPrivate, TypeF32(state), state.laneCount); + continue; + } if (output.kind != StageOutputKind::Position && output.kind != StageOutputKind::Parameter && output.kind != StageOutputKind::Layer) { throw std::runtime_error("unsupported mesh output kind"); } @@ -727,7 +767,7 @@ void EmitMeshEntryPoint(SpirvEmitterState& state) { } const auto type = MeshOutputType(state, output.kind); const auto value = MeshLoad(state, output.meshDataVariable, spv::StorageClassPrivate, type, ConstantU32(state, half)); - const auto pointer = MeshElement(state, output.variableId, spv::StorageClassOutput, type, index); + const auto pointer = IsMeshDistance(output.kind) ? MeshDistancePointer(state, output, index) : MeshElement(state, output.variableId, spv::StorageClassOutput, type, index); state.module.AddFunction(spv::OpStore, pointer, value); } });