Skip to content

perf(libSceAgcDriver): order in-place vertex inputs behind pending GPU writes with a barrier - #4649

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
Mouad-Scriptz-0:perf/agc-inplace-vertex-barrier
Oct 16, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
Mouad-Scriptz-0:perf/agc-inplace-vertex-barrier

Conversation

@Mouad-Scriptz-0

@Mouad-Scriptz-0 Mouad-Scriptz-0 commented Oct 12, 2026 •

Copy link
Copy Markdown

What

A draw's vertex buffers bound in place from a host import no longer wait on the CPU for pending GPU writes over them. InPlaceDrawInput flushed GPU writes before every in-place input; for a vertex buffer the CPU never reads those bytes, so when a write recorded in the open or in-flight batches overlaps the range, the draw now records a VK_PIPELINE_STAGE_ALL_COMMANDS_BIT -> VK_PIPELINE_STAGE_VERTEX_INPUT_BIT barrier (shader and transfer writes to vertex attribute reads) before its rendering begins, and binds the import. Index buffers keep the flush: InPlaceIndexRange scans them on the CPU for the highest index. Labels, queued stores and copied writers over the range still take the copy path.

Minecraft (PPSA17221) generates a 48-byte vertex buffer with a compute dispatch every frame and draws from it next; the flush turned that into one submit-and-wait of the open batch per frame ([hooksync] under APS5_PROFILE_DRAW=1: about 430 pending-write syncs per 10 s, 2.2 s waited, every one reading unchanged bytes afterwards). With the barrier the home screen goes from 42 to 60 fps on Intel Arc Meteor Lake and the report drops to 30 to 50 syncs per 10 s, none on an open batch.

Intended behaviour: a vertex range under a pending GPU write is written back by the GPU (pending storage images are flushed first, as the old CPU flush did) and the draw is ordered behind it by the barrier, instead of waiting for the batch on the CPU. The write-back's own storage-upload read therefore stays pending next to the draw's input read, so agc_driver_vertex_target_range_imported accepts either kind for the first read it finds and keeps the copied case (no read at all) strict; the draw consuming the GPU-produced offsets (check) is the proof the barrier orders it.

Tested

Fedora 44 (kernel 7.2.8, GCC 16.2.1, Ninja, Release, BUILD_TESTING=ON, Intel Arc Meteor Lake, Mesa 26.2.3), worktree on main 7663bc6: agc_driver_vertex_target_range_imported, agc_driver_vertex_target_range_copied, agc_driver_tessellation (it passes on this base), agc_recorder_kept_bytes, agc_recorder_read_list, agc_pipeline_cache, agc_pipeline_library and agc_pipeline_link_optimization pass with the new cases (a vertex input free of pending writes is not marked, one under a pending write is bound in place and marked, one under a queued label store still refuses); tools/check_conventions.py --base origin/main and git diff --check pass. Minecraft home screen (a performance change, so the trace form applies, which I have not captured yet; the figures below come from the recorder's [hooksync] report with APS5_PROFILE_DRAW=1, not from APS5_TRACE_FRAMES): 42 to 60 fps on this device, with the recorder report dropping from about 430 pending-write syncs per 10 s to 30 to 50, none on an open batch. Not tested on Windows or NVIDIA.

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 (fix(agc): honor bounded direct index fetches #4824, which also touched InPlaceDrawInput, is closed)
  • Unblocks: PPSA17221 (no Game bring-up discussion yet)
  • GPUs tested: Intel Arc Meteor Lake (i915, Mesa 26.2.3, Fedora 44) for the tests and the Minecraft run; Windows RTX 4080 SUPER by xXPBSXx on an earlier head (pipeline cache and library tests only)
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

@github-actions github-actions Bot added the agc libSceAgc* label Oct 12, 2026
@xXPBSXx

xXPBSXx commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

Windows test (MinGW GCC 15.2, Release, Windows 10, RTX 4080 SUPER). Built on current main 3779597 together with four other open GPU PRs (their own commits cherry-picked, no merge commits), so only the tests that belong to this PR say something about it:

  • tests/Recorder.cpp: its pipeline cache and pipeline library modes, agc_pipeline_cache and agc_pipeline_library (3 of 3 runs each, passed). The other Recorder.cpp modes (agc_pipeline_library_off, agc_pipeline_library_fast_only) were not run.

Not tested: no game run. The batch was built once, not per PR; nothing failed, so no bisect was needed.

AI-assisted (Claude Code).

@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.

Built merged onto current main and ran the libSceAgcDriver ctests:

  • agc_driver_vertex_target_range_imported fails with this PR ("the vertex regression used the wrong copied/imported path", tests/Recorder.cpp:5643) and passes on main (same build dir, PR files reverted). That test expects a vertex range under a pending GPU write to follow the existing flush/import behaviour; with the new behindGpuWrite path the pending read it checks (DescribePendingRead) no longer matches. Please make the code and that test agree, and say in the PR which behaviour is intended.
  • agc_driver_tessellation segfaults here too; please check whether it does on main for you.
  • Conventions is red: the template needs Depends on filled with #N or none (#4824 also touches InPlaceDrawInput in Draw.cpp, so check for overlap).

The new cases in drawInputInPlaceTests do fail to compile without the change, so they exercise it.

@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.

The idea is right. A GPU-produced vertex buffer should be ordered with a barrier, not with a CPU wait. The driver submits everything to one VkQueue, so a barrier also covers writes in batches already submitted. Two parts of the current change are too broad, though, and they probably explain the agc_driver_vertex_target_range_imported failure adriadam10 reported.

1. Skipping FlushGpuWrites skips more than the recorder wait. The flush hook also writes back storage-image results that are still pending (StorageTexture layers marked by markLayersPending, written to guest memory on flush). Those results live only in the VkImage until written back, so the host import doesn't hold them yet and no buffer barrier can make them visible. With behindGpuWrite set, the hook no longer runs and only PendingWriteOverlaps is consulted. A vertex range under a render target or storage image with pending results is then bound in place over stale bytes. That is the vertex-over-target case the failing test covers. The new path should apply only when the overlapping writes are buffer writes into the import itself. It should still refuse, or fall back to the flush, when an image has pending results over the range (StorageTexture::FlushPending or its query form).

2. The source access mask misses attachment writes. VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT doesn't cover COLOR_ATTACHMENT_WRITE or DEPTH_STENCIL_ATTACHMENT_WRITE. Even after point 1, any path where a draw's attachment aliases the import (directly or through a proxy) needs those in srcAccessMask. ALL_COMMANDS as the source stage is fine.

Minor:

  • vertexInputBehindGpuWrite is set before the label and copied-writer checks that can still return nullptr. The draw then emits the barrier for an input that was copied rather than bound. That is harmless but adds a barrier, and it would be clearer to set the flag only on success.
  • The perf(/Unblocks evidence (42 → 60 fps on the Minecraft home screen) would be stronger as the aps5-summary.jsonl lines of main and of the change, without APS5_PROFILE_DRAW, per the Performance report form. The [hooksync] counts are good supporting detail.

@github-actions

github-actions Bot commented Oct 15, 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
PR 6529 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp L931-932
PR 6612 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp L1795-1796

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 1368 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1896 core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 3891 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4072 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 4156 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4396 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4397 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4422 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4423 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4424 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4520 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4521 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4522 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4523 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4566 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4599 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4718 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4740 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4749 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4760 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4813 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5021 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5431 core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5499 core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5867 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5875 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5925 core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6038 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6222 core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6292 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6312 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6454 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6486 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp

This was referenced Oct 15, 2026
@Mouad-Scriptz-0
Mouad-Scriptz-0 force-pushed the perf/agc-inplace-vertex-barrier branch from b1e0675 to 92d7697 Compare October 15, 2026 21:05
@Mouad-Scriptz-0

Copy link
Copy Markdown
Author

Pushed 92d7697 on current main, addressing the review:

  • agc_driver_vertex_target_range_imported now agrees with the code. Intended behaviour (written in the description): a vertex range under a pending GPU write is written back by the GPU, pending storage images flushed first as the old CPU flush did, and the draw is ordered behind it by the barrier instead of waiting on the CPU. The write-back's storage-upload read therefore stays pending next to the draw's input read, so the test accepts either kind for the first read it finds. The copied case stays strict (no read at all), and the draw consuming the GPU-produced offsets is the proof the barrier orders it. agc_driver_vertex_target_range_copied, agc_recorder_kept_bytes, agc_recorder_read_list, the pipeline cache and library tests pass.
  • agc_driver_tessellation passes on this base; the segfault was not from this change.
  • Checklist now follows the current template (Depends on: none; fix(agc): honor bounded direct index fetches #4824 is closed). The fps evidence is from the recorder's [hooksync] report, not an APS5_TRACE_FRAMES capture; I'll attach the summary lines and traces when I can run the title again.

@adriadam10 adriadam10 self-assigned this Oct 16, 2026
Mouad-Scriptz-0 and others added 2 commits October 16, 2026 11:30
…U writes with a barrier

An in-place draw input flushed pending GPU writes over its range on the
CPU before the bind, a submit-and-wait of the open batch when a dispatch
in it wrote the buffer. The CPU never reads a vertex buffer's bytes, so a
vertex input under a pending write is bound in place behind a barrier from
shader and transfer writes to vertex attribute reads, recorded before the
draw's rendering begins; index inputs keep the flush, since the highest
index is read on the CPU. Minecraft generates a vertex buffer with a
compute dispatch every frame and drew from it one CPU wait later.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@adriadam10
adriadam10 force-pushed the perf/agc-inplace-vertex-barrier branch from 92d7697 to 4339eee Compare October 16, 2026 09:40

@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.

Re-checked 92d7697: agc_driver_vertex_target_range_imported passes now, and the FlushPending for storage images covers CnRJay's first point. Their two remaining points are addressed in 4339eee, pushed on top:

  • the vertex-input barrier's srcAccessMask now includes COLOR_ATTACHMENT_WRITE and DEPTH_STENCIL_ATTACHMENT_WRITE;
  • behindGpuWrite is only set when the input is actually bound in place, so a copied input no longer emits the barrier.

Built and ran the libSceAgcDriver tests (only agc_driver_tessellation fails, same as on main); the new cases fail to compile without the change. Merging once CI is green. The Minecraft numbers are not verified on our side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants