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
The Windows registration check fails when a test executable is built but no ctest test runs it. Put the executable under ANYPS5_RUN_QUARANTINED_FRAME_REPLAY as well, so OFF neither builds nor registers it and ON does both.
The recorder reads a delta block with ReadCommittedUnsynced while the second DMA copy into it is still in flight, so the capture can hold zeros and the replay then does not reproduce the second copy. The test failed intermittently on the Windows lane. Do not register it on Windows unless ANYPS5_RUN_QUARANTINED_FRAME_REPLAY is ON, and list it in the libSceAgcDriver technical debt.
The journal was read whole into one buffer that the decoded modules
viewed, so a replay held the entire file (1 GiB at the default
APS5_PIPELINE_JOURNAL_MAX_MIB) until its last entry was built. The load
now parses the file a record at a time through positioned reads and keeps
it open; a replay thread reads an entry's modules again when it builds the
entry and rejects it when a record no longer reads back as loaded. The
replay report gains the peak module bytes held at once.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On a discrete GPU a write-only V# element whose stride is wider than the
widest buffer store (16 bytes) and that a direct dispatch sweeps is staged
in a device-local shadow of up to 256 MiB, as read and written swept
elements are. Its stores no longer cross the bus into the host import.
AI-assisted. Tested by me on my own machine.
A draw with DB_RENDER_CONTROL.DEPTH_COPY ran as a compute pass over the
whole surface, chosen when the viewport and scissor covered it. It now
runs as the title's own draw: the precheck rewrites the draw's registers
to bind no depth target and a driver-authored guest pixel program, then
draws it through the normal path. The program reads POS_FIXED_PT, loads
one sample of the Z_32_FLOAT plane through a T# built from the DB
registers (DepthCopySourceWords), and exports it to color target 0 as
32_R, so only the pixels the draw covers are written, and the normal draw
path orders it after the draws that rendered the plane.
- The plane is decoded by decodeDepthPlane, factored out of decodeDepth,
so its address, pipe-bank XOR and slice match the registered surface.
- Two programs, one per source kind: image_load 2D_MSAA with the sample in
s8, and image_load 2D. They are published with the null pixel program
and prepared at use.
- A multisampled plane is read from its host image or sample shadow
without the guest-memory import checks of native multisample sampling,
inside a DepthCopySourceScope opened by Graphics::Draw for draws whose
decode found the multisampled copy program (State::depthCopySource).
- The compute pass, its shader and the CopySample path are removed.
- A new execution test draws depth over a triangle, copies it with the
same triangle and compares the covered texels with a reference draw.
Co-Authored-By: Claude Opus 5.5 <[email protected]>