Skip to content

perf(agc): data-only draw hits compare only the user words a stage reads - #5656

Merged
oneandonlydean merged 4 commits into
boykopovar:mainfrom
Raghav314:perf/agc-data-hit-user-word-mask
Oct 20, 2026
Merged

oneandonlydean merged 4 commits into
boykopovar:mainfrom
Raghav314:perf/agc-data-hit-user-word-mask

Conversation

@Raghav314

@Raghav314 Raghav314 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

What

A data-only draw hit (#494) reuses a cached stage only when every user data word of the stage is unchanged. A title that changes a user word per draw that a stage never reads (a per-draw counter, a pointer only another stage follows) gets no reuse for that stage, though its compiled result and captured memory would be the same.

  • When a stage is captured, it records the user words it depends on, as a 64-bit mask over its user data:
    • the words its SRT walk reads, as values or as bases (SrtUserDataUse::asValue | asBase);
    • the words its binding layout forwards into shader data (userDataRegisterReads), which covers words a stage reads straight from its SGPRs;
    • the two words of each vertex fetch table register in its vertex stage info.
  • A capture whose walk reads no user word gets the full mask, and a word at index 64 or above always counts as read.
  • On a data-only hit, a cached stage is a candidate when the words that changed are all outside its mask. Its captured memory is then validated as before; every other check is unchanged.
  • The mask is stored with the stage's result in the pipeline journal, so the journal file format goes from 4 to 5. Disk cache entries recompute it from the variant when they are loaded, so the disk cache format is unchanged.

PPSA36082

In Hades II most draws change user words between draws (a per-draw counter and ring pointers). In the level UI/menu, the stage preparation in the draw path (Shader.DrawStage) goes from 6.5 to 5.1 ms per frame and the frame time p50 from 18.5 to 17.6 ms (530 to 560 presents in 10 s). The picture is unchanged.

Level UI/menu standing still, APS5_HOST_IMPORT_MIB=6784, no APS5_PROFILE_* switch, APS5_TRACE_EVENTS=1048576 so the traces are whole, shader cache warmed for each build, one run each (measured on main 93e54ce):

  • main:
    {"commit":"93e54cedd3dd","dirty":false,"env":{"APS5_HOST_IMPORT_MIB":"6784","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"70:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","resolution":"1920x1080","window":{"spec":"seconds 70:10","first_present":4314,"ms":10016.434},"presents":530,"frame_ms":{"p50":18.546,"p90":21.041,"p99":23.527,"max":42.509},"gpu_busy_ms_per_present":2.046,"worker_busy_pct":{"queue worker 0x0":94.100},"top_phases_ms_per_frame":{"Driver.Packet":17.778,"Driver.Packet.draw":15.493,"Driver.Draw":15.323,"Driver.Draw.shader_compile_and_link":10.913,"Shader.DrawStage":6.526,"Shader.DrawStage.capture_resources":4.228,"Driver.Draw.draw_enqueued":2.180,"Shader.DrawStage.materialize":1.505},"trace_dropped":false,"trace":"aps5-trace-93e54cedd3dd-723638.json.gz"}
    
  • this PR:
    {"commit":"bd7a16e291bc","dirty":false,"env":{"APS5_HOST_IMPORT_MIB":"6784","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"70:10"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 615.78.08","os":"Linux 7.2.10-1-cachyos","resolution":"1920x1080","window":{"spec":"seconds 70:10","first_present":4301,"ms":10010.943},"presents":560,"frame_ms":{"p50":17.623,"p90":19.970,"p99":24.124,"max":29.993},"gpu_busy_ms_per_present":2.047,"worker_busy_pct":{"queue worker 0x0":93.836},"top_phases_ms_per_frame":{"Driver.Packet":16.769,"Driver.Packet.draw":14.312,"Driver.Draw":14.152,"Driver.Draw.shader_compile_and_link":9.588,"Shader.DrawStage":5.116,"Shader.DrawStage.capture_resources":3.273,"Driver.Draw.draw_enqueued":2.273,"Shader.DrawStage.materialize":1.212},"trace_dropped":false,"trace":"aps5-trace-bd7a16e291bc-723937.json.gz"}
    

The frame time moves with the window position (an earlier pair at 150:10 gave 466 to 574 presents), so the stage time is the steadier number.

Tested

Linux (CachyOS), x86-64, GCC, 7800X3D / RTX 4090, NVIDIA 615.78.08, on main 0d8c51a.

  • agc_driver_draw_data_hit_user_words: the pixel stage loads a buffer descriptor through user words 0 and 1 and never reads word 2. A draw that changes only word 2 is a data-only hit that reuses both stages; a draw that points words 0 and 1 at another descriptor compiles the pixel stage again and reuses the vertex stage. _verify runs the same draws with APS5_VERIFY_DRAW_DATA_HITS=1.
  • A pixel stage that adds user word 2, read straight from its SGPR, to its output: a draw that changes only word 2 writes 191 instead of 128. With userDataRegisterReads left out of the mask the draw reuses the old stage, writes 127, and verify mode aborts.
  • JJenkx's round trip of a full 64-bit mask through the disk cache, and his commit that admits the serial key twice first, as main's second-sight admission needs.
  • The linux job of current build.yml run locally on perf(agc): draw finisher pipeline (APS5_DRAW_PIPELINE) #6946's branch, which is this PR with perf(agc): draw finisher pipeline (APS5_DRAW_PIPELINE) #6946 on top: configure with SPIRV tools on, test registration check, build, libs, the whole ctest and conventions. 1693/1693 pass, 16 expected skips, conventions OK.
  • Hades II with APS5_VERIFY_DRAW_DATA_HITS=1 APS5_VERIFY_DRAW_ENTRIES=1 APS5_VERIFY_REGISTER_KEYS=1 on the earlier perf(agc): draws that differ only in user data reuse their shape's decode and stages #494-based version of this change: 0 stages differed, picture fine.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: perf(agc): draw finisher pipeline (APS5_DRAW_PIPELINE) #6946
  • GPUs tested: NVIDIA GeForce RTX 4090, 615.78.08, Linux 7.2.10 (CachyOS)
  • AI-assisted: yes

@CnRJay CnRJay left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing only the user words a stage depends on is a natural refinement of #494. Running Hades II with APS5_VERIFY_DRAW_DATA_HITS=1 and 0 differing stages is the right correctness check.

On the evidence: both summary lines were captured with APS5_PROFILE_DRAW=1 set (it is in their env). The Performance report form asks for runs with no APS5_PROFILE_* switch, because the draw profiler times every draw and changes the cost being measured, here on the draw path itself. Please capture the two lines again with only APS5_HOST_IMPORT_MIB and APS5_TRACE_SECONDS. If the p50 gain (34.0 → 26.9 ms) holds without the profiler, that settles it. Both lines also show trace_dropped: true, so the attached traces are incomplete; a shorter window keeps them whole.

One question on the mask: are user words a stage uses as plain values, read straight from user SGPRs into ALU work rather than through the SRT walk or forwarded shader data, always covered by userDataRegisterReads? If a stage can consume a user word that neither source records, a change to that word would leave it outside the mask and reuse a stage compiled for the old value. Either a sentence saying which path covers it, or a test with a pixel stage that does arithmetic on a user word (and nothing else), would close that.

@Gashrod1

Copy link
Copy Markdown
Contributor

Measured on DAVE THE DIVER (PPSA19115), as announced in #5436. Short version: data-only hits trigger on almost every keyless draw, but the frame time barely moves, because the user words that change in this title are pointers the stages read.

Setup. Windows 11, RTX 5060 (596.36), Ryzen 7 5800X; build with -fno-omit-frame-pointer -g1, Release otherwise. Same underwater scene each time, APS5_PROFILE_DRAW=1 APS5_TRACE_SUMMARY_EVERY=10. Base: main 956269a plus the MSAA stack this title needs (#4820, #5496, #5445, #5892, #5558, local changes listed in #5963). The test build merges 8267abd (this PR with #494) into that base.

Median over the 10 s intervals after the first 50 s:

Build frame p50 Driver.Draw Graphics.Draw
base 43.6 ms 39.2 ms 27.6 ms
base, APS5_DRAW_CACHE_ENTRIES=65536 41.8 ms 37.1 ms 27.0 ms
base + #494 + #5656 42.1 ms 37.3 ms 26.8 ms

The gain is within run-to-run noise (the #5656 run had 9 intervals in the scene, the base 28). From the [draw-cache] register key lines (10 s windows):

So the shape lookup works and the decode is skipped nearly everywhere, but the stages are still captured and compiled. A churn trace on the base (exact key vs shape, logged per user word) showed 288 shapes against ~456k exact keys in a few minutes, every new key differing only in user words, about 95% of the changed words increasing: ring-buffer pointers that the SRT walk reads, so userDataMask rightly keeps them. A stage that could be reused here would need its captured addresses relocated rather than compared as-is.

Port notes, in case they are useful for a rebase:

  • main now reads the vertex user-data tail continuation from the user words (DrawProgram::tailContinuation). I moved that read into ReadUserWords so a data-only refresh recomputes it, and skip the candidates of a stage whose continuation changed.
  • main's 0adbdcf caches a draw key only at its second sight, so the first draw of a shape never gets an entry and agc_driver_draw_data_hit_user_words fails. I admitted the first key of a shape that has no entry yet (at most one key per shape skips the second sight), and ran agc_driver_draw_cache_reuse with APS5_NO_DRAW_DATA_HITS=1, since it checks the second-sight rule. With that, the five agc_driver_draw* tests pass on Linux. The lower full-hit rate above may come from this admission rule or from the scene; I have not separated the two.
  • ShaderDiskCache sizes on current main: ShaderInvocation 120, RecompileResult 360, format version bumped past 32.

Most of the frame in this title is on the resource side (Graphics.Draw, ~27 ms), which neither variant touches; I will look there next.

AI-assisted: yes

@fma-rc

fma-rc commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Stacked this on #6188 (0eb0d89) over d92a8da in Astro's Playroom on Linux (RTX 5080, heavy in-game scene, one 20 s run each, APS5_PROFILE_DRAW=1). The frames match.

FPS register-key hits per 10 s capture / recompile / decode, us per draw
d92a8da 7.57 9k of 227k 7.8 / 4.3 / 1.4
6188 8.37 83k of 242k 5.4 / 1.3 / 3.0
6188 + 5656 12.77 372k of 373k 0.1 / 0.0 / 0.4

So with 6188 compiling ahead, 5656's data-only hits cover almost every draw in this scene. In the merge I applied the user-word mask in 6188's compileDrawAhead too.

@Raghav314

Copy link
Copy Markdown
Contributor Author

Thanks. Rebased on c4c2b3a, and the format and size points are fixed:

DispatchVariant::userDataMask isn't dead: a draw keeps its stages as DispatchVariants (DrawCache.hpp), and the mask is set when a stage is captured (DrawCapture.cpp) and read on a data-only hit (DrawLookup.cpp). Dispatches themselves don't use it in this PR.

LLM assistance used.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 18, 2026
@Raghav314
Raghav314 force-pushed the perf/agc-data-hit-user-word-mask branch from 9809434 to fe87a2c Compare October 18, 2026 19:48
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

@Raghav314 This one is reviewed and CI was green on fe87a2c, but it conflicts with current main again, and part of the conflict is one git won't show you.

#4650 has merged (b876d43). It moved ShaderInvocation / RecompileResult to 208 / 480 with its own new field, the same sizes this PR asserts. So git auto-merges those static_asserts without complaint, but the combined structs now carry both fields. When you merge or rebase onto main, please:

  • recompute the ShaderInvocation and RecompileResult size asserts for the combined layout, and keep both encoder lines in ShaderDiskCache.cpp (main's and yours), in the journal's field order;
  • resolve the visible conflict on the DescriptorBinding size assert (main 656, PR 632) the same way, from the real combined struct;
  • bump the disk-cache format version if adding both fields changes the on-disk layout compared with what main writes now.

Once it's pushed we'll check the resolution, run full CI again (agc_driver_draw_data_hit_user_words, _verify and agc_shader_disk_cache) and then the draw-cache gate. Thanks!

Raghav314 and others added 4 commits October 20, 2026 03:57
A data-only hit (boykopovar#494) reused a cached stage only when every user data word
of the stage was unchanged. A stage records the user words it depends on when
it is captured: the words its SRT walk reads as values or bases
(SrtUserDataUse), the words its binding layout forwards into shader data
(userDataRegisterReads), and the vertex fetch table words of its vertex stage
info. A cached stage is now a candidate when only words outside that set
changed; its captured memory is validated as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… SGPR follows a change of that word

The word reaches the data-only mask through userDataRegisterReads (the stage
layout's user-data registers). Dropping those bits from the mask makes the
draw reuse the old stage (it writes 127, expected 191) and verify mode abort.

LLM assistance used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Raghav314
Raghav314 force-pushed the perf/agc-data-hit-user-word-mask branch from fe87a2c to 5892c84 Compare October 19, 2026 22:36
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
poonasor added a commit to poonasor/AnyPS5 that referenced this pull request Oct 20, 2026
…povar#6946 onto 4b462ef (programs table, AppendContentKey order, test name clash with the Windows near macro)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JJenkx JJenkx mentioned this pull request Oct 20, 2026
12 tasks done
@JJenkx

JJenkx commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Retested your current head 5892c846c75bb17c10e1c5ebe96bea0d2db30c5a in the Hades II (PPSA36082) integration refreshed to main d0852defa2a5fbc7f64ab20abddb7e1c24733345. The candidate reaches the hub with the character visible in the 170-second capture and closes normally. The baseline also reaches the hub; both restore runtime configuration and leave no game process.

This is integration evidence, not an isolated main-versus-PR acceptance test: the preserved baseline 50d7ce6043ef3ae9d409e07e3589bdad207d2c53 includes the existing #6300 HTILE and #6539 SRT work plus local correctness fixes. Candidate 00e19490602563f74c50b6e189e8b1de4039ac1c adds your four PR commits. Resolving the overlap preserves both sets of stage/variant metadata and adjusts the combined RecompileResult size assertion to 552.

On AMD Radeon RX 6650 XT / RADV Mesa 26.2.4-1, Arch Linux:

  • The four agc_driver_draw_cache_reuse variants pass on both baseline and candidate.
  • Both agc_driver_draw_data_hit_user_words variants additionally pass on the candidate.
  • Focused runs use ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, with zero skips, missing-layer messages or VUID- errors. The new tests were explicitly enabled in the selective execution-test build before the final run.

Evidence archive includes the complete focused logs, source delta/history, native reports/logs and reviewed hub captures. Native runs themselves did not enable core validation. No FPS claim: these runs differ in capture timing/duration and do not contain matched official performance traces.

@JJenkx

JJenkx commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Follow-up with matched Hades II (PPSA36082, 01.006.000) hub traces on the same RX 6650 XT/RADV machine. The four runs use baseline → candidate → candidate → baseline order, warm caches with title restart, the exact same eight-file save manifest, identical scheduled inputs, window and settings. Reviewed 155/175-second captures show the same character/camera position in the idle hub; animation phases differ.

Build/run Frame p50 ms Frame p99 ms Shader compile/link ms per present
baseline 1 24.104 35.452 13.106
candidate 1 21.286 29.974 9.830
candidate 2 21.771 30.162 10.011
baseline 2 23.322 38.693 12.618

Both candidate medians are lower than both baseline medians. The average of the two run medians is about 9% lower, and shader compile/link time per present about 23% lower. This is limited evidence from two five-second idle-hub captures per build, not a gameplay-wide speed claim.

As in the earlier comment, baseline 50d7ce6043ef3ae9d409e07e3589bdad207d2c53 is the Hades integration on main d0852defa2a5fbc7f64ab20abddb7e1c24733345 with #6300/#6539 and local correctness fixes. Candidate 00e19490602563f74c50b6e189e8b1de4039ac1c adds this PR's four commits through head 5892c846c75bb17c10e1c5ebe96bea0d2db30c5a. This is not stock-main versus the standalone PR and does not replace that acceptance comparison.

Capture: APS5_TRACE_SECONDS=160:5, identical APS5_TRACE_EVENTS=1048576 capacity override to avoid truncated traces; no draw profiling or other diagnostic trace modes. Native stdout remained attached to a PTY recorded with util-linux script, rather than redirected to a game log file. Thus the event-capacity override is explicit rather than claiming literally only one APS5_TRACE_* variable. All four traces report no dropped events, and all four runs close normally and restore the kernel setting. Native core validation was disabled; the previously attached matched focused GPU validation results still apply to these unchanged source pins.

Trace/evidence archive contains all four traces and trace_digest.py outputs, summary lines, native reports, terminal recordings, reviewed hub captures, exact candidate delta and reproduction harness. No save files or game assets are included.

Official summary lines, in run order:

{"commit":"50d7ce6043ef","dirty":false,"env":{"APS5_DRAW_CACHE_ENTRIES":"4096","APS5_HOST_IMPORT_MIB":"16384","APS5_PAD_INPUT_FILE":"<path>","APS5_PIPELINED_DRAWS":"1","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"160:5"},"gpu":"AMD Radeon RX 6650 XT (RADV NAVI23)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","cpu":"AMD Ryzen 9 7950X 16-Core Processor","cpu_threads":32,"memory_mib":127901,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 160:5","first_present":9061,"ms":5018.886},"presents":193,"frame_ms":{"p50":24.104,"p90":30.687,"p99":35.452,"max":37.382},"gpu_busy_ms_per_present":9.881,"worker_busy_pct":{"queue worker 0x0":98.212},"top_phases_ms_per_frame":{"Driver.Packet":25.511,"Driver.Packet.draw":22.967,"Driver.Draw":22.819,"Driver.Draw.shader_compile_and_link":13.106,"Graphics.Draw":12.756,"Graphics.Draw.draw_and_resource_release":11.108,"Shader.DrawStage":9.543,"Driver.Draw.draw_enqueued":7.12},"trace_dropped":false,"trace":"aps5-trace-50d7ce6043ef-2273323.json.gz"}
{"commit":"00e194906025","dirty":false,"env":{"APS5_DRAW_CACHE_ENTRIES":"4096","APS5_HOST_IMPORT_MIB":"16384","APS5_PAD_INPUT_FILE":"<path>","APS5_PIPELINED_DRAWS":"1","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"160:5"},"gpu":"AMD Radeon RX 6650 XT (RADV NAVI23)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","cpu":"AMD Ryzen 9 7950X 16-Core Processor","cpu_threads":32,"memory_mib":127901,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 160:5","first_present":9129,"ms":5016.902},"presents":228,"frame_ms":{"p50":21.286,"p90":26.482,"p99":29.974,"max":31.317},"gpu_busy_ms_per_present":9.533,"worker_busy_pct":{"queue worker 0x0":98.14},"top_phases_ms_per_frame":{"Driver.Packet":21.57,"Driver.Packet.draw":19.305,"Driver.Draw":19.175,"Graphics.Draw":11.556,"Graphics.Draw.draw_and_resource_release":10.172,"Driver.Draw.shader_compile_and_link":9.83,"Driver.Draw.draw_enqueued":7.179,"Shader.DrawStage":6.804},"trace_dropped":false,"trace":"aps5-trace-00e194906025-2357397.json.gz"}
{"commit":"00e194906025","dirty":false,"env":{"APS5_DRAW_CACHE_ENTRIES":"4096","APS5_HOST_IMPORT_MIB":"16384","APS5_PAD_INPUT_FILE":"<path>","APS5_PIPELINED_DRAWS":"1","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"160:5"},"gpu":"AMD Radeon RX 6650 XT (RADV NAVI23)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","cpu":"AMD Ryzen 9 7950X 16-Core Processor","cpu_threads":32,"memory_mib":127901,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 160:5","first_present":9260,"ms":5006.487},"presents":224,"frame_ms":{"p50":21.771,"p90":26.388,"p99":30.162,"max":30.76},"gpu_busy_ms_per_present":9.723,"worker_busy_pct":{"queue worker 0x0":98.259},"top_phases_ms_per_frame":{"Driver.Packet":21.938,"Driver.Packet.draw":19.753,"Driver.Draw":19.627,"Graphics.Draw":11.441,"Graphics.Draw.draw_and_resource_release":10.045,"Driver.Draw.shader_compile_and_link":10.011,"Driver.Draw.draw_enqueued":7.387,"Shader.DrawStage":6.799},"trace_dropped":false,"trace":"aps5-trace-00e194906025-2432685.json.gz"}
{"commit":"50d7ce6043ef","dirty":false,"env":{"APS5_DRAW_CACHE_ENTRIES":"4096","APS5_HOST_IMPORT_MIB":"16384","APS5_PAD_INPUT_FILE":"<path>","APS5_PIPELINED_DRAWS":"1","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"160:5"},"gpu":"AMD Radeon RX 6650 XT (RADV NAVI23)","driver":"radv Mesa 26.2.4-arch1.1","os":"Linux 7.2.9-arch1-1","cpu":"AMD Ryzen 9 7950X 16-Core Processor","cpu_threads":32,"memory_mib":127901,"title_id":"PPSA36082","title":"Hades II","content_version":"01.006.000","resolution":"1920x1080","window":{"spec":"seconds 160:5","first_present":8890,"ms":5011.761},"presents":198,"frame_ms":{"p50":23.322,"p90":30.306,"p99":38.693,"max":59.096},"gpu_busy_ms_per_present":9.689,"worker_busy_pct":{"queue worker 0x0":98.152},"top_phases_ms_per_frame":{"Driver.Packet":24.815,"Driver.Packet.draw":22.333,"Driver.Draw":22.192,"Driver.Draw.shader_compile_and_link":12.618,"Graphics.Draw":12.463,"Graphics.Draw.draw_and_resource_release":10.911,"Shader.DrawStage":9.24,"Driver.Draw.draw_enqueued":6.984},"trace_dropped":false,"trace":"aps5-trace-50d7ce6043ef-2512164.json.gz"}

poonasor added a commit to poonasor/AnyPS5 that referenced this pull request Oct 20, 2026
…povar#6946 onto 4b462ef (programs table, AppendContentKey order, test name clash with the Windows near macro)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@oneandonlydean
oneandonlydean merged commit 2185dbf into boykopovar:main Oct 20, 2026
12 checks passed
milescoviello added a commit to milescoviello/AnyPS5 that referenced this pull request Oct 20, 2026
Module records are SPIR-V and take almost all of a journal's bytes, so
APS5_PIPELINE_JOURNAL_MAX_MIB fills with modules and later pipelines
are not recorded (boykopovar#8309). A module record now keeps its identity, stage
and encoded size, followed by a zstd frame (level 1) of the encoded
result; loading and replay expand it before decoding. The journal format
becomes 6 (main took 5 for the recipe codec change of boykopovar#5656).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@EdouardSence

EdouardSence commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

A small positive result on ASTRO BOT now that #7844 removes the import-budget regression that hid it in my earlier A/B.

Builds: the merge parent 2185dbf92^1 and the merge 2185dbf92, both + #7844 (pre / post). Linux, RX 9070 XT / RADV Mesa 26.2.4, PPSA21567 01.019.000, Sky Garden (-lvl underwater_aerial_garden), headless, level start, APS5_TRACE_SECONDS=90:50, warm caches, order pre → post → post → pre.

Run Frame p50 ms p90 ms p99 ms
pre 1 258.9 275.8 319.9
post 1 253.9 273.3 280.4
post 2 253.2 272.8 280.0
pre 2 257.8 278.9 401.4

Both post medians are below both pre medians: about 2 % lower frame time, with a tighter p99. GPU busy time per present is unchanged (~136 ms), so the gain is on the CPU side.

Edit: where the remaining data-only misses come from on this level (corrected). A local counter in lookupDraw's data-candidate loop (post build, Sky Garden, not pushed) shows that about 87 % of the stage variants it checks are rejected because a user data word the stage uses changed. The changing words come in steps of four (w0, w4 … w28), which first made me think of inline buffer descriptors. A prototype that reuses the stage when only inline V# words (or user words the shader only reads at run time through push constants) change, and patches the bindings and push constants, passes APS5_VERIFY_DRAW_DATA_HITS but gives no measurable gain (255–258 ms against 256–259 ms). Classifying what still blocks the candidates explains why:

Rejected candidates, by the first blocking word Share
a word the SRT walk reads memory through (asBase, a per-draw table pointer) ~70 %
a vertex fetch table register ~25 %
a value word that is not a plain V# copy ~4 %

So on this title the per-draw cost comes from per-object descriptor tables in memory, at a new address and with new contents for each draw. The existing relocation (relocateDrawEntry) only covers moved tables with the same contents. Reusing a stage here would need the captured table to be re-read and its descriptors patched without recompiling, like the flattened-SRT data positions, but for tables reached through a user pointer. I haven't tried that.

Tested and written by Claude (AI assistant) on a contributor's machine.
AI-assisted: yes

milescoviello added a commit to milescoviello/AnyPS5 that referenced this pull request Oct 20, 2026
Pipeline creation and SPIR-V validation read State::vertexPoints and the
three point sizes, so the pipeline journal's recipe codec and canonical
state now carry them, the State layout guard names its new size and the
journal format becomes 6 (main took 5 for the recipe codec change of boykopovar#5656).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
milescoviello added a commit to milescoviello/AnyPS5 that referenced this pull request Oct 20, 2026
The pipeline journal guards State and ShaderStages, which this branch
extends. The recipe codec and canonical state now carry the hull wave
size, the geometry stage after tessellation, the merged hull flag
(TessellationConfiguration keeps its size, so its guard can't see it)
and rasterizer discard, and the journal format becomes 6 (main took 5
for the recipe codec change of boykopovar#5656). A pipeline drawn with a
tessellation capture validates against that draw's capture buffers,
which a replay doesn't have, so it isn't journaled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
milescoviello added a commit to milescoviello/AnyPS5 that referenced this pull request Oct 21, 2026
Pipeline creation and SPIR-V validation read State::vertexPoints and the
three point sizes, so the pipeline journal's recipe codec and canonical
state now carry them, the State layout guard names its new size and the
journal format becomes 6 (main took 5 for the recipe codec change of boykopovar#5656).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build perf Performance change shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants