Deck: the frame is CPU-bound in the submission path, not GPU-bound (16 fps at 50% internal, GPU-bound wait 0.4 ms) #402
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8015c9a
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot#402
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Steam Deck OLED (RDNA2, 4-core), current main
cf605c85, NVMe install, internal Resolution 50% (640x400 -> game's own upscale), Kokiri Forest, steady camera turn. The Deck sits at 16-19 fps here and it is not GPU-bound: the renderer's own per-frame stats, one window:So the frame is ~50 ms wall while the GPU waits 0.4 ms for the submission: the cost is on the CPU/driver side recording and flushing the frame. The submission-side line items alone (submit 24 + commands 6 + descriptors 5) are about 36 of those 50 ms. Roughly 8.8 flushes a frame (1053/120), most of them "other" rather than present/wait/cap - many small command-buffer breaks a frame across 175 passes and 171 barriers.
Two things I ruled out on this machine, both negative, so they can be left out of the search:
Question for whoever knows this path: on RADV with RX_VK_BATCH already on, is the ~9-flushes-a-frame "other" pattern reducible for a scene with this many passes/barriers, or is there a known knob to hold a frame in fewer/larger submissions? This is the Deck's main frame-rate ceiling; the GPU is idle.
More Deck arms for this issue (Steam Deck OLED, same save, Kokiri spawn). Build: main
fad1a964through the dev_packs pipeline (rendererf978d720), install on microSD, so read #386's ~15-20% storage penalty into every row. Options fixed across arms: cap 60, vsync off, AMD FSR 3.1, internal 50% (RX_VK_TEX_*memory switches on),RX_VK_MESH_OPT=1,RX_VK_READBACK_LATE=newest. Protocol fixed: load the save, ~10 idle samples, ~15 s full-speed right-stick spin, ~20 s walk, same script every arm; fps is the bridge's rolling average, min over samples where shown.power_dpm_force_performance_level=high), Threaded OffRX_VK_OCC_FIXED=-1)* the walk arms end at different headings, so walk columns across arms are not comparable; within-arm they're consistent.
What this says about the bottleneck in this issue:
gamemoderundefaults (CPU governor, io priority) recover any of the gap on top of this recipe; and pinned clocks measured against an internal-NVMe install to separate #386 from the clock effect.One Deck-specific note for whoever reads the ceiling: the two levers this table isolates stack. This run's spin idle is 17.2 at auto clocks on SD; pinning multiplies it 1.23 (measured, this table) and the internal-NVMe install adds the ~1.19 from #386's measurement — so the same arm on internal storage at pinned clocks should land near 25 idle before anything in this issue's scope is touched. That arithmetic is mine, not a measurement; the pin stage against the NVMe install will replace it with a number. What remains after both levers is this issue's actual subject: the ~24 ms submit walk.
Finishing the launch-state levers on this issue (same build, save, protocol as the table above):
gamemoderun(SteamOS gamemode 1.8.2): idle 16.8 / 17.0 against 17.1 / 17.3 for plain launch - nothing. That gamemode build's own strings say "no cpu governors found" on this image, and it does not touch GPU clocks.performance(amd-pstate-epp, 8 policies, GPU left at auto): idle 17.9 / 18.0 - about +4%, inside noise on the spin arms (15.5 / 18.6 against 17.0 / 17.6). The submit walk this issue measures is single-threaded work per frame; the governor mainly changes how quickly a thread wakes into boost, not the walk itself.power_dpm_force_performance_level/scaling_governor); the arms ran with the same wrapper as the rows above.So on the Deck the frame's shape is: one first-order launch-state lever (GPU clock, +23% here and again +30% dukenheimer measured in Game Mode), a small second (governor), and this issue's ~24 ms submit walk underneath both. Filed separately as a Deck launch proposal with the numbers table.
The launch-state proposal with this table is now #716 (Deck: launching from desktop mode gets no performance treatment).
Correction on my Threaded rendering arms in the table above, plus the rerun done right. What happened: the ini row
threaded_rendering=sits at its default and the options writer drops default rows, so my sed never applied - the four "On" arms loggedAuto: offand were Off. That part of the table is void, sorry. The rerun used the env overrides instead (RX_VK_ASYNC=1= the menu's On row exactly per vk_api.h:68; andRX_VK_RECORDER=threadon top, which the menu cannot select), four+ arms interleaved, same build/protocol/storage:RX_VK_ASYNC=1only, two armsRX_VK_ASYNC=1 RX_VK_RECORDER=thread, two armsReading: the menu's On row is parity at best on the Deck (idle 18.1-18.6 inside the Off arms' 17.9-21.3 spread; walk maybe a hair better, draws lean lower so call it a wash). The recorder-thread configuration is reliably worse: -14% idle/spin in both arms - on this APU, packing vkCmd* into a ring for a recorder thread costs more than it overlaps. That is worth keeping out of any Auto rule for 8-logical-CPU AMDs even as step 2 matures. Caveat on the Off spread itself: 17.9 to 21.3 across three identical arms is a warning that single arms here carry several fps of machine-state variance; the paired rerun of the clock-pin A/B (issue #716) is in flight before anyone quotes +23% as settled.
Milestone: best-playable Deck configuration, measured numbers. Angela asked for this written up for the devs. Deck LCD (Jupiter/Vangogh), game on the SD card, Kokiri Village load scenario (top save, ~2 min settle, idle → full-speed camera spin → forward walk).
Runtime: dev_packs runtime built from merged main
a4821d5674bad9...(so #488 movie pacing + #399 skip-title + #588 + #238 all in), hostaabc168d9ded, rendererf1e064e34a99. Settings, exactly as run: iniquality_preset=1 handheld_profile=1 resolution=4 upscaling=2 (FSR3.1, 50%) sharpness=30 frame_rate_cap=3 vsync=0 frame_pacing=1 threaded_rendering=0 dlss=0 anisotropic_filtering=4 vram_budget=1at 1280x800; envRX_VK_READBACK_LATE=newest RX_VK_MESH_OPT=1 RX_VK_BC_LAYERS=8 RX_VK_TEX_MAX_DIM=512 RX_VK_TEX_SKIP_MB=8 HOS_SHORT_SLEEP_TIMER=1; plus the launch-state from issue #716:power_dpm_force_performance_level=high(sclk locked 1600 MHz) and cpu governorsperformance. Threaded confirmedAuto: offin each arm log.Three same-config arms (two from the interleaved clock pair, both pinned):
Steady-state (load-in samples with >2400 ms frame delay dropped): spin 17/21 samples >=25 fps, walk 29/32 >=25, idle medians 23.4-25.4 with p25 at 22.4+. So the steady 25-30 band is met for spin and walk on SD storage; idle sits at the bottom edge. Compare the same protocol on the pre-#488 sync runtime at auto clocks: idle ~17-18, spin ~19, walk ~28-31. Frame delay (game draw -> present backlog) drops from ~2 s idle to ~0.5-0.9 s under load, which matches the CPU-bound submit picture from this issue.
Honest caveats: byte-identical arms spread ±2-3 fps at idle and walk swings with streaming distance (draws 400-2500), so bands not points; lowest idle samples are load-in tail; all SD - #386's ~15-20% NVMe penalty still applies. One config note: the game logs
vi: frame rate cap 144for iniframe_rate_cap=3- the menu row label and the ini value disagree with what I assumed for weeks (we have been quoting "cap 60" rows that were actually 144 + vsync off). Worth checking the menu mapping.Movie check on this exact runtime: intro
5947 pictures ... 4.0 ms decode, 874 skipped to keep up with its sound- pacing code present in binary; dedicated skip-count arms rerunning now.Correction + mechanism, from the repo's own sampler on the Deck. The scene benchmark and a 60-second
HOS_PROFILE=5 HOS_SAMPLE_ALLrun on the exact Kokiri Village hot spot (rt-maina4821d56, GPU pinned 1600 MHz + governors performance, SD storage) change the picture: that spot is not "out of CPU" - it is bound on a serial wait chain around presentation, with ~7 of 8 cores idle.The arm itself first (best Kokiri numbers this Deck has produced, and Angela watching live called it "excellent, even better than our last milestone"): idle mean 27.9 (10 samples), spin 27.4, walk 31.2 - the 23-25 idle band from my earlier arms was the same config, so treat pinned Kokiri as ~24-28 and this profile as the mechanism behind it.
Profile facts (278k raw stack lines, resolved against the exact rt-main binaries with
RX_MAPS; classifier on request):syscall<-longshot:SleepConditionVariableSRW+0x7b<-hos_cv_sleep<-nn::os::ReceiveLightMessageQueue/queue_get: the game's own threads parked on message queues and events.Presentation_Thread39% of wall,sead::MainThread36%, fiveModuleSystemWorkers ~25% each, the game'sBaseProcExecutor0-3~18% each. The renderer's ownmkw_vk_worker,mkw_vk_capture_,mkw_vk_order,mkw_vk_gpu_hangthreads sampled insidesem_trywait/Sleep/sync_watch/copy_helper_main- parked, waiting for work.mkwvk::build_descriptors(300 samples),collect_in(207),mkwvk::draw(168),mkwvk::program_for(110),mkwvk::replay(96), then a long tail of lifted-gamef_*singletons anda64_fpx_*exact-float helpers. Caller context below them:site_call,n_nvnCommandBufferEndRecording,n_nvnCommandBufferAddControlMemory,run_submit,memory_capture_submit.So "CPU-bound submit path" needs a qualifier I should have used from the start: the submit path IS where the on-CPU time lives, and the micro-costs in this issue's scope are real (descriptor building and collect_in are literally the top leaves) - but no core is anywhere near saturated at 28 fps. The frame time is dominated by waits: the game's MainThread sleeping a large slice of every frame (the #73 chain analysis measured ~8.6 ms of its own loop as sleep - same signature), and a ~2 s deep present backlog draining at 28/s behind that cadence. Cutting per-draw costs buys headroom inside the serial budget; the structural win is wherever the frame's sleep/wait cadence is set.
Two things I can contribute from here: (1) testing p0lux's #567/#590 as runtimes on this exact save + protocol right now - they target exactly these top leaves, and the Hyrule Town spin stall (#750: 2-6 fps, GPU-written 691x432 texture re-uploaded per present, 714,844-page capture) is where the page-capture cost is gross, not marginal; (2) if anyone wants a same-window
HOS_THREAD_TIMESor presents.csv cross-check before trusting one 60 s sample, ask and I'll run it on the Deck (idle ±1, walk ±7 noise band applies).Hardware note for all of this: Steam Deck LCD (Jupiter, VANGOGH, LPDDR4X-4266), not OLED - draw/cost structures should match OLED, absolute fps and memory-bound margins will not.
Our Deck data on this issue's question (Steam Deck OLED, stock clocks, docked Game Mode, the moving Kokiri route, 2 interleaved runs per arm unless noted).
Where the frame went (
HOS_CHAIN, one run, the Longshot user build 92640867 = maine7ed2f7a+ Deck patches; output 2048x1152, FSR 3.1 ~50%, frame generation 2x):sead::MainThread, in series. That's the same serial chain your sampler shows.The readback switches (one build: main
cf605c85+ #225 + #223 + #303; the player's settings: Handheld, 1536x864 at 100% render, frame generation 3x):RX_VK_READBACK_LATE=newest(#238, on main)RX_VK_READBACK_EARLY=1(#303)RX_VK_READBACK_LATE=newestis the switch to use.sead::MainThreadspent ~20 ms a frame on the path waiting in the capture for late pages (HOS_CHAINon the installed build).RX_VK_CAPTURE_WAIT=0) gave nothing: 13.35 → 12.85 real at these settings, and 14.4 → 14.5 at 1280x800 with the upscaler off. The readbacks waited instead: 2,000-2,500 of 3,000 against ~900.On "GPU-bound wait 0.4 ms": that figure counts only waits for a free recording slot (the log says
(recording slots only)).GPU timelines read 46-55 ms of GPU a frame at 1280x800 native, and 53-59 ms at the player's 1536x864.