From e25d561b7cd0626da87f7166c0093c1ec0056bd0 Mon Sep 17 00:00:00 2001 From: Yunus Shaikh <75296019+EagleBearerYS@users.noreply.github.com> Date: Tue, 13 Oct 2026 00:57:30 -0700 Subject: [PATCH] fix(shader): take a hull's input stride from its patch term when it reads one control point derive() learned the LS-to-HS stride from the control-point term of hull LDS reads, or from the patch term of reads at a computed control-point index. A hull with one input control point reads it at constant offsets, so neither applies and the stride stays unknown ('the hull shader reads no LDS per control point, so the local stride is unknown'). Every hull read already has a patch term of exactly inputControlPoints * localStride, so take the stride from it for reads without a control-point term too. Resident Evil Village's quad patches have one input control point. --- .../tests/execution/Tessellation.cpp | 28 +++++++++++++++++++ .../Optimization/src/TessellationLowering.cpp | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp index b3cf120de6..6edeb88bb1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp @@ -63,6 +63,16 @@ alignas(256) constexpr std::array HullCompareBranch{ 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b, 0xbf810000, }; +alignas(256) constexpr std::array HullSingleInput{ + 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, + 0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x022224ff, 0x00000020, 0xdbfc0000, + 0x02000015, 0x162c2890, 0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216, + 0x7da82881, 0xbe94047e, 0xb06a0020, 0x162602f9, 0x0086066a, 0xd8d80008, 0x06000013, 0xd8dc0201, + 0x07000013, 0xbf8cc07f, 0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xbefe0414, + 0xe0781000, 0x0403180a, 0x4a162481, 0x16161690, 0x4c1616ff, 0x00008000, 0xe0741000, 0x0202070b, + 0xbf810000, +}; + alignas(256) constexpr std::array HullRestoredExecSaved{ 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, @@ -717,6 +727,23 @@ void RequireRegisteredStages() { AgcDriverShutdown_nid_postfix(); } +ShaderRecompiler::RecompileResult CompileHull(const ShaderRecompiler::SpirvTarget& target, std::span code, const ShaderRecompiler::TessellationConfiguration& configuration) { + static const std::array userData{0x1000u}; + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::RecompileRequest request{ + {ShaderStage::TessellationControl, reinterpret_cast(code.data()), code, 0, {}}, + {64, 0u, userData, std::nullopt, std::nullopt, ShaderRecompiler::ShaderVertexStageInfo{}, memory}, + target, + ShaderRecompiler::BindingLayout{0, 0, 0, AgcDriver::Graphics::PipelinePushSlotBytes}, + ShaderRecompiler::GraphicsCompileContext{0u, {}, std::nullopt, configuration, {reinterpret_cast(Indices.data()), static_cast(Indices.size()), 2, 1}}, + }; + return ShaderRecompiler::Recompile(request); +} + +void RequireSingleControlPointHulls(const ShaderRecompiler::SpirvTarget& target) { + Require(!CompileHull(target, HullSingleInput, {1u, 3u, 1u, 2u, 2u}).spirv.empty(), "a hull that reads only the one input control point of its patch was not compiled"); +} + } int main() { @@ -728,6 +755,7 @@ int main() { std::puts("tessellation: skipped, the device has no tessellation shaders"); return VulkanTestSkipped; } + RequireSingleControlPointHulls(target); ExpectRefusal(target, ShaderStage::TessellationControl, HullUnguardedFactors, "not written by control point 0 alone"); ExpectRefusal(target, ShaderStage::TessellationControl, HullSquaredAddress, "not a whole number of control points"); ExpectRefusal(target, ShaderStage::TessellationControl, HullHalfSlotIndex, "not a whole number of control points"); diff --git a/core/shader/recompiler/Optimization/src/TessellationLowering.cpp b/core/shader/recompiler/Optimization/src/TessellationLowering.cpp index 8d9e91c426..3834b28369 100644 --- a/core/shader/recompiler/Optimization/src/TessellationLowering.cpp +++ b/core/shader/recompiler/Optimization/src/TessellationLowering.cpp @@ -659,7 +659,7 @@ private: for (auto& access : accesses) { if (access.kind == TessellationAttribute::LocalOutput) agree(localStride, access.address.relative, access.pc); if (access.kind == TessellationAttribute::ControlInput && access.address.vertex != 0) agree(localStride, access.address.vertex, access.pc); - if (access.kind == TessellationAttribute::ControlInput && access.address.dynamic && access.address.relative > 0 && access.address.relative % inputs == 0) agree(localStride, access.address.relative / inputs, access.pc); + if (access.kind == TessellationAttribute::ControlInput && (access.address.dynamic || access.address.vertex == 0) && access.address.relative > 0 && access.address.relative % inputs == 0) agree(localStride, access.address.relative / inputs, access.pc); if (access.kind == TessellationAttribute::ControlOutput && access.address.vertex != 0) agree(controlStride, access.address.vertex, access.pc); if (access.kind == TessellationAttribute::EvaluationInput) { const auto end = access.address.constant + 4 * static_cast(access.components);