Repository navigation
perf(recompiler): keep the front-end resource plan in the shader disk cache - #4883
Conversation
|
A title measurement: Resident Evil Village (PPSA01556, 01.210.000) on Windows 11, RTX 3070 Laptop, WinLibs GCC 15.2.0 r7, Release. It registers its whole PSO cache at boot behind a black screen, about 6,000 shaders, so the boot measures shader preparation directly. The build is
The cold run took about 240 s to reach 2,800 shaders. The front end is the bottleneck of this boot, and with this PR a warm boot skips most of it. A heads-up on the tests: #5181 leaves branch conditions the CPU walk can never evaluate out of resource plans. I had the same idea locally. With it, |
d6cd9f2 to
3d86335
Compare
|
Thank you, that's the title measurement this PR needed. On a warm cache, Resident Evil Village gets all of its roughly 2,900 shaders back by 32 s with plans from the disk cache, against about 1,400 without them. The Thanks also for the #5181 heads-up. I'm adding a fixture whose plan keeps an evaluable phi, so the coverage check holds in whichever order the two PRs land. I'll post here when it's pushed. |
…lope encodeInfo and decodeInfo wrote the ShaderInfo fields inline between the compiled-shader header and the binding layout, and EncodeEntry and DecodeEntry built and checked the file header (magic, format, source version, key and payload hashes) inline. Move both into helpers, encodeShaderInfo/decodeShaderInfo and wrapEntry/unwrapEntry, so that a second kind of entry can reuse them. The bytes of a compiled variant entry do not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ache The disk cache keeps compiled variants (SPIR-V, shader info, binding layout) but not the IrResourcePlan the front end extracts for the resource walk, so a process can only get a plan by running the whole front end. Since boykopovar#471 the plan owns its values (ownPlanValues clones the graph behind every root into valueStorage and blockStorage), so it can be written out and rebuilt. - EncodePlan writes the value graph in storage order, each value as LEB128 integers: opcode, type, id, flags, register, operand count, the immediate if it has one, each argument as the zigzag distance to the value's own index (0 for null) and, for phis, the index of each predecessor block. The roots (descriptor source dwords, SRT reads, control-flow conditions, uniform-fill values) are value indices; the plain data (memory info, indirect image tables, control-flow edges, materialization sources, guarded, clean and pure flat slots, the flags, the ShaderInfo through the variant's encoder) follows. A plan whose roots or arguments point outside its storage is not encoded. Control-flow conditions can pull most of a program into the plan: a synthetic 2,038-dword compute shader with a branch every 20 dwords has 42,775 plan values, which take 518 KB this way and 2.5 MB with fixed-width fields; its compiled variant entry is 935 KB. - DecodePlan rebuilds the graph the way ownPlanValues does and rejects an unknown opcode, an overlong integer, an index past the values or blocks, a descriptor source, image, buffer or sampler past the sources, a branch without two successors, an edge past the blocks, and a uniform fill of more than four words. - BuildPlanKey is the in-memory source key (RecompileCacheKey::Build and the host subgroup size), every code word and the switch key, after a plan entry tag. It leaves out the binding layout, so all layouts of a shader share one plan. - EncodePlanEntry/DecodePlanEntry use the variant's header and hashes; LoadPlan and StorePlan use the same directory and background writer and count plan hits, misses, writes and load failures on their own. - Size asserts cover the plan and the structures it is written from. - FormatVersion 30. agc_shader_disk_cache now round-trips the plans of 14 in-tree shader fixtures (SRT reads, guarded and chained pointers, control-flow conditions, an indirect image table, a loop phi, a fragment shader) and a hand-built plan with a uniform fill. For each it materializes the original and the decoded plan over 1000 random user-data and memory runtimes and requires the same snapshot, poison, null-root count and read trace, or the same error. Truncated, bit-flipped and other-format entries are rejected, malformed plans do not decode, and the key follows the code, the target and the host subgroup size but not the layout, addresses or user data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he front end getSource built ResourceProgram (PrepareResourceProgram and ExtractPlan) for the first request of every shader a process saw, before findOrCompileVariant looked in the disk cache. With a warm cache every launch still decoded, built the CFG, structurized, translated and ran SSA, the fold passes, SrtWalker and ResourceTracker for every shader; only SPIR-V emission was skipped. getSource now builds the plan key and tries LoadPlan first. On a hit it sets the plan and leaves the program null: findOrCompileVariant already rebuilds the program when no stored variant fits the layout. The loaded plan takes the request's code address as its shader hash, as a plan built in this process would. On a miss it builds the plan as before and stores it. A plan the front end refuses is not stored. The disk is not used when the cache is off, under the debug probe, with APS5_DUMP_IR (which prints the front-end passes), or with APS5_NO_PERF_DISK_PLANS=1, a cache-neutral switch for comparisons. FrontEndBuilds() counts PrepareResourceProgram calls. The new cross-process test prepares, captures and materializes 14 shader fixtures in one process and repeats it in a second process sharing the cache directory: the second process must run the front end 0 times, hit 14 plans and 14 variants, and produce byte-identical SPIR-V, bindings, push constants, snapshots, read traces and plans. A damaged plan entry is rejected and rebuilt alone with the same output and stored again; a changed code word and another switch value miss. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ng source DecodePlan checked every descriptor source index a plan holds except the two inside an indirect image table: the heap source and the material source. A damaged entry could therefore decode with a table that points past the plan's descriptor sources, and only the materializer's own bounds checks stood between it and an out-of-range read. The front end interns both sources before the image source that names them, so a plan it builds always passes the new check. agc_shader_disk_cache now requires a plan whose table heap or material source lies past the sources not to decode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plan tests compared a decoded plan with the original only through the codec itself (re-encoding must give the same bytes) and through materialization. Neither catches a field that EncodePlan and DecodePlan both leave out: the bytes still agree, and the walk does not read every field. Leaving userDataBase, the plan's sRGB formats, the value ids or several MemoryInfo fields (secondaryOffset, dataDwords, dataFormat, planningOnly, idxen) out of both sides passed every test. The hand-built plan also never materializes (its uniform values cannot be evaluated), so its 1000 runtimes all failed with the same error, and no plan held an immediate wider than 32 bits. samePlan compares every field of two plans without the codec: the value graph by storage index (opcode, type, id, flags, register, immediate, arguments, phi blocks), the block ids, the roots, and the plain data through the structures' own equality. The round trip now requires it for the 14 fixtures and the hand-built plan, which sets every MemoryInfo field, all indirect table fields, a second buffer, the whole uniform fill and a 64-bit immediate to values other than their defaults. The second process also builds each fixture's plan with its own front end and requires it to equal the plan it used, so a stored plan must match what the front end builds in another process, not only its own re-encoding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
APS5_NO_PERF_DISK_PLANS=1 is listed as a neutral switch, so it stays out of the variant key, and it exists to compare warm starts with and without stored plans. Nothing tested either property: the cross-process test ran with the switch unset only. A fourth child process now sets the switch against the cache the first process filled. It must run the front end for all 14 fixtures, neither load nor store a plan, still hit all 14 stored variants, and produce the same SPIR-V, bindings, push constants, snapshots, read traces and plans as the first process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…test The round trip requires some fixture to plan a phi, and only one did: the loop counter of "bindless sample in a loop latch", which reaches the plan through the latch condition. The counter is not invariant, so the CPU walk can never evaluate that condition. A plan that keeps only the conditions the walk can evaluate, as boykopovar#5181 aims to, would hold no phi and fail the check with "no fixture plans a phi". "descriptor invariant phi" loads the image descriptor from the same SRT offset on both sides of a branch on an SRT dword. Its eight dwords are phis of equivalent reads: the tracker keeps them, since it splits only descriptor phis that are not invariant, and the walk evaluates them through the invariant read. The plan stores them as descriptor dwords, whichever conditions it keeps, so the phi encoding stays covered. The fixture goes after the other compute fixtures, so they keep their seeds and their damaged-entry checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ec writes The plan and ShaderInfo encoders write these structures field by field, and the sizeof asserts are their only reminder to update the encoders. A field that fits in existing padding keeps the size: main's layerExported landed in ShaderInfo with sizeof still 232, and the three bools boykopovar#5089 adds to DescriptorSource::IndirectImage keep it at 28. A warm cache would then hand back entries without the new field and nothing would fail. Count the fields of every aggregate the plan codec reaches (IrResourcePlan, GuestRegister, MemoryInfo, DescriptorSource and IndirectImage, ResourceBlock, SrtRead, UniformFill, UniformFillPlan, and ShaderInfo with its resource and stage I/O records) by brace-initializing each with more and more convertible-to-anything values until the initializer no longer compiles, and assert the counts. Unlike the size asserts this does not depend on the ABI, so it is checked on every platform. IrValue is a class with private state and keeps only its size assert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
boykopovar#5097 added ShaderInfo::layerExported to the entry encoder, and this series moves that line into encodeShaderInfo, which plan entries use too. The entry round trip left the flag false, so an encoder and a decoder that both skipped it still passed. Set it like the other ShaderInfo flags: with layerExported dropped from both, the round trip now fails with "entry round trip: shader info differs". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
buffer_store_format_* and its D16 forms wrote memory component i from data VGPR i and ignored the V#'s DST_SEL, while loads through the same V# apply it. A store through a swizzled V# therefore did not read back what it stored: with 8_8_8_8_UNORM and DST_SEL Z,Y,X,W, storing (1.0, 0.75, 0.2, 0.25) wrote 0x4033bfff instead of 0x40ffbf33. The RDNA 2 ISA guide (section 8.1.4, table 31) takes DST_SEL from the resource for BUFFER_STORE_FORMAT_*. RADV builds texel-buffer V#s with the format's swizzle (Z,Y,X,W for B8G8R8A8, 0,0,0,X for A8) and ACO stores through them unchanged, so each format component takes the data slot whose selector names it, and slots that select 0 or 1 write nothing. StoreTexel applies the same rule to image stores. TBUFFER stores keep the identity selectors. These sources settle only a DST_SEL that names each format component once. A V# that names one twice or not at all, or holds a reserved selector, now throws when the binding plan is prepared, and a GPU-selected one records an invalid-descriptor fault and skips the store. ShaderInfo records which buffers have formatted stores (disk cache format 31, as the open boykopovar#4883 takes 30). Technical debt records that none of this is measured on hardware. agc_driver_buffer_store_format_dst_sel stores through Z,Y,X,W 8_8_8_8_UNORM (also D16 and store_format_x), Y,Z,X,W 16_16_16_16_UINT, 0,0,0,X 8_UINT and X,0,0,1 32_UINT V#s in wave32 and wave64, reads the first three back with buffer_load_format_xyzw, checks that three unsettled selector sets are refused, and compiles a store through a GPU-selected V#. On main it fails with red and blue swapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3d86335 to
5d3ea5f
Compare
|
@EagleBearerYS the fixture is pushed in ce8944f. "descriptor invariant phi" loads one image descriptor from the same SRT offset on both sides of a branch. Its plan keeps eight invariant phis as descriptor dwords, and the CPU walk evaluates them from both the built plan and the stored one. As it turns out, with #5181's current head (16bd650) the phi check would pass even without it. #5181's The same push rebases onto |
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
|
…474) * fix(shader): verify materialized snapshot identity before memo reuse Ported from upstream PR boykopovar#4757. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(shader): emit both registration layouts of a pixel shader from one program Ported from upstream PR boykopovar#4884. The libSceAgcDriver test hunks are not ported: those tests are not built by any macOS configuration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(recompiler): keep the front-end resource plan in the shader disk cache Ported from upstream PR boykopovar#4883. A warm launch loads the stored resource plan and skips the shader front end. ShaderDiskCache FormatVersion 32 -> 33. The libSceAgcDriver test hunks are not ported: those tests are not built by any macOS configuration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(shader): compile reusable SRT expression plans Ported from upstream PR boykopovar#1341. The disk-cache plan decoder from boykopovar#4883 rebuilds the execution plan after a stored plan is decoded, since it is derived from the value graph and not stored. The libSceAgcDriver test hunks are not ported: those tests are not built by any macOS configuration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(shader): keep only evaluable branch conditions in resource plans Ported from upstream PR boykopovar#5181. The plan values now live in an IrValueArena, so the disk-cache plan codec from boykopovar#4883 walks and fills the arena instead of a vector of owned values. The libSceAgcDriver test hunks and the docs hunk are not ported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(shader): ignore unused descriptor fields for scalar buffer loads Ported from upstream PR boykopovar#5049. The test and docs hunks are not ported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(agc): cheaper shader-memory captures per draw (ShaderMemory half) Ported from upstream PR boykopovar#1193, ShaderMemory.hpp/.cpp of its last commit only: word-mask page marks, pooled pages with a last-page shortcut, and lazily copied 64-byte blocks for pages no recorded GPU work writes. Adapted for native guest memory: a lazy block is read through GuestMemory::Read instead of the guest address as a host pointer, and NativeGuestMemory gains a no-op FlushGpuWrites (it keeps no deferred GPU writes). The SRT evaluator half is superseded by boykopovar#1341; the draw cache commits are stacked on upstream #494/boykopovar#1191 and not ported. The capture profile line keeps its current fields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(graphics): retain vertex descriptor decode plans (ShaderInputState half) Ported from upstream PR boykopovar#1313, Graphics/ShaderInputState only. The Metal draw path calls DecodeVertexStageInfo per draw, which now builds a VertexStagePlan and reads only the referenced, coalesced table words. The Vulkan DrawDecode/DrawCache hooks that keep the plan across draws are not ported (that driver is not built on macOS). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(graphics): retain vertex input layouts (Metal draw) Ported from upstream PR boykopovar#1316 and adapted to Metal. VertexInputCache (LRU per shader variant, shape-checked on every hit, a variant seen with two shapes is bypassed) now takes a layout builder, so MetalDraw.mm keeps the layout it built with the Metal format predicate instead of calling BuildVertexInputLayout on every draw. Vertex buffer addresses are still validated per draw. The Vulkan Context/VulkanDevice/Draw.cpp hooks and the libSceAgcDriver test are not ported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(shader): build and run the shader disk cache codec tests on macOS Registers anyps5_metal_shader_disk_cache with ctest in the Metal guest-replay configuration and adds a plan round trip: value arena, rebuilt SRT execution plan (evaluated against the original), phi blocks, indirect image tables, and the 9-field SamplerResource / 5-field SampledResourcePair encodings. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
EncodePlan and DecodePlan from boykopovar#4883 walked valueStorage as a vector of owned values; they now use IrValueArena, and decoded operands are detached.
EncodePlan and DecodePlan from boykopovar#4883 walked valueStorage as a vector of owned values; they now use IrValueArena, and decoded operands are detached. (cherry picked from commit dc0ca9d)
EncodePlan and DecodePlan from boykopovar#4883 walked valueStorage as a vector of owned values; they now use IrValueArena, and decoded operands are detached.
…2bdcdb) with the boykopovar#757/boykopovar#758 integration (b26df6a, a9a751c) From SleglJan (Jan Šlégl), fix/757-key-domain-v2 @ 92bdcdb: a table column's identity is its heap access shape alone, so reads of one table through different keys share one column, one table image and one set of decode modes. The column holds a list of key domains and an unkeyed flag; each access merges its domain into the shared column and keeps its own domain source alive. An access without a domain freezes the column unkeyed (whole snapshot); a store to any key buffer clears every domain; narrowing takes the union of all domains' records. Integrator adaptations as in b26df6a / a9a751c: a masked (key << n) & mask read marks the shared column unkeyed; a shared column takes the widest maxKey of its reads; `address` is part of the column's identity. On the game branch `recordPointer` is part of it as well. Skipped: the boykopovar#4883 plan encoder/decoder fields and plan tests (the game branch has no front-end resource plan in the disk cache), and SRT value key domains (KeyDomain::value), which the game branch's boykopovar#757 predates. SleglJan's verifyImageTableKeyUnion/LateKey/UnkeyedAccess are ported with keys read from the material V# at a lane-uniform selector instead of SRT constants.
What
With a warm shader cache, every launch still runs the whole recompiler front end once for every shader.
getSourcebuildsResourceProgram(decode, CFG, structurizer, translation, SSA, the fold passes, SrtWalker, ResourceTracker, thenExtractPlan) beforefindOrCompileVariantlooks in the disk cache, because the disk cache keeps compiled variants but not the resource plan. Only SPIR-V emission is skipped. #2292 measured 0.3 to 0.8 s per registration with 0 cache misses and lists this fix as its option 4. #2774 (about 1,950 hits, no misses) found the plan "is built beforeShaderDiskCache::Loadis consulted". #3504 hit its slow pass "with a warm shader cache", and #4404 reports hundreds of queued preparation jobs on a warm run with zero misses.This PR stores the plan in the disk cache and loads it before the front end:
ownPlanValues), so it can be written out.EncodePlanwrites the value graph in storage order as LEB128 integers (opcode, type, id, flags, register, immediate, each argument as the zigzag distance to the value's own index, phi predecessor blocks), the roots as value indices, and the plain data. ShaderInfo uses the variant's encoder, now split out ofencodeInfo.DecodePlanrebuilds the graph the wayownPlanValuesdoes. It rejects an unknown opcode, an overlong integer, and a value, block, descriptor source or image index past the end, including an image table's heap and material sources and the targets of control-flow edges.RecompileCacheKey::Buildand the host subgroup size), every code word and the switch key, under a plan tag. The binding layout is not in it, so all layouts of a shader share one plan. FormatVersion 30. Size asserts and field counts cover the plan structures (below).getSourcetriesLoadPlanfirst. On a hit the program stays null:findOrCompileVariantalready rebuilds it when no stored variant fits a layout. On a miss it builds the plan as before and stores it. A plan the front end refuses is not stored. The disk is not used when the cache is off, under the debug probe, withAPS5_DUMP_IR, or withAPS5_NO_PERF_DISK_PLANS=1, a cache-neutral switch for comparisons.FrontEndBuilds()countsPrepareResourceProgramcalls, for the tests.Field counts: the plan and ShaderInfo encoders write their structures field by field, and until now the size asserts were the only reminder to update them. A field that fits in existing padding does not change the size.
main's #5097 addedShaderInfo::layerExportedandsizeof(ShaderInfo)stayed 232. The three bools #5089 adds toDescriptorSource::IndirectImagekeep it at 28. A size assert catches neither, so a missed encoder update would go unnoticed and a warm cache would hand back entries without the new field. Next to the size asserts, astatic_assertnow pins the field count of every aggregate the plan codec reaches:IrResourcePlan,GuestRegister,MemoryInfo,DescriptorSource,IndirectImage,ResourceBlock,SrtRead,UniformFill,UniformFillPlan, andShaderInfowith its buffer, image, sampler, sampled-pair and stage input and output records. The count is found by brace-initializing the aggregate with more and more values of a type that converts to anything, until that stops compiling. This is plain C++20 and does not depend on the ABI, so unlike the size asserts it is checked on MinGW too.IrValueis a class with private state, so it keeps only its size assert.This adds no new correctness assumption. A warm run on
mainalready pairs a variant compiled by an earlier process with a plan rebuilt by the current one, and both come from the same front end under the same SourceVersion. Specialized modules are still built in each process. Storing them would be a separate change.Bytes per plan entry: 1.4 to 7.1 KB for the 15 in-tree fixtures in the test (4.3 KB on average). Control-flow conditions can pull most of a program into the plan, so the worst case is large. A synthetic compute shader with a branch every 20 dwords has plans of 518 KB, 2.07 MB and 8.25 MB at 2,038, 8,038 and 32,038 dwords. Its variant entries are 935 KB, 3.70 MB and 14.7 MB. Fixed-width fields would have made those plans 2.5 MB at 2,038 dwords, which is why values are LEB128.
Overlap, all textual: #4806 changes
Words()toRead()next to these hunks in Recompiler.cpp and ShaderDiskCache.cpp. #4732 generates the cache version, and #4475 and other open PRs also bump FormatVersion. #4271 and #4757 edit Recompiler.cpp neargetSource. #1341 adds an execution plan toIrResourcePlan, which would be rebuilt afterDecodePlan. #5089 adds three fields toIndirectImage. Whichever of #5089 and this PR lands second must add them to the plan encoder, and the field count turns a missed update into a build error. No open or closed PR stores the plan.Tested
WSL Ubuntu, i9-14900KF, GCC 15, the CI configuration (Release, SPIRV-Tools on, lavapipe), with the series on
maind1fd937. Also native Windows 11 (WinLibs GCC 15.2.0, Release with tests) on an RTX 5080 (NVIDIA 616.92), same head: fullctestwithANYPS5_REQUIRE_VULKAN=1, 1021 tests, 0 failed, 7 skipped astools/check_skips.py --job windowsexpects;agc_shader_disk_cache, including its two-process runs, passes there. Not tested with a title.agc_shader_disk_cache(1.2 s) gains these tests:agc_shader_memory, except "descriptor invariant phi", which is its "descriptor phi image" with both branches loading the same descriptor. They cover SRT reads, guarded and chained pointers, control-flow conditions, an indirect image table, a loop phi, an invariant descriptor phi and a fragment shader. The hand-built plan gives every plan-level field, everyMemoryInfoand image table field, the uniform fill and a 64-bit immediate a value other than the default.APS5_NO_PERF_DISK_PLANS=1, a process on the same cache runs the front end for all 15 fixtures, loads and stores no plan, still hits the 15 stored variants, and produces the same output.getSourcehunk reverted, the second process reportsfront end builds 14, plan hits 0, plan misses 0, plan load failures 0, variant hits 14, variant misses 0and the test fails. With the change it passes.EncodePlanwrite the default value instead, which is the same as leaving the field out of both the encoder and the decoder. The test fails for every one of them.ResourceMaterializer::Materializecall also encoded, decoded and re-encoded its plan, materialized the decoded copy with the same runtime, and aborted on any difference. All 1002 tests passed. Across 266 test executables besides the disk-cache test, more than 3,700 materializations matched (snapshots, read traces and error messages), with no mismatch.Performance: there is no title or Performance report summary line here, only a synthetic harness. It is a compute shader with 16 V#s loaded from an SRT and a buffer load, an ALU chain and a divergent branch every 20 dwords. It calls
PrepareShaderin one process with an empty cache (cold) and then in a new process with the same cache (warm), withAPS5_SPIRV_OPT=none.main43e3a70 and this series on the same base were built from one tree and run interleaved on the same machine. Since thenmainhas gained one front-end pass and one ShaderInfo field (#4031's ancillary word), which these runs do not include. Load average was 7 to 20 from other jobs. Medians of 7 runs (5 at 32k):PrepareShader395 ms on main, 25 ms with this PR (15.7x).APS5_NO_PERF_DISK_PLANS=1, the branch's warm runs match main: 420 ms, 698 ms and 5.2 s.I can attach the JSON lines of every run, the harness source and the instrumentation patch. A warm-start comparison on a title would show the real gain: start-up time with and without
APS5_NO_PERF_DISK_PLANS=1against the same cache, from the reporters of #2292, #2774 or #4404.main0c3dc9c for this pull request: it builds in the CI configuration and the 32 shader, pipeline-library, structurizer and control-flow tests (ctest -R 'agc_shader|agc_driver_prepared|agc_pipeline_library|agc_structurizer|control_flow') pass on lavapipe.Rebased onto
main60fb618.mainhad added areclaimargument togetSourceandResourceProgram(57b2dc8), which conflicted with the disk-plan path ingetSource. The plan loaded from the disk cache is used as before; when there is none,ResourceProgramis built withmain'sreclaimargument.PrepareShaderstill passesreclaim = false, and on a disk-plan hit its program is rebuilt by the existing fallback infindOrCompileVariantwhen a variant must be compiled. The per-file diff is unchanged. WSL2 Ubuntu 26.04, lavapipe (Mesa 26.0.8), CI configuration: the disk cache, structurizer and control-flow tests 16 of 16, fullctest1067 of 1067.Phi coverage with #5181: the new fixture "descriptor invariant phi" loads one image descriptor from the same SRT offset on both sides of a branch. Its plan keeps eight invariant phis as descriptor dwords, and the CPU walk evaluates them, from the built and from the stored plan, to the image descriptor in the SRT. The only other planned phi is the loop counter of "bindless sample in a loop latch", in a latch condition the walk cannot evaluate. With #5181 16bd650 merged locally (the plan codec moved to its value arena), the phi check passes with or without the new fixture, because #5181 counts
CompositeExtractU32x2as evaluable on its own and keeps that latch condition. With extracts checked through their source instead, it fails withno fixture plans a phiwithout the fixture and passes with it. On the branch,ctest -R 'disk_cache|shader_cache|agc_shader|recompiler|structurizer': 16 of 16.Rebased onto
main0214069.main's #5097 addedlayerExportedto the ShaderInfo encoder, which this PR splits intoencodeShaderInfoanddecodeShaderInfo. The flag is now written last in both, right afterrasterPrimitive, as onmain. A variant entry's payload therefore has the same bytes as onmain, and plans store the flag too. The entry round trip now setslayerExported, as it does the other ShaderInfo flags, so an encoder and decoder that both skip it fail withentry round trip: shader info differs. #5097 also took FormatVersion 29, so this PR moves to 30. Againstmain, the series changes the same lines as before the rebase except the FormatVersion line, plus the field-count asserts. Nothing elsemainchanged after 60fb618 touches a structure the codecs write, apart from the newStageInputKind::LayerExported, which the stage-input encoder writes as its integer value. WSL2 Ubuntu 26.04, lavapipe (Mesa 26.0.8), CI configuration:ctest -R 'disk_cache|shader_cache|agc_shader|recompiler|structurizer'16 of 16, fullctest1077 of 1077 (43 skipped themselves: lavapipe features, and the WSLg display has no 120 Hz mode). The field-count asserts compile in that build (GCC 15.2), with Clang 21, and in a native Windows compile ofShaderDiskCache.cppwith MinGW GCC 15.2.0 (WinLibs r7). With #5089's three bools added toIndirectImage, every size assert still holds and the build stops atstatic assertion failed: IndirectImage changed: update the plan encoder(the comparison reduces to '(10 == 7)') with each of those compilers. A bool added in the padding ofShaderInfo,MemoryInfo,IrResourcePlan,StageInputorSamplerResourcealso keeps its size and stops the build at that structure's field count.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code