Skip to content

perf(graphics): retain vertex input layouts - #1316

Merged
adriadam10 merged 4 commits into
boykopovar:mainfrom
TibixDev:pr/vertex-input-layouts
Oct 14, 2026
Merged

adriadam10 merged 4 commits into
boykopovar:mainfrom
TibixDev:pr/vertex-input-layouts

Conversation

@TibixDev

@TibixDev TibixDev commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

What

Retain validated vertex layouts for stable shader variants, while bypassing inputs that do not benefit from reuse. Rebased onto main d146ec1b6, including the current vertex-fetch, null-descriptor and record-count behavior.

  • Empty vertex inputs keep the default empty layout; they perform no cache lookup, layout build or shared allocation.
  • A variant evicted from the 1,024-entry cache, or observed changing layout shape under the same ID, is bypassed before the cache mutex. The draw uses the original value-returning layout builder, with no cache allocation or LRU update. The bypass check is a small inline front end to the locked lookup.
  • The bounded 8 KiB bypass bitmap is conservative: hash collisions can suppress caching for another variant, which still receives normal validation. Bits last for the device lifetime. This favors avoiding repeated misses over re-admitting an evicted variant if it becomes hot later.
  • The shape key includes formatComponents and null-descriptor state. Live addresses are validated on each cache hit. Record counts, zero-fill decisions and copied guest bytes remain per-draw work. Draw recipes keep their existing value-owned layouts.

Depends on: none.

Tested

Fedora 44, GCC 16.2.1, Release: agc_vertex_input, agc_driver, agc_driver_pm4 and agc_driver_graphics pass. The cache test also passes with AddressSanitizer and leak detection (ANYPS5_ENABLE_ASAN=ON, ASAN_OPTIONS=detect_leaks=1:halt_on_error=1). Contribution checks and git diff --check pass.

Coverage includes 10,000 generated layouts compared with the original builder, live address rejection, null descriptors, format-component changes, record-count and zero-stride/OOB decisions, retained-layout lifetime after eviction, conservative bypass collisions, invalid bypassed inputs, concurrent reuse and concurrent eviction.

cmake --build build-fedora --target agc_vertex_input_tests agc_driver_tests agc_driver_graphics_tests agc_driver_pm4_tests --parallel 8
ctest --test-dir build-fedora -R '^(agc_vertex_input|agc_driver|agc_driver_graphics|agc_driver_pm4)$' --output-on-failure --timeout 30
ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 ./build-asan-review/tests/agc_vertex_input_tests

Isolated CPU measurements

Ryzen 5 5600, pinned to CPU 3, identical Release executable and format-property callback. The baseline calls BuildVertexInputLayout, whose implementation is unchanged from main d146ec1b6; the cache mode uses the draw's revised layout-selection path. Three ABBA cycles, six processes per mode, one warm-up plus nine measured batches of 32,768 requests each. The table reports medians of process CPU nanoseconds per request; wall time is also printed by the harness. CPU time avoids charging unrelated scheduling delays to either implementation.

Attributes Workload Main, ns/call Revised cache, ns/call
0 Empty layout 4.76 0.49
1 1 reused variant 51.77 16.48
1 64 reused variants 52.26 18.57
1 2,048 cyclic variants 51.61 35.43
1 Already-evicted variants only 52.29 51.86
1 One variant alternating shape 55.08 52.98
4 1 reused variant 199.11 32.12
4 64 reused variants 199.45 34.10
4 2,048 cyclic variants 199.08 119.89
4 Already-evicted variants only 201.74 200.99
4 One variant alternating shape 197.49 201.36
8 1 reused variant 362.01 51.85
8 64 reused variants 361.76 55.54
8 2,048 cyclic variants 360.72 213.30
8 Already-evicted variants only 360.06 365.80
8 One variant alternating shape 363.21 364.23

All output checksums match. Measured reuse cases have 100% cache hits; the cyclic 2,048-variant cases settle at 50%; already-evicted and changing-shape cases take the bypass path with 0%. Empty layouts skip layout construction entirely. Fully bypassed cases are close to baseline: their measured median changes range from approximately -4% to +2%, with overlapping run ranges. The earlier empty-layout and eviction-every-reuse costs are removed.

These are synthetic layout-component measurements, not a saved title trace or frame-time claim. First-time admission still costs a lookup and cache insertion, and this does not establish a title-specific hit rate or a gain under contended multi-threaded draws. Windows runtime and full Linux/Windows CI still need validation.

taskset -c 3 ./build-fedora/tests/agc_vertex_input_tests --benchmark-vertex-input baseline
taskset -c 3 ./build-fedora/tests/agc_vertex_input_tests --benchmark-vertex-input cache

Checklist

  • Based on main at the revision above; related work checked.
  • One topic.
  • No new code comments.
  • Unsupported paths retain their errors.
  • No notes, logs, or images added to the repository.
  • No new third-party code.
  • General behavior.
  • Depends on: none.
  • AI-assisted: yes.

@TibixDev

TibixDev commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Rebased again on c8a2f063 to preserve the new BDA test registration. The measured vertex code is unchanged. I compared the production headers at 5850a6cd with that upstream plus this PR alone, using the same CPU harness and a fixed format-property query. No other PR was included.

Fedora 44, GCC 16 -O3 -DNDEBUG, Ryzen 5 5600 pinned to CPU 3. Six runs per variant in ABBA order; one warmup and nine measured batches per run. Values are medians of the six run medians.

The saved 20,000-request layout trace fell from 165.5 to 19.8 ns/call, or 88.0% less time. Each timed batch repeated it eight times. Run medians ranged from 163.3–181.7 ns upstream and 19.7–20.0 ns with this PR.

Synthetic cases, ns/call (upstream → PR):

Attributes 1 variant 64 variants 2,048 variants
0 1.6 → 7.8 1.6 → 8.0 1.6 → 8.2
1 49.7 → 13.3 49.8 → 15.1 50.2 → 118.2
4 201.7 → 19.3 200.0 → 22.9 201.7 → 331.9
8 367.5 → 29.9 368.6 → 35.4 366.4 → 472.2

All output checksums match. The cache capacity is 1,024, so the 2,048-variant cases measure eviction on each reuse. Empty layouts and over-capacity workloads regress. These CPU measurements do not establish a frame-time gain.

The vertex-input, driver, graphics and PM4 tests pass. Windows was not tested.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 8, 2026
@TibixDev
TibixDev force-pushed the pr/vertex-input-layouts branch from 12433de to 36b1817 Compare October 8, 2026 05:37
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 8, 2026
@TibixDev
TibixDev force-pushed the pr/vertex-input-layouts branch from 36b1817 to 1f8e6b2 Compare October 8, 2026 06:36
@TibixDev

TibixDev commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Fixed the clean-build failure: the vertex-input test now includes the repository Vulkan headers explicitly. Rebased on 7a178e22. The production cache code is unchanged.

Linux Release test and convention checks pass. MinGW compiles the test source. The local Windows link is blocked by unresolved SjLj symbols in libc, so Windows runtime validation is left to CI.

I checked #1426: it handles short vertex buffers and does not replace this cache.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 8, 2026

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks. The code reads fine and agc_vertex_input passes here (Linux, full ctest 420/420), but two things before this can land:

  • Your own numbers show regressions: 8 attributes with 2,048 variants goes 366.4 → 472.2 ns/call and empty layouts 1.6 → 8 ns/call, while the win is on a saved 20,000-request trace. Please show it helps on real draw streams (hit rate of the cache and frame/CPU time on a title, with the title ID), or limit it to the cases that win (e.g. skip the cache when it misses repeatedly or for empty layouts) and drop what measured slower.
  • It conflicts with main in core/libs/prx/libSceAgcDriver/CMakeLists.txt (test list). Please rebase and rerun agc_vertex_input and agc_driver_graphics afterwards.

Not verified on our side: Windows runtime and real-title performance.

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the work. Following up on the earlier request, which has had no reply or push. Two things still block this:

  • No evidence on real draws. The win (165.5 → 19.8 ns/call) is a replay of a saved layout trace, while your own table shows two regressions (8 attributes × 2,048 variants 366.4 → 472.2 ns/call; empty layouts 1.6 → 8 ns/call). The most this can save is ~145 ns per draw, against a mutex, an LRU list and a shared_ptr on every draw. Please post the cache hit rate and CPU/frame time on a title (title ID, host), or make the cache pay only where it wins: leave empty layouts on the old path and don't take the lock for a variant that keeps missing.
  • It now conflicts with main in Graphics/include/VertexInput.hpp, Graphics/include/Context.hpp and libSceAgcDriver/CMakeLists.txt (the vertex-fetch changes from #1769 and the null-V# / record-count fixes touched the same functions). When you rebase, check that the cached layout keeps honouring those: the zero-fill and null-V# cases must still be validated per draw, not frozen into the cached shape. Rerun agc_vertex_input and agc_driver_graphics afterwards.

Not verified on our side: Windows runtime and real-title performance.

@TibixDev
TibixDev force-pushed the pr/vertex-input-layouts branch from 1f8e6b2 to d7718f8 Compare October 12, 2026 04:20
@TibixDev

Copy link
Copy Markdown
Contributor Author

@adriadam10 addressed the rebase and cache-regression requests in d7718f8d4, based on main d146ec1b6.

Empty inputs now skip layout construction and the cache entirely. Evicted variants, and variants that change shape under the same ID, take an inline bypass before the mutex and use the original value-owned builder. The bounded bypass bitmap can conservatively suppress caching after a hash collision; that tradeoff and first-admission costs are documented in the body.

The rebase preserves per-draw address, record-count and zero-fill behavior, and adds formatComponents and null-descriptor state to shape matching. Regression tests cover those cases, retained lifetime, bypassed invalid inputs and concurrent eviction.

Fresh isolated CPU measurements (same executable, main's unchanged builder versus revised selection; six processes/mode, ABBA, CPU 3): eight-attribute reuse 362.0 → 51.8 ns/call, and 2,048 cyclic variants 360.7 → 213.3 ns/call. Empty selection skips construction; fully bypassed cases are near baseline (roughly -4% to +2%, overlapping ranges). The reproducible harness, full table and synthetic hit rates are in the body. These are component timings; title hit rates/frame-time gains remain unmeasured.

agc_vertex_input, agc_driver, agc_driver_graphics and agc_driver_pm4 pass; the vertex-input suite also passes ASan with leak detection. Contribution checks pass. Ready for another review; full Linux/Windows CI still needs a maintainer run.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Conflicts with main in Graphics/include/Context.hpp (c9e48e5 added sampleLocationSampleCounts on the line after singlePassStorage); please rebase.

One question while you are there: Graphics/src/Draw.cpp:1117 now runs ValidateVertexAddresses on the recipe path, which main doesn't validate. That is a format decode per attribute on every recipe hit. What does it guard? If nothing, please drop it.

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the rework; the bypass for empty and evicted layouts addresses the earlier regressions. Still open:

  • Conflict: Graphics/include/Context.hpp conflicts with main (sampleLocationSampleCounts was added after singlePassStorage). Please rebase and rerun the PR's tests afterwards.
  • Recipe path (Graphics/src/Draw.cpp:1117): ValidateVertexAddresses runs on every recipe hit, while main only copies recipe->vertexInput there. That is a behaviour change plus a format decode per attribute per draw, and it is the question @oneandonlydean asked. Please drop it, or say which case it guards and add a test that fails without it.
  • Evidence on real draws: the numbers are still synthetic, with no hit rate or CPU/frame time from a title. The most this saves is about 150 ns per draw (4 attributes), against a mutex and LRU update on every cached draw. Please post the hit rate and CPU time on a title (ID, host), or explain why the gain is worth the added state.
  • The bypass bitmap is never cleared and a hash collision silently disables caching for another variant; a short note on why that is acceptable for long sessions would help.

RINNECODER added a commit to RINNECODER/AnyPS5 that referenced this pull request Oct 14, 2026
…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>
@adriadam10 adriadam10 self-assigned this Oct 14, 2026
TibixDev and others added 4 commits October 14, 2026 16:05
Cache validated layouts by shader variant and exact static shape, independently of buffer transport. Preserve per-draw address checks and share layout ownership with draw recipes.

Written with AI assistance.
Preserve current null-descriptor and format-component semantics, keep live validation per draw, and leave empty or bypassed layouts on the value-owned path. Add differential, concurrency, and isolated timing coverage.

Written with AI assistance.
A recipe hit copies its stored layout as before; the address validation
stays on the cached and uncached non-recipe paths only.
@adriadam10
adriadam10 force-pushed the pr/vertex-input-layouts branch from d7718f8 to 7a12992 Compare October 14, 2026 14:19

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Picked this up from here since the branch had no push after the last review:

  • Rebased onto current main (conflicts in CMakeLists.txt, Context.hpp, Draw.cpp).
  • Recipe path: dropped ValidateVertexAddresses, a recipe hit copies recipe->vertexInput exactly as main does (@oneandonlydean's question).
  • Hit rate / title numbers: not available to us either; the synthetic table stands as the measured benefit, and the bypass removes the regressions seen before. The note about the never-cleared bypass bitmap is already in the PR body.

agc_vertex_input and the scoped ctest (714 tests) pass on the rebased head; agc_driver_tessellation segfaults inside RADV's pipeline-library link, as on main. Merging once the full build is green.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

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.

Pull request Conflicting files (lines on main) Same exports Same new files
#4932 core/libs/prx/libSceAgcDriver/CMakeLists.txt L1420

@adriadam10
adriadam10 merged commit 84b01f9 into boykopovar:main Oct 14, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants