Skip to content

perf(agc): copy draw inputs on NVIDIA's Windows driver instead of binding host imports in place - #6763

Open
SP4C3B4R-8 wants to merge 2 commits into
boykopovar:mainfrom
SP4C3B4R-8:perf/windows-draw-input-copies
Open

SP4C3B4R-8 wants to merge 2 commits into
boykopovar:mainfrom
SP4C3B4R-8:perf/windows-draw-input-copies

Conversation

@SP4C3B4R-8

@SP4C3B4R-8 SP4C3B4R-8 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What

Since #2253, a draw binds its vertex and index ranges in place from the host import that holds them. That was measured on Linux. NVIDIA's Windows driver reads a host import of guest memory far slower than a buffer it allocated, and the draws pay for it on the GPU.

Astro Bot (PPSA21564), -lvl hub_crashsite, standing at the start position, same build both times, APS5_PROFILE_GPU=1:

in place (current main) copied (this PR)
draw GPU time per present ([gputime]) 164.3 ms 45.4 ms
fps (60 s steady window) 4.71 7.52

Same number of draws (about 220 timed ranges per present); compute dispatches cost the same in both. The frames match (shadows, foliage, sky checked on dumped frames).

Others' A/B runs in this thread: an RTX 3060 shows the same (draws 32.3 against 24.3 ms per frame in place, parl0v), while AMD's Windows driver reads in place about as fast as a copy (RX 9070 XT: 69.8 against 73.1 ms, SoRadGaming), so copying there only costs CPU.

  • The device decides once (DrawInputsInPlaceUsable, Context::drawInputsInPlace): ranges are copied with VK_DRIVER_ID_NVIDIA_PROPRIETARY on Windows, as before perf(agc): read draw vertex and index buffers in place from host imports #2253, and bound in place everywhere else.
  • APS5_DRAW_INPUTS_IN_PLACE=1 binds them in place and APS5_NO_DRAW_INPUTS_IN_PLACE=1 copies them on any device; the second wins.
  • docs/dev/ARCHITECTURE.md gets the line.

Copies in device-local host-visible memory (ReBAR) cut the draw time further, to 39 ms, but the frame rate stayed the same in this scene (CPU-bound there after this change), so this PR keeps the copies where they were.

Tested

Windows 11 Pro (26300), MinGW-w64 GCC 15.2.0, Release, Ryzen 7 7800X3D, RTX 5070 Ti (16 GB).

  • New ctest agc_draw_inputs_in_place_driver: the decision per driver and host (NVIDIA proprietary on Windows copies; NVIDIA elsewhere, AMD, Intel, RADV and llvmpipe bind in place).
  • New ctest agc_draw_inputs_copied (agc_driver_recorder_tests --draw-inputs-copied-only): with Context::drawInputsInPlace off, an aligned imported range is not bound in place and its read is not noted. With InPlaceDrawInput ignoring the flag it fails ("an imported draw input was bound in place where draw inputs are copied"). It passes with APS5_DRAW_INPUTS_IN_PLACE=1 exported, and agc_draw_inputs_in_place passes with APS5_NO_DRAW_INPUTS_IN_PLACE=1 exported: neither depends on the environment.
  • agc_pipeline_link_optimization still passes; libs builds.
  • tools/check_conventions.py --base origin/main passes.

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
  • Fixes: none
  • Unblocks: none
  • GPUs tested: NVIDIA GeForce RTX 5070 Ti, driver 617.42, Windows 11 Pro 26300
  • AI-assisted: yes (Claude). I reviewed and take responsibility for every line.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GN8FJFTedayRXLHythXkZ4

@SP4C3B4R-8

Copy link
Copy Markdown
Contributor Author

Rebased onto main 51acb79 (head bf1bae55c). One conflict in InPlaceDrawInput, from #4649 (in-place vertex inputs ordered behind pending GPU writes by a barrier), which landed meanwhile: the Windows check now sits in front of its version, so on Windows neither path binds in place unless APS5_DRAW_INPUTS_IN_PLACE=1. agc_draw_inputs_in_place, agc_draw_inputs_copied and #4649's agc_driver_vertex_target_range_* pass. The numbers in the description are from the previous base (5d9dcc4).

@SP4C3B4R-8
SP4C3B4R-8 force-pushed the perf/windows-draw-input-copies branch from 643afa8 to bf1bae5 Compare October 16, 2026 10:39
@github-actions

github-actions Bot commented Oct 16, 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 6187 core/libs/prx/libSceAgcDriver/CMakeLists.txt L1435
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt L1435
PR 6642 core/libs/prx/libSceAgcDriver/CMakeLists.txt L1435
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt L1435
PR 7508 core/libs/prx/libSceAgcDriver/CMakeLists.txt L705

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 1191 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 1341 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 1368 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4072 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 4472 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5426 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5441 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5499 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 5656 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/ARCHITECTURE.md
PR 6044 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ARCHITECTURE.md
PR 6146 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6177 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6185 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6202 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6249 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6290 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6292 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6300 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6501 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6539 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6611 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6615 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6636 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
docs/dev/ENVIRONMENT.md
PR 6661 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6683 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6701 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6727 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 6741 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6747 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6788 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6868 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6924 docs/dev/ENVIRONMENT.md
PR 6931 docs/dev/ARCHITECTURE.md
PR 6937 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6941 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6946 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/ARCHITECTURE.md
docs/dev/ENVIRONMENT.md
PR 6947 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/ARCHITECTURE.md
docs/dev/ENVIRONMENT.md
PR 6955 docs/dev/ENVIRONMENT.md
PR 6958 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6960 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6963 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6970 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6971 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7048 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7201 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7223 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7233 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7254 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 7258 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7292 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7332 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/ENVIRONMENT.md
PR 7340 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
docs/dev/ENVIRONMENT.md
PR 7369 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
PR 7390 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7417 docs/dev/ENVIRONMENT.md
PR 7446 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7449 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7484 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
docs/dev/ENVIRONMENT.md
PR 7505 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 7514 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/ENVIRONMENT.md
PR 7615 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7622 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/ARCHITECTURE.md
docs/dev/ENVIRONMENT.md
PR 7647 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 7683 docs/dev/ENVIRONMENT.md
PR 7800 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp
PR 7803 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7827 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp

@SoRadGaming

Copy link
Copy Markdown
Contributor

The A/B you asked for, on AMD: same scene, ASTRO BOT (PPSA21564 01.007.000) -lvl hub_crashsite.

Host: Windows 11 Pro 26200, Ryzen 7 5800X3D, Radeon RX 9070 XT (AMD proprietary driver 32.0.23033.1002), 32 GB, WinLibs GCC 15.2.0 r7, Release.

Build. main 5d9dcc4 plus a stack of open PRs (#6188, #6273, #6292, #6505, #6503 and others) and this PR's condition in InPlaceDrawInput. On that base InPlaceDrawInput still has the four-argument signature (no behindGpuWrite), so I applied the change by hand there. One binary, toggling only APS5_DRAW_INPUTS_IN_PLACE. Warm shader cache, no input; FPS from the window's present counter over 120 s, after a 120 s settle. Runs alternate.

hub FPS
draw inputs copied (this PR's Windows default) 7.42 / 7.44
APS5_DRAW_INPUTS_IN_PLACE=1 7.67 / 7.65

GPU time with APS5_PROFILE_GPU=1, [gputime] draw class, per present:

draw GPU ms / present
copied 73.1
in place 69.8

So on this AMD driver the GPU reads host imports of guest memory about as fast as driver-allocated buffers, unlike the 164 vs 45 ms you measured on NVIDIA. Copying only adds CPU work, and this scene is limited by the pipelined draw committer here, so the frame rate drops about 3 %.

That points at the NVIDIA driver rather than Windows as the case to copy for. Gating the default on VK_DRIVER_ID_NVIDIA_PROPRIETARY instead of _WIN32 might keep your gain without this loss. I can't check other AMD generations or Intel.

AI-assisted (Claude Code); the builds and runs are on my machine.

@ps54all

ps54all commented Oct 16, 2026

Copy link
Copy Markdown

Data point from a CPU-bound title on Windows: Metal Gear Solid 4 (PPSA18194), gameplay from a save, i9-10850K, RTX 3080 10 GB (610.74), MinGW GCC 15.2. Build: a local stack (#6188, #6187/#6192, #6273, #6077) + this PR, one binary, APS5_PROFILE_DRAW=1, 1200 s per arm, medians over 10 s gameplay windows.

copied (this PR's default) in place (APS5_DRAW_INPUTS_IN_PLACE=1)
fps ~7.2 ~7.0
draws per 10 s 162k 179k
draw thread per draw 53.9 us 48.9 us
vertex phase per 10 s 1788 ms 921 ms
GPU busy per present 29.7 ms 30.6 ms

Here the frame is held by the draw thread (~140 ms per present, GPU busy ~30 ms), so the copies cost CPU on that thread (vertex time about 2x) and the GPU saving does not show. Frame rate is about the same (scenes are not identical across the two runs; the vertex phase is the clear difference). Not asking for a change; noting that the gain depends on whether the title is GPU- or draw-thread-bound, in case a size threshold or per-buffer heuristic is considered later.

Tested with AI assistance (Claude Code).

@parl0v

parl0v commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

A/B on a Windows NVIDIA card in a second title: RTX 3060 12 GB (NVIDIA 617.42), i5-12400F, Windows 10 19045, MinGW GCC Release, main 4524944 + this PR (bf1bae5). Minecraft Dungeons II (PPSA16064 01.100.100), title screen, no input, 200 s rows alternating, APS5_PROFILE_DRAW=1 APS5_PROFILE_GPU=1; the same libs both ways, toggled with APS5_DRAW_INPUTS_IN_PLACE=1. FPS is the mean of the last 10 [present] windows, GPU busy and idle gaps per frame over the last 8, classes per present from the last [gputime] line.

draw inputs FPS GPU busy idle gaps draw copy staging-in / -out
copied (this PR's default) 6.66 / 6.67 / 6.68 97 / 96 / 96 ms 53 / 54 / 54 ms 24.3 ms 5.9 ms 0.8 / 0.8 ms
in place (APS5_DRAW_INPUTS_IN_PLACE=1) 6.58 / 6.66 / 6.69 105 / 105 / 104 ms 47 / 45 / 45 ms 32.3 ms 6.0 ms 0.8 / 0.8 ms

The draw class drops by a quarter (-8 ms per present) and GPU busy by the same amount, so the in-place reads cost the same here as on your 5070 Ti. The frame rate doesn't move because this scene is CPU-bound on this machine: the freed time shows up as idle gaps (+8 ms). 0 fatal in all six rows.

AI-assisted: yes (Claude Code)

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

Copy link
Copy Markdown
Contributor

One test-isolation edge on Windows: main() deliberately does not set APS5_DRAW_INPUTS_IN_PLACE for --draw-inputs-copied-only, but it also does not clear an inherited value. The copied CTest sets APS5_NO_DRAW_INPUTS_IN_PLACE=1; DrawInputsInPlace() does not consult that variable on Windows, so a caller that already exports APS5_DRAW_INPUTS_IN_PLACE=1 still takes the in-place path and the copied-only assertion fails. Could the copied-only setup explicitly remove the Windows variable or otherwise control the actual Windows switch?

@SP4C3B4R-8
SP4C3B4R-8 force-pushed the perf/windows-draw-input-copies branch from bf1bae5 to 3dfcf22 Compare October 17, 2026 08:23
@SP4C3B4R-8 SP4C3B4R-8 changed the title perf(agc): copy draw inputs on Windows instead of binding host imports in place perf(agc): copy draw inputs on NVIDIA's Windows driver instead of binding host imports in place Oct 17, 2026
@SP4C3B4R-8

Copy link
Copy Markdown
Contributor Author

Thanks all for the A/B runs.

@SoRadGaming good call: the slow in-place reads are NVIDIA's driver, not Windows. With parl0v's RTX 3060 numbers (draws 32.3 against 24.3 ms per frame in place) and yours on the RX 9070 XT (in place as fast as copied), the default now copies only with VK_DRIVER_ID_NVIDIA_PROPRIETARY on Windows and binds in place everywhere else. The device decides once (DrawInputsInPlaceUsable, Context::drawInputsInPlace), and agc_draw_inputs_in_place_driver checks the decision per driver and host.

@MarcZX100 fixed: the copied-only test no longer depends on the environment. It sets Context::drawInputsInPlace itself, and the recorder test binary no longer sets APS5_DRAW_INPUTS_IN_PLACE. For users, APS5_DRAW_INPUTS_IN_PLACE=1 and APS5_NO_DRAW_INPUTS_IN_PLACE=1 now work on any device, and the second wins. agc_draw_inputs_copied passes with APS5_DRAW_INPUTS_IN_PLACE=1 exported, and agc_draw_inputs_in_place passes with APS5_NO_DRAW_INPUTS_IN_PLACE=1 exported.

@ps54all noted. On a frame held by the draw thread the copies cost CPU. A size threshold could come later, once there is a scene where it changes the frame rate.

Rebased onto main (5f61879a9), one commit.

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

HQasper commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Another NVIDIA data point, in case it helps: RTX 5090, driver 617.42, Windows 11, Ryzen 9 9950X3D.

Setup:

  • Hello Neighbor (PPSA35154), in game after Continue, 60 s window.
  • Three alternating runs per side with the same binary.
  • The only difference is whether draw ranges are bound in place or copied. I used a local switch that copies them as this PR does.
in place copied
fps, mean of 3 runs 7.20 9.78
single runs 6.88 / 7.55 / 7.17 9.67 / 9.82 / 9.85

The build was main 47a266c with #6060's settle floor lowered to 64 KiB locally (APS5_SETTLE_FLOOR_KIB=64), so the absolute numbers are not main's. No traces were captured.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 18, 2026
@SP4C3B4R-8
SP4C3B4R-8 force-pushed the perf/windows-draw-input-copies branch from 3dfcf22 to dc86eb9 Compare October 18, 2026 08:28
@Scramblrrrr

Copy link
Copy Markdown
Contributor

I am preparing a 007 First Light Windows cross-test of dc86eb9 on RTX 5070 Ti / 7800X3D / NVIDIA 617.42. The active fresh-main baseline is still completing cold pipeline compilation, so there is no result for this PR yet. The integration is 87f94c6 on main e5c2b3c, with the required public boot/resource PRs and an opt-in 16 MiB import window. Merge preview has one VulkanDevice.cpp conflict adjacent to that window option; your driver policy can be preserved independently.

I will compare your in-place/copy switches in the same build, scene and warm cache and report official trace summaries plus draw GPU time. No duplicate implementation from me. Raw title data stays local. AI-assisted: Codex.

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

Copy link
Copy Markdown
Contributor

Completed a Windows 007 First Light menu pair on integration 8bdc43094f996d1334776dac7dc71ca2092915f7, carrying this PR's dc86eb93e32700178bb886398345499a111b3496 on main e5c2b3c325. RTX 5070 Ti, NVIDIA 617.42, Ryzen 7800X3D, Windows 11 Pro 26200, Release, SPIRV-Tools OFF. The merge keeps your policy and the separate opt-in 16 MiB import window. All 51 selected native controls pass (23.77 s, zero skips), including driver selection and both input paths.

Same binary, warm caches, 3840x2160, Performance mode, offline main menu with its animated background, no input during each sample. Only APS5_DRAW_INPUTS_IN_PLACE=1 versus APS5_NO_DRAW_INPUTS_IN_PLACE=1 changes. Shared settings: draw cache 1024, resource entries 16, host imports 4096 MiB, texture budget 2048 MiB, synchronous shader preparation, SDR override, profiling and 10 s summary collection. Identical 2 GiB free/28 GiB private guards. Both render correctly and were stopped intentionally after measurement.

Input policy Presents Seconds FPS GPU busy ms/present
Imported 106 61.37206 1.727 178.94
Copied 109 62.29968 1.750 176.96

This single pair shows no clear gain in this scene (about 1.3% between arms, similar to movement/run variation). Recent fully-menu GPU counter reports put the draw class near 3 ms/present in both; compute dominates the GPU time. This does not contradict the draw-heavy Astro Bot result, and it does not measure level gameplay.

The first copied warm-up had a 43.455 s transition stall and resumed before the owned stop; I did not use it as a menu sample. Similar WAIT_REG_MEM_64 diagnostics occur in the previous baseline and imported control, and the copied repeat passed the transition, so there is no demonstrated copy-specific hang or regression.

Official trace_digest.py outputs from six fully contained menu intervals in each arm:

import8bd-b:

**6 intervals**, 286.4 to 347.8 s, 106 presents
NVIDIA GeForce RTX 5070 Ti, NVIDIA 617.42, Windows 10.0.26200, 3840x2160, commit 8bdc43094f99

| # | seconds | presents | p50 | p90 | p99 | max | GPU busy | busiest worker | largest phase |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 286-297 | 18 | 592.6 | 652.1 | 653.1 | 653.1 | 179.5 | 100 % | Driver.Packet 2821.5 |
| 2 | 297-307 | 18 | 603.6 | 652.8 | 671.3 | 671.3 | 187.7 | 101 % | Driver.Packet 2852.1 |
| 3 | 307-317 | 17 | 588.4 | 697.7 | 888.6 | 888.6 | 168.8 | 101 % | Driver.Packet 2947.2 |
| 4 | 317-327 | 18 | 566.3 | 808.9 | 847.4 | 847.4 | 177.7 | 100 % | Driver.Packet 2821.8 |
| 5 | 327-338 | 18 | 597.4 | 636.3 | 877.5 | 877.5 | 178.0 | 100 % | Driver.Packet 2919.3 |
| 6 | 338-348 | 17 | 603.7 | 657.9 | 838.6 | 838.6 | 181.5 | 100 % | Driver.Packet 2887.1 |

p99 ms ##@@@% 0-888.6
gpu ms @@%@@@ 0-187.7

copy8bd-a2:

**6 intervals**, 325.3 to 387.6 s, 109 presents
NVIDIA GeForce RTX 5070 Ti, NVIDIA 617.42, Windows 10.0.26200, 3840x2160, commit 8bdc43094f99

| # | seconds | presents | p50 | p90 | p99 | max | GPU busy | busiest worker | largest phase |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 325-335 | 18 | 588.8 | 631.5 | 638.9 | 638.9 | 191.4 | 100 % | Driver.Packet 2802.5 |
| 2 | 335-346 | 18 | 603.9 | 626.1 | 671.5 | 671.5 | 165.8 | 100 % | Driver.Packet 2856.3 |
| 3 | 346-356 | 18 | 606.5 | 635.4 | 671.3 | 671.3 | 177.7 | 100 % | Driver.Packet 2934.0 |
| 4 | 356-367 | 18 | 590.6 | 700.8 | 736.5 | 736.5 | 185.7 | 101 % | Driver.Packet 2921.7 |
| 5 | 367-377 | 18 | 581.0 | 664.5 | 752.8 | 752.8 | 168.0 | 100 % | Driver.Packet 2822.5 |
| 6 | 377-388 | 19 | 574.1 | 598.9 | 720.9 | 720.9 | 173.3 | 100 % | Driver.Packet 2757.3 |

p99 ms %%%@@@ 0-752.8
gpu ms @%%@%% 0-191.4

No title patches or time scaling. Raw title code/assets/descriptors/requests/logs remain local. AI-assisted: Codex.

@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed perf Performance change perf numbers missing perf pull request without a before and after summary line labels Oct 18, 2026
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
@SP4C3B4R-8
SP4C3B4R-8 force-pushed the perf/windows-draw-input-copies branch from dc86eb9 to ff0ee09 Compare October 19, 2026 08:32
@github-actions

Copy link
Copy Markdown

This pull request is labeled perf, so it needs the summary line of a capture on main and one with the change, from the same title, scene and machine, as the Performance report form asks. Please paste both aps5-summary.jsonl lines, attach the two traces and add the output of tools/trace_digest.py.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
SP4C3B4R-8 and others added 2 commits October 19, 2026 11:48
…ding host imports in place

NVIDIA's Windows driver reads a host import of guest memory far slower than a buffer it allocated.
Since draw vertex and index ranges are bound in place from host imports (boykopovar#2253, measured on Linux), Astro
Bot's Crash Site on Windows 11 with an RTX 5070 Ti spends 164 ms of every frame in draws on the GPU, and
45 ms with the ranges copied as before; with the same build, 4.71 fps in place against 7.52 copied. An
RTX 3060 shows the same (draws 32.3 against 24.3 ms per frame), while AMD's Windows driver reads them in
place about as fast as a copy (RX 9070 XT: 69.8 against 73.1 ms), so copying there only costs CPU.

The device decides once (DrawInputsInPlaceUsable, Context::drawInputsInPlace): ranges are copied with
VK_DRIVER_ID_NVIDIA_PROPRIETARY on Windows and bound in place everywhere else. APS5_DRAW_INPUTS_IN_PLACE=1
binds them in place and APS5_NO_DRAW_INPUTS_IN_PLACE=1 copies them on any device, the second winning.
agc_draw_inputs_in_place_driver checks the decision per driver and host; agc_draw_inputs_copied checks
that an aligned imported range is copied and its read not noted when the context copies, whatever the
inherited environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GN8FJFTedayRXLHythXkZ4
@adriadam10
adriadam10 force-pushed the perf/windows-draw-input-copies branch from ff0ee09 to 6df86af Compare October 19, 2026 10:03
@m335671

m335671 commented Oct 19, 2026

Copy link
Copy Markdown

A/B on a CPU-bound UE5 title: Sword of the Sea (PPSA25344), Windows 11 26200, RTX 4070 12 GB, NVIDIA 617.42, Ryzen 9 7900, 3840x2160.

Build: main 36f60df9e with this PR merged locally (head 4a6069481; the patch is identical to ff0ee0975, which is what I built; the later 6df86af0f only adds docs rows). Same binary for both sides:

  • baseline: APS5_DRAW_INPUTS_IN_PLACE=1 (in-place binding, as on main);
  • candidate: the PR default (draw inputs copied on NVIDIA Windows).

Both runs used APS5_PROFILE_DRAW=1 APS5_PROFILE_GPU=1 APS5_TRACE_SUMMARY_EVERY=10 APS5_THREAD_PRIORITY=high, a warm shader cache and the same path: about 30 s on the title screen, then New Game, the opening cinematic and about 1 min in game. The figures are medians over each scene's 10 s summary intervals; intervals containing a hitch over 3 s are left out.

Scene frame p50 (baseline → PR) GPU busy / present [gputime] draw / present
Title screen 80.0 → 76.7 ms (−4 %) 52.5 → 47.4 ms 12 → 6 ms
Opening cinematic 242.9 → 245.2 ms (±0) 125.2 → 106.3 ms (−15 %) ~31 → ~14 ms
In game 431.3 → 413.0 ms (−4 %) 252.3 → 170.0 ms (−31 %) ~113 → ~23 ms

The PR does what it says on this title: draw GPU time halves on the title screen and drops about 5× in game. Frame time barely moves, because the title is CPU-bound on the queue worker: queue worker 0x0 is at 98–99 % busy in both runs, and in game Driver.Packet is about 720–750 ms per frame, of which memory_wait is about 255–267 ms. I saw the same picture last night on main 40393b0f9 with the pre-rebase head: draws went from 12.0 to 6.4 ms on the title screen.

tools/performance_compare.py, in-game interval (baseline = in place, candidate = PR)
Metric Baseline Candidate Change Change (%)
frame_ms.p50 431.261 413.158 -18.103 -4.198
frame_ms.p90 455.783 424.865 -30.918 -6.783
frame_ms.p99 487.533 447.400 -40.133 -8.232
mean_frame_ms 431.141 413.047 -18.093 -4.197
gpu_busy_ms_per_present 252.340 173.341 -78.999 -31.307
Driver.Packet 753.306 721.972 -31.334 -4.160
Driver.Packet.dispatch 169.215 137.014 -32.201 -19.030
Driver.Packet.draw 188.195 178.386 -9.809 -5.212
Driver.Packet.memory_wait 255.096 267.239 +12.143 +4.760
{"commit":"2ccad8739d02","dirty":false,"env":{"APS5_DRAW_INPUTS_IN_PLACE":"1","APS5_PROFILE_DRAW":"1","APS5_PROFILE_GPU":"1","APS5_THREAD_PRIORITY":"high","APS5_TRACE_DIR":"<path>","APS5_TRACE_SUMMARY_EVERY":"10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 617.42","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"summary_every 10","first_present":1137,"ms":10347.373},"interval_start_s":294.871,"interval_end_s":305.218,"presents":24,"frame_ms":{"p50":431.261,"p90":455.783,"p99":487.533,"max":487.533},"gpu_busy_ms_per_present":252.34,"worker_busy_pct":{"queue worker 0x0":98.101,"queue worker 0x20":76.811},"top_phases_ms_per_frame":{"Driver.Packet":753.306,"Driver.Packet.memory_wait":255.096,"Driver.Packet.draw":188.195,"Driver.Draw":186.668,"Driver.Packet.dispatch":169.215,"Graphics.Draw":155.603,"Graphics.Draw.draw_and_resource_release":141.098,"Driver.Dispatch":120.981},"trace_dropped":false,"trace":""}
{"commit":"2ccad8739d02","dirty":false,"env":{"APS5_PROFILE_DRAW":"1","APS5_PROFILE_GPU":"1","APS5_THREAD_PRIORITY":"high","APS5_TRACE_DIR":"<path>","APS5_TRACE_SUMMARY_EVERY":"10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 617.42","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"summary_every 10","first_present":1148,"ms":10326.181},"interval_start_s":505.701,"interval_end_s":516.027,"presents":25,"frame_ms":{"p50":413.158,"p90":424.865,"p99":447.4,"max":447.4},"gpu_busy_ms_per_present":173.341,"worker_busy_pct":{"queue worker 0x0":98.089,"queue worker 0x20":76.951},"top_phases_ms_per_frame":{"Driver.Packet":721.972,"Driver.Packet.memory_wait":267.239,"Driver.Packet.draw":178.386,"Driver.Draw":176.905,"Graphics.Draw":166.423,"Graphics.Draw.draw_and_resource_release":141.328,"Driver.Packet.dispatch":137.014,"Driver.Dispatch":110.59},"trace_dropped":false,"trace":""}

Limits:

  • There was one run per side on the full path. On the title screen I have two runs per side: 77.8–80.1 ms in place against 74.1–76.7 ms with the PR.
  • Between identical runs of this title I measured about ±6 % of noise in the cinematic.
  • In game, the character falls through the floor shortly after getting control in both variants (this is unrelated to the PR), so the in-game scene only roughly matches between runs.
  • I saw no visual regression. In game there seemed to be slightly more colour with the PR, but I have no screenshots, so take that as anecdotal.

AI-assisted: yes (Claude Code). The build, the runs and this comment were done and written by Claude Code on my machine.

This branch has not been deployed

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

Labels

agc libSceAgc* build docs perf numbers missing perf pull request without a before and after summary line perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants