From 25a83722c7d34d411846af0bfe42b4b70b3edbba Mon Sep 17 00:00:00 2001 From: Celegans12 <53881228+Celegans12@users.noreply.github.com> Date: Mon, 12 Oct 2026 01:22:21 +0200 Subject: [PATCH] fix(agc): bound tessellation patch records by the draw's patch count TessellationLowering proved ring and LDS addresses affine over a relative patch index of up to 256. A hull that lays its patch-constant records out downwards, at 0x8000 - 208 * (rel + 1) like the quad particle hull in Ghost of Yotei, then reached a negative address for the last slot and aborted as "not affine". The relative patch index of hull and domain programs stays below VGT_LS_HS_CONFIG.NUM_PATCHES (bits 7:0, patches per threadgroup), which is 63 for that draw. DecodeShaderStages now carries NUM_PATCHES as patchesPerGroup in the tessellation configuration (draw keys, hull and domain cache keys, recompile request version 22) and refuses zero, and the lowering bounds the relative patch index by it. Configurations without it keep 256. --- .../Execution/src/Driver/Draw/DrawLookup.cpp | 2 +- .../src/Driver/Draw/DrawRegisterKey.cpp | 2 +- .../prx/libSceAgcDriver/Graphics/src/Draw.cpp | 1 + .../libSceAgcDriver/Graphics/src/Pipeline.cpp | 1 + .../libSceAgcDriver/Graphics/src/State.cpp | 3 ++- .../prx/libSceAgcDriver/tests/Graphics.cpp | 6 +++-- .../libSceAgcDriver/tests/ShaderMemory.cpp | 21 +++++++++++++-- .../tests/execution/Tessellation.cpp | 27 ++++++++++++++----- core/shader/recompiler/CacheKey.hpp | 2 ++ .../ControlFlow/src/RequestSerializer.cpp | 10 ++++--- .../Optimization/ShaderStageInputInfo.hpp | 1 + .../Optimization/src/TessellationLowering.cpp | 3 +-- core/shader/recompiler/Recompiler.hpp | 1 + .../src/ShaderInputInfoBuilder.cpp | 2 ++ 14 files changed, 62 insertions(+), 20 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp index 3df0685a7..069e72aeb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp @@ -26,7 +26,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr& x, const std::optional& y) { if (x.has_value() != y.has_value()) return false; if (!x) return true; - return x->inputControlPoints == y->inputControlPoints && x->outputControlPoints == y->outputControlPoints && x->domain == y->domain && x->partitioning == y->partitioning && x->outputTopology == y->outputTopology; + return x->inputControlPoints == y->inputControlPoints && x->outputControlPoints == y->outputControlPoints && x->domain == y->domain && x->partitioning == y->partitioning && x->outputTopology == y->outputTopology && x->patchesPerGroup == y->patchesPerGroup; }; if (s.stages.path != t.stages.path || s.stages.registerValue != t.stages.registerValue || s.stages.vertexWaveSize != t.stages.vertexWaveSize || s.stages.fragmentWaveSize != t.stages.fragmentWaveSize || !sameMesh(s.stages.mesh, t.stages.mesh) || !sameTess(s.stages.tessellation, t.stages.tessellation)) return false; if (!sameColor(s.color, t.color) || s.colors.size() != t.colors.size() || s.blends.size() != t.blends.size()) return false; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index b21d0850b..b7dd5158f 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -678,6 +678,7 @@ bool ValidationKey(const Context& context, std::span shade add(tessellation.domain); add(tessellation.partitioning); add(tessellation.outputTopology); + add(tessellation.patchesPerGroup); } } return keyed; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 1a57c3872..6eeefefa2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -827,6 +827,7 @@ void pipelineKey(std::vector& key, const Context& context, const Stat append(key, tessellation.domain); append(key, tessellation.partitioning); append(key, tessellation.outputTopology); + append(key, tessellation.patchesPerGroup); } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 404f0ccef..15a1a57a4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -612,8 +612,9 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) { validate((value & ~0x0207ed0du) == 0 && (value & 0x02002000u) == 0x02002000u && (value & 3u) == 1u && ((value >> 3u) & 3u) == 1u, "unsupported tessellation routing"); const auto config = read(queue.context, 0x2d6); const auto parameters = read(queue.context, 0x2db); - ShaderRecompiler::TessellationConfiguration tess{(config >> 8u) & 0x3fu, (config >> 14u) & 0x3fu, parameters & 3u, (parameters >> 2u) & 7u, (parameters >> 5u) & 7u}; + ShaderRecompiler::TessellationConfiguration tess{(config >> 8u) & 0x3fu, (config >> 14u) & 0x3fu, parameters & 3u, (parameters >> 2u) & 7u, (parameters >> 5u) & 7u, config & 0xffu}; validate(tess.inputControlPoints != 0 && tess.inputControlPoints <= 32 && tess.outputControlPoints != 0 && tess.outputControlPoints <= 32, "invalid tessellation control-point counts"); + validate(tess.patchesPerGroup != 0, "VGT_LS_HS_CONFIG.NUM_PATCHES is zero"); if ((parameters & ~0x000600ffu) != 0) Require(false, zeroMessage(0x2db, parameters, "VGT_TF_PARAM donut, factor detection, memory type or reserved state")); validate((tess.domain == 1 || tess.domain == 2) && tess.partitioning == 2 && (tess.outputTopology == 2 || tess.outputTopology == 3), "only triangle and quad domains with fractional-odd partitioning and triangle output are supported by the reference path"); result.minTessellationLevel = readFloat(queue.context, 0x287); diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 6338e2462..8c9c3d3d8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -599,7 +599,7 @@ void ShaderStageTests() { } Require(stages.path == AgcDriver::Graphics::ShaderPath::Tessellation && stages.tessellation, "tessellation routing changed"); const auto& tessellation = *stages.tessellation; - Require(tessellation.inputControlPoints == 3 && tessellation.outputControlPoints == 3 && tessellation.domain == 1 && tessellation.partitioning == 2 && tessellation.outputTopology == 2 && stages.minTessellationLevel == 1.0f && stages.maxTessellationLevel == 64.0f, "the passthrough triangle-patch configuration decoded differently"); + Require(tessellation.inputControlPoints == 3 && tessellation.outputControlPoints == 3 && tessellation.domain == 1 && tessellation.partitioning == 2 && tessellation.outputTopology == 2 && stages.minTessellationLevel == 1.0f && stages.maxTessellationLevel == 64.0f && tessellation.patchesPerGroup == 85, "the passthrough triangle-patch configuration decoded differently"); for (const auto [parameters, reason] : {std::pair{0x44049u, "VGT_TF_PARAM"}, std::pair{0x140049u, "VGT_TF_PARAM"}, std::pair{0x2040049u, "VGT_TF_PARAM"}, std::pair{0x40059u, "fractional-odd partitioning"}, std::pair{0x4004du, "fractional-odd partitioning"}, std::pair{0x40048u, "fractional-odd partitioning"}, std::pair{0x40029u, "fractional-odd partitioning"}, std::pair{0x4004bu, "fractional-odd partitioning"}}) { queue.context[0x2db] = parameters; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, reason); @@ -609,7 +609,7 @@ void ShaderStageTests() { queue.context[0x286] = 0x41100000; const auto quadStages = AgcDriver::Graphics::DecodeState(queue).stages; const auto& quads = quadStages.tessellation; - Require(quads && quads->inputControlPoints == 4 && quads->outputControlPoints == 4 && quads->domain == 2 && quads->partitioning == 2 && quads->outputTopology == 3 && quadStages.minTessellationLevel == 1.0f && quadStages.maxTessellationLevel == 9.0f, "counter-clockwise quad patches with HOS levels [1, 9] decoded differently"); + Require(quads && quads->inputControlPoints == 4 && quads->outputControlPoints == 4 && quads->domain == 2 && quads->partitioning == 2 && quads->outputTopology == 3 && quadStages.minTessellationLevel == 1.0f && quadStages.maxTessellationLevel == 9.0f && quads->patchesPerGroup == 63, "counter-clockwise quad patches with HOS levels [1, 9] and 63 patches per group decoded differently"); queue.context[0x2db] = 0x40069; Require(AgcDriver::Graphics::DecodeState(queue).stages.tessellation->outputTopology == 3, "counter-clockwise triangle patches decoded differently"); for (const auto [maximum, minimum, reason] : {std::tuple{0x3f800000u, 0x40000000u, "tessellation level clamps"}, std::tuple{0x41100000u, 0xbf800000u, "tessellation level clamps"}, std::tuple{0x3f000000u, 0x00000000u, "tessellation level clamps"}, std::tuple{0x42800000u, 0x42800000u, "tessellation level clamps"}, std::tuple{0x7fc00000u, 0x3f800000u, "non-finite register"}, std::tuple{0x7f800000u, 0x3f800000u, "non-finite register"}}) { @@ -626,6 +626,8 @@ void ShaderStageTests() { queue.context[0x2d5] = 0x0200210d; queue.context[0x2d6] = 0; expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "control-point counts"); + queue.context[0x2d6] = 0xc300; + expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "NUM_PATCHES is zero"); queue = makeState(); for (const auto value : {0x2003u, 0x2018u, 0x20c0u, 0x80002000u}) { queue.context[0x2d5] = value; diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index 907f26bfc..967549361 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -1686,6 +1686,23 @@ 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"); + RecompileRequest hull{}; + hull.shader = {ShaderStage::TessellationControl, 0x10000u, code, 0, {}}; + hull.context.waveSize = 64; + hull.graphics = GraphicsCompileContext{0u, {}, std::nullopt, TessellationConfiguration{4u, 4u, 2u, 2u, 3u, 63u}, {}}; + const auto hullReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(hull)); + const auto* replayed = hullReplay.request.graphics && hullReplay.request.graphics->tessellation ? &*hullReplay.request.graphics->tessellation : nullptr; + require(replayed != nullptr && replayed->domain == 2u && replayed->outputTopology == 3u && replayed->patchesPerGroup == 63u, "the quad tessellation configuration or its patches per group were lost in serialization"); + auto grouped = hull; + grouped.graphics->tessellation->patchesPerGroup = 62u; + for (const auto stage : {ShaderStage::Local, ShaderStage::TessellationControl, ShaderStage::TessellationEvaluation}) { + hull.shader.stage = stage; + grouped.shader.stage = stage; + RecompileCacheKey::Build(hull, key); + const auto patchKey = key; + RecompileCacheKey::Build(grouped, key); + require((key != patchKey) == (stage != ShaderStage::Local), "only the hull and domain cache keys may depend on the patches per group, which bound their relative patch index"); + } const std::array users{}; request.context.waveSize = 32u; request.context.userData = users; @@ -1826,13 +1843,13 @@ void verifyPixelRequestSerialization() { minimal.context.pixel = ShaderPixelStageInfo{}; minimal.context.pixel->rasterPrimitive = RasterPrimitive::Rectangle; const auto encoded = serializer.Serialize(minimal); - require(requestPrefix(encoded, 8u) == "NVNQQRUAAAA=", "new requests did not use serialization version 21"); + require(requestPrefix(encoded, 8u) == "NVNQQRYAAAA=", "new requests did not use serialization version 22"); require(serializer.Deserialize(encoded).request.context.pixel->rasterPrimitive == RasterPrimitive::Rectangle, "the pixel primitive class did not round-trip"); constexpr std::size_t mappingOffset = 8u + 37u + 18u + 163u; for (std::size_t bytes = 0; bytes < 20u; ++bytes) { expectFailure([&] { static_cast(serializer.Deserialize(requestPrefix(encoded, mappingOffset + bytes))); }, "truncated data", "a truncated pixel mapping, packing, dual-source, alpha-to-coverage, stencil reference export flag or primitive class was accepted"); } - for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRYAAAA="}) { + for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQRcAAAA="}) { expectFailure([&] { static_cast(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted"); } static constexpr std::string_view version18Request{ diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp index 4ecd835a2..ca00752f2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Tessellation.cpp @@ -64,6 +64,16 @@ alignas(256) constexpr std::array LocalIndexAsData{ 0x0000030a, 0xd8340010, 0x0000070a, 0xbf8cc07f, 0xbefd2106, }; +alignas(256) constexpr std::array HullDescendingRecords{ + 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, + 0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, + 0x02000015, 0x162c2890, 0xd5430016, 0x045a24ff, 0x00000030, 0xbf8cc07f, 0xe0781000, 0x02020216, + 0x7da82881, 0xbe94047e, 0xb06a0060, 0x162602f9, 0x0086066a, 0xd8d80008, 0x06000013, 0xd8dc1008, + 0x07000013, 0xbf8cc07f, 0x7e300306, 0x7e320306, 0x7e340306, 0x7e360306, 0x34142484, 0xbefe0414, + 0xe0781000, 0x0403180a, 0x4a162481, 0x161616ff, 0x000000d0, 0x4c1616ff, 0x00008000, 0xe0741000, + 0x0202070b, 0xbf810000, +}; + alignas(256) constexpr std::array HullUnguardedFactors{ 0xd7650000, 0x000100c1, 0xd7660000, 0x000200c1, 0xbf8a0000, 0x7da206f9, 0x81060000, 0xd5480014, 0x02151101, 0x362402ff, 0x000000ff, 0x160028a0, 0xd5430015, 0x040224ff, 0x00000060, 0xdbfc0000, @@ -297,24 +307,24 @@ std::uint32_t CoveredPixels(std::uint32_t patch, bool interior) { return count; } -void RequirePatches(AgcDriver::VulkanDevice& device, const ShaderRecompiler::SpirvTarget& target, std::span hull, const std::string& program) { +void RequirePatches(AgcDriver::VulkanDevice& device, const ShaderRecompiler::SpirvTarget& target, std::span hull, const std::string& program, const PatchDraw& patches = Triangles) { std::uint32_t cursor = 0; const Stages stages{ - Compile(target, ShaderStage::Local, LocalCode, cursor), - Compile(target, ShaderStage::TessellationControl, hull, cursor), - Compile(target, ShaderStage::TessellationEvaluation, DomainCode, cursor), - Compile(target, ShaderStage::Fragment, PixelCode, cursor), + Compile(target, ShaderStage::Local, LocalCode, cursor, patches), + Compile(target, ShaderStage::TessellationControl, hull, cursor, patches), + Compile(target, ShaderStage::TessellationEvaluation, DomainCode, cursor, patches), + Compile(target, ShaderStage::Fragment, PixelCode, cursor, patches), }; const auto center = Row(-0.5f + 1.0f / 3.0f); for (const auto cull : {VK_CULL_MODE_NONE, VK_CULL_MODE_BACK_BIT}) { - Draw(device, stages, cull, VK_FRONT_FACE_COUNTER_CLOCKWISE); + Draw(device, stages, cull, VK_FRONT_FACE_COUNTER_CLOCKWISE, patches); const std::string name = program + (cull == VK_CULL_MODE_NONE ? ", unculled" : ", back-face culled"); Require(CoveredPixels(0, false) > 100 && CoveredPixels(0, true) == 0, name + ": the level-1 patch was not drawn as one flat triangle, centre " + PixelText(PixelAt(Column(0, 0.25f), center))); Require(CoveredPixels(1, false) == 0, name + ": the patch with level 0 was not culled"); Require(CoveredPixels(2, false) > 100 && PixelAt(Column(2, 0.25f), center)[1] > 32, name + ": the level-5 patch has no interior domain points at its centre, " + PixelText(PixelAt(Column(2, 0.25f), center))); Require(PixelAt(0, 0) == Background && PixelAt(Width - 1, Height - 1) == Background, name + ": the corners changed"); } - Draw(device, stages, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE); + Draw(device, stages, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, patches); Require(CoveredPixels(0, false) == 0 && CoveredPixels(2, false) == 0, program + ": tessellated triangles did not keep the control points' winding"); } @@ -443,10 +453,13 @@ int main() { ExpectRefusal(target, ShaderStage::TessellationEvaluation, DomainCulledPrimitive, "passthrough primitive"); ExpectRefusal(target, ShaderStage::Local, LocalIndexAsData, "outside a tessellation ring or LDS address"); ExpectRefusal(target, ShaderStage::TessellationControl, HullCode, "outside its patch's quad factors", {{3u, 3u, 2u, 2u, 2u}, Indices}); + ExpectRefusal(target, ShaderStage::TessellationControl, HullDescendingRecords, "not affine"); + ExpectRefusal(target, ShaderStage::TessellationControl, HullDescendingRecords, "not affine", {{3u, 3u, 1u, 2u, 2u, 158u}, Indices}); RequirePatches(*device, target, HullCode, "hull program"); RequirePatches(*device, target, HullRestoredExecSaved, "hull program saving its restored exec"); RequirePatches(*device, target, HullDynamicIndex, "hull program reading a control point at a computed index"); + RequirePatches(*device, target, HullDescendingRecords, "hull program with descending patch records in 157-patch groups", {{3u, 3u, 1u, 2u, 2u, 157u}, Indices}); RequireCounterClockwise(*device, target); RequireLevelClamps(*device, target); RequireQuads(*device, target); diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp index 19c4d46bb..14fd7574d 100644 --- a/core/shader/recompiler/CacheKey.hpp +++ b/core/shader/recompiler/CacheKey.hpp @@ -106,6 +106,8 @@ private: append(key, tessellation != nullptr); if (tessellation == nullptr) return; for (const auto value : {tessellation->inputControlPoints, tessellation->outputControlPoints, tessellation->domain, tessellation->partitioning, tessellation->outputTopology}) append(key, value); + if (request.shader.stage == ShaderStage::Local) return; + append(key, tessellation->patchesPerGroup); } template diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index e01487069..ab5257176 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -520,15 +520,17 @@ void writeTessellationConfiguration(Writer& writer, const TessellationConfigurat writer.WriteU32(configuration.domain); writer.WriteU32(configuration.partitioning); writer.WriteU32(configuration.outputTopology); + writer.WriteU32(configuration.patchesPerGroup); } -TessellationConfiguration readTessellationConfiguration(Reader& reader) { +TessellationConfiguration readTessellationConfiguration(Reader& reader, std::uint32_t version) { TessellationConfiguration configuration{}; configuration.inputControlPoints = reader.ReadU32(); configuration.outputControlPoints = reader.ReadU32(); configuration.domain = reader.ReadU32(); configuration.partitioning = reader.ReadU32(); configuration.outputTopology = reader.ReadU32(); + if (version >= 22u) configuration.patchesPerGroup = reader.ReadU32(); return configuration; } @@ -731,7 +733,7 @@ GraphicsCompileContext readGraphicsCompileContext(Reader& reader, DeserializedGr graphics.mesh = storage.mesh; } if (reader.ReadBool()) { - storage.tessellation = readTessellationConfiguration(reader); + storage.tessellation = readTessellationConfiguration(reader, version); graphics.tessellation = storage.tessellation; } storage.draw.indexAddress = reader.ReadU64(); @@ -748,7 +750,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const std::string buffer; Writer writer(buffer); writer.WriteU32(0x41505335u); - writer.WriteU32(21u); + writer.WriteU32(22u); writeShaderBinary(writer, request.shader); writeGuestContext(writer, request.context); writeSpirvTarget(writer, request.target); @@ -779,7 +781,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const Reader reader(decoded); if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature"); const auto version = reader.ReadU32(); - if (version < 1u || version > 21u) throw std::runtime_error("unsupported recompile request serialization version"); + if (version < 1u || version > 22u) throw std::runtime_error("unsupported recompile request serialization version"); DeserializedRequest result{}; result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader); result.request.context = readGuestContext(reader, result, version); diff --git a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp index ba65e3242..f5029b374 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ShaderStageInputInfo.hpp @@ -112,6 +112,7 @@ struct ShaderTessellationInputInfo { std::uint32_t domain = 0; std::uint32_t partitioning = 0; std::uint32_t outputTopology = 0; + std::uint32_t patchesPerGroup = 256; }; struct ShaderVertexInputInfo { diff --git a/core/shader/recompiler/Optimization/src/TessellationLowering.cpp b/core/shader/recompiler/Optimization/src/TessellationLowering.cpp index 91766c3ed..6b3854848 100644 --- a/core/shader/recompiler/Optimization/src/TessellationLowering.cpp +++ b/core/shader/recompiler/Optimization/src/TessellationLowering.cpp @@ -17,7 +17,6 @@ namespace ShaderRecompiler { namespace { -constexpr std::int64_t PatchIndexCount = 256; constexpr std::int64_t LocalVertexIndexCount = 8192; constexpr std::int64_t TriangleFactorBytes = 16; constexpr std::int64_t QuadFactorBytes = 24; @@ -418,7 +417,7 @@ std::uint32_t ComponentCount(IrOpcode opcode) { class Lowering { public: Lowering(IrProgram& program, const ShaderTessellationInputInfo& tessellation) - : program(program), tessellation(tessellation), stage(program.Resources().stage), evaluator(stage == IrShaderStage::TessellationControl ? tessellation.outputControlPoints : 0u, stage == IrShaderStage::Local ? LocalVertexIndexCount : PatchIndexCount) {} + : program(program), tessellation(tessellation), stage(program.Resources().stage), evaluator(stage == IrShaderStage::TessellationControl ? tessellation.outputControlPoints : 0u, stage == IrShaderStage::Local ? LocalVertexIndexCount : static_cast(tessellation.patchesPerGroup)) {} void Run() { for (IrBlock* block : program.BlockOrder()) { diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 7cd0b618c..d1f53bb4d 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -286,6 +286,7 @@ struct TessellationConfiguration { std::uint32_t domain; std::uint32_t partitioning; std::uint32_t outputTopology; + std::uint32_t patchesPerGroup = 256; }; inline constexpr std::uint32_t MeshDrawPushOffsetBytes = 104; diff --git a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp index 368d9f857..5eab8b673 100644 --- a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp +++ b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp @@ -181,12 +181,14 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues if (tessellation == nullptr) throw std::runtime_error("ShaderInputInfoBuilder: tessellation configuration is missing"); if (tessellation->inputControlPoints == 0u || tessellation->inputControlPoints > 32u || tessellation->outputControlPoints == 0u || tessellation->outputControlPoints > 32u) throw std::runtime_error("ShaderInputInfoBuilder: invalid tessellation control-point counts"); if ((tessellation->domain != 1u && tessellation->domain != 2u) || tessellation->partitioning != 2u || (tessellation->outputTopology != 2u && tessellation->outputTopology != 3u)) throw std::runtime_error("ShaderInputInfoBuilder: unsupported tessellation configuration"); + if (tessellation->patchesPerGroup == 0u || tessellation->patchesPerGroup > 256u) throw std::runtime_error("ShaderInputInfoBuilder: invalid tessellation patches per group"); auto& target = vertexStorage.tess; target.inputControlPoints = tessellation->inputControlPoints; target.outputControlPoints = tessellation->outputControlPoints; target.domain = tessellation->domain; target.partitioning = tessellation->partitioning; target.outputTopology = tessellation->outputTopology; + target.patchesPerGroup = tessellation->patchesPerGroup; } if (stage == ShaderStageKind::Mesh) { if (mesh == nullptr) throw std::runtime_error("ShaderInputInfoBuilder: a mesh-stage program has no mesh configuration");