From b1e88d5995795b56b00df928280bdec345bd722c Mon Sep 17 00:00:00 2001 From: milescoviello Date: Fri, 16 Oct 2026 11:36:24 +0000 Subject: [PATCH 1/2] fix(agc): journal the mesh output primitive size #5884 added MeshConfiguration::outputPrimitiveSize, and #5875's pipeline journal guards the layouts it encodes. The two merged separately, so on Linux with libstdc++ PipelineJournal.cpp no longer compiles: MeshConfiguration is 40 bytes, ShaderStages (which holds an optional one) 112. The guards now name those sizes, and the recipe codec writes and reads the new field, so a replayed line-strip geometry pipeline keeps its two-vertex primitives. Co-Authored-By: Claude Opus 5.5 --- .../prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp | 8 ++++---- core/libs/prx/libSceAgcDriver/tests/Graphics.cpp | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp index 094b1ed9bd..8729ac4d9d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp @@ -36,8 +36,8 @@ static_assert(sizeof(VkVertexInputBindingDescription) == 12, "VkVertexInputBindi static_assert(sizeof(VkVertexInputAttributeDescription) == 16, "VkVertexInputAttributeDescription changed: update the recipe codec"); #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) static_assert(sizeof(State) == 752, "State changed: update CanonicalPipelineState and the recipe codec"); -static_assert(sizeof(ShaderStages) == 108, "ShaderStages changed: update the recipe codec"); -static_assert(sizeof(ShaderRecompiler::MeshConfiguration) == 36, "MeshConfiguration changed: update the recipe codec"); +static_assert(sizeof(ShaderStages) == 112, "ShaderStages changed: update the recipe codec"); +static_assert(sizeof(ShaderRecompiler::MeshConfiguration) == 40, "MeshConfiguration changed: update the recipe codec"); static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "TessellationConfiguration changed: update the recipe codec"); #endif @@ -147,11 +147,11 @@ void encodeOptional(Writer& writer, const std::optional& value, void (*e } void encodeMesh(Writer& writer, const ShaderRecompiler::MeshConfiguration& mesh) { - for (const auto value : {mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize}) writer.U32(value); + for (const auto value : {mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize, mesh.outputPrimitiveSize}) writer.U32(value); } void decodeMesh(Reader& reader, ShaderRecompiler::MeshConfiguration& mesh) { - for (auto* value : {&mesh.inputPrimitive, &mesh.primitivesPerGroup, &mesh.verticesPerGroup, &mesh.maxVertices, &mesh.maxPrimitives, &mesh.threadsPerGroup, &mesh.ldsSizeDwords, &mesh.provokingVertex, &mesh.esgsItemSize}) *value = reader.U32(); + for (auto* value : {&mesh.inputPrimitive, &mesh.primitivesPerGroup, &mesh.verticesPerGroup, &mesh.maxVertices, &mesh.maxPrimitives, &mesh.threadsPerGroup, &mesh.ldsSizeDwords, &mesh.provokingVertex, &mesh.esgsItemSize, &mesh.outputPrimitiveSize}) *value = reader.U32(); } void encodeTessellation(Writer& writer, const ShaderRecompiler::TessellationConfiguration& tessellation) { diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 5acb0bb165..dcc4ea5ae9 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -7537,6 +7537,11 @@ void pipelineJournalTests() { BuildPipelineStoreKey(grid, context, gridState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL); BuildPipelineStoreKey(gridReplayed, context, gridDecoded.state, gridDecoded.vertexInput, layouts.View(), shaders, gridDecoded.attachmentLayout); Require(grid != original && grid == gridReplayed, "the decoded sample grid state keys another pipeline"); + auto lineState = state; + lineState.stages.mesh = ShaderRecompiler::MeshConfiguration{1, 2, 2, 4, 4, 32, 64, 1, 4, 2}; + GraphicsPipelineRecipe lineDecoded; + Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(lineState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)), lineDecoded), "the line strip mesh recipe does not decode"); + Require(lineDecoded.state.stages.mesh && lineDecoded.state.stages.mesh->outputPrimitiveSize == 2u && lineDecoded.state.stages.mesh->esgsItemSize == 4u, "the recipe dropped the mesh output primitive size"); auto fourSampleState = state; fourSampleState.samples = 4; auto maskState = fourSampleState; From 2d5f761cb59239996a905915c3f20cce8f1a30ac Mon Sep 17 00:00:00 2001 From: milescoviello Date: Fri, 16 Oct 2026 11:45:11 +0000 Subject: [PATCH 2/2] fix(agc): discard pipeline journals written before the mesh field The layout guards only build on Linux with libstdc++, so a Windows or macOS build of main after #5884 wrote journals whose mesh records lack outputPrimitiveSize under the new shader-cache source version. The journal format is now 3, so such a file is discarded instead of being read with the new codec. MarcZX100 pointed this out. Co-Authored-By: Claude Opus 5.5 --- .../Graphics/src/PipelineJournal.cpp | 2 +- core/libs/prx/libSceAgcDriver/tests/Graphics.cpp | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp index 8729ac4d9d..77d4566132 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp @@ -44,7 +44,7 @@ static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "Tessel constexpr std::uint32_t FileMagic = 0x4c4a5041u; // Bumped when the recipe codec, CanonicalPipelineState or the store key change in a way the // layout guards above do not catch (a field renumbered or reinterpreted at the same size). -constexpr std::uint32_t FileFormat = 2u; +constexpr std::uint32_t FileFormat = 3u; constexpr std::uint32_t ModuleRecord = 1u; constexpr std::uint32_t GraphicsRecord = 2u; constexpr std::uint32_t ComputeRecord = 3u; diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index dcc4ea5ae9..d6c6af2d83 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -7656,6 +7657,19 @@ void pipelineJournalTests() { const auto after = PipelineJournalTotals(); Require(cancelled->Done() && after.replayFailed == 0 && after.replayRejected == 0 && after.replayedGraphics + after.replayPresent <= totals.replayedGraphics + totals.replayPresent + 1, "a cancelled replay did not stop cleanly"); } + { + const std::uint32_t meshWithoutOutputSize = 2u; + std::fstream file(PipelineJournalPath(), std::ios::in | std::ios::out | std::ios::binary); + file.seekp(4); + file.write(reinterpret_cast(&meshWithoutOutputSize), sizeof(meshWithoutOutputSize)); + file.close(); + Require(file.good(), "the journal header could not be rewritten"); + auto stale = StartPipelineJournalReplay(context, fingerprint, hooks); + Require(stale != nullptr, "the replay of a journal in the previous format did not start"); + stale->Wait(); + const auto discarded = PipelineJournalTotals(); + Require(discarded.loadedRecipes == 0 && discarded.loadedModules == 0, "a journal in format 2, whose mesh records lack the output primitive size, was loaded"); + } ClearCachedPipelines(context.device); } Require(replayedComputeIdentityLow != 0, "the replayed compute identity is empty");