MoltenVK runs the tessellation evaluation stage as a post-tessellation
vertex function that reads the control stage's per-vertex outputs from a
raw buffer, and pads the evaluation input struct to every output of the
control shader it is linked with. The shader library cache of a
VkPipelineCache matches a cached conversion when the new pipeline has the
inputs that conversion used, so an evaluation shader converted next to a
hull with one 16-byte output is reused next to the merged hull, whose LDS
record adds a second per-vertex output. The domain stage then reads the
merged hull's control points at a 16-byte stride over a 48-byte buffer and
the patch lands elsewhere. Created without the pipeline cache, each
tessellation pipeline converts its stages for its own interface.
DecodeColorBuffer dropped the DCC address of a color target with more than
one mip, so the target's resident image had no keys: it was uploaded from
guest memory under the game's fast-clear keys, and no draw into it marked
them uncompressed. The game's textures over the same chain name the keys,
so they kept sampling the clear (0000 reads as (0,0,0,0)) whatever was
drawn. MXGP 26, Borderlands 4 and Forspoken render such targets.
- ColorTarget.chainDccAddress keeps the keys of a 2D single-layer chain;
dccAddress stays 0, so the per-draw single-mip key handling is unchanged.
- SurfaceForTarget gives the resident surface those keys, and the image
then treats them as a multi-mip storage image does: a uniform clear is
cleared on the GPU at upload, and the first write marks every key
uncompressed.
- A draw recipe's key check uses the surface's keys.
- sizeof(State) is 840: the recipe codec and CanonicalPipelineState do not
copy the field.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The graphics pipeline store kept a fixed 1024 entries and the pipeline journal replayed at most
1024 records, and a replayed pipeline that found the store full was dropped. A title whose
working set is larger rebuilt the rest at the draw on every run, and evicted pipelines it kept
using were rebuilt again and again.
The bound now starts at 1024 and doubles up to 4096: a journal replay that does not fit grows it
instead of dropping the pipeline, and so does eviction churn (a ghost list of evicted keys, checked
every bound/32 evictions: more than half of them came back). It never shrinks during a run;
APS5_PIPELINE_STORE_ENTRIES still fixes it. The journal loads its last 4096 records by default
(APS5_PIPELINE_JOURNAL_ENTRIES). Resident pipelines are bounded by count, not bytes.
agc_driver_recorder_tests --benchmark-pipeline-store-memory [count] (opt-in, not registered)
builds count graphics pipelines with one fragment module each through CachedPipeline and prints
process private bytes and VK_EXT_memory_budget heap usage before and after: with the triangle
shaders about 59 KiB host and no device memory per pipeline on an RTX 4090, 100 KiB host and
8 KiB device-local on a UHD 770.
Tests: agc_pipeline_store_growth (fails on main), agc_pipeline_store_fixed_bound,
agc_pipeline_store_replay_growth (replays on one thread: the mock Vulkan is not thread-safe).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017SNUYRd7sRKZrMYpquAsnU
An address-based build releases the heap mirror at the base of each range
it imports. A mirror over a joined run of ranges starts at its first range
but spans the others too, so importing that range alone released it, and
the next descriptor bind rebuilt it: in Echoes of the End, 140 releases
and rebuilds of a 544 MiB mirror in 480 s. A heap mirror is now released
only when the imported range covers its reserved span.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A sparse heap the guest grows by mapping an adjacent range is no longer
covered by one leased range, so bindSparseMirror refused it and every
use copied the heap's committed pieces on the CPU. The mirror now takes
a joined range over the run of continuing ranges (crossedRun), kept
while its member ranges stay the same.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
encodeState and decodeState carry colorRop, which changes the recipe codec. Main's journal format is already 9, so the bump to 9 this branch made is a no-op; use 10.
CB_COLOR_FMASK's low 16 bits carry the surface's pipe-bank XOR, as with the other 64 KiB-aligned
metadata bases, so the guarded FMASK range starts at the 64 KiB-aligned surface.
With one fragment, CMASK code 0 marks a tile whose only fragment holds the clear color, as radeonsi's
CMASK fast clear writes it. Such tiles are materialized like a one-sample target's; codes other than 0
and 0xc-0xf still throw.