Repository navigation
Headless, the layer's frame history shows a frame at the vsync a display would give it: Star Fox's cinematic throttle no longer sleeps out its own frame - #2339
Draft
elkir0 wants to merge 1 commit into
Conversation
…nc a display would show it at, not when the history first saw it handed on: Star Fox's frame throttle (vthrottle) sleeps out each frame for out.desired_frame_time plus a predictive delta (main+0xb447210, one SleepThread in main+0xba3b0f0) that steers the time from a frame's GPU end to its flip, and reads the flip from nn::vi::ListFrameHistory at the start of each frame (main+0xb75d06c), right after its own sleep. With no Metal presented time (headless, or before the handler runs) hle_vi.c gave such a frame the time the history first saw it, which was that read, so the game took its own sleep for slack before the flip and held its delta at about +1.3 ms: its prologue cinematic (present interval 2) ran at 34.3 ms a frame and never waited at the acquire, the GPU and the CPU mostly idle (a host sample had its input, sim and output stage threads in SleepThread 86-99% of the time). hle_vi.c now shows such a frame at the first tick of the present pacer's grid (its target, kept while a frame is late by less than a period; one tick a vsync of the present tick rate) at or after both its present and the end of its GPU work (mtl_gpu_done in nvn_mtl.m, on the frame's GPU tag, one less than its history number), and not before that tick or while that work runs; hle_nvn.c tells the history of each present after the pacer has run, with the present time, the pacer's target and the tick, and none under DM_DETCLOCK, where the first-seen time stays; a Metal presented time still wins. Measured headless on a base M4 (10-core GPU, 16 GB), one build on f75ac06 run by its host and by this one, the cinematic from 95 to 195 s: 27.7 -> 28.7 fps, frame p50 34.3 -> 33.4 ms, frames under 60 ms 34.23 -> 33.18 ms (29.2 -> 30.1 fps); the rest are scene cuts (24 and 23 frames of 60 ms or more), first texture uploads in the submission and the game's own loading. On 6a6a018 (768x432 docked) 28.4-28.9 -> 29.1-30.0 fps per 15 s window. The title stays at 60 fps; the ring course 59.3 fps with f75ac06's host, and with the change on 6a6a018 59.5-60.0 fps per 30 s window, p95 17.38 ms (59.4-59.9, p95 18.0-19.1 ms without). t_vi_history gets the virtual vsync's cases (an early present, a late one with a later GPU end, GPU work still running, no GPU record, the frame one less, a presented time winning, the guest clock; three injected bugs each fail it), t_host_stubs.c's weak stub takes the new arguments; the gfx suite (165 programs) and the host suite (50) pass for Star Fox, as do test_docs. regress.py impact: every NVN game through nvnQueuePresentTexture. Not run: regress.py check (only Star Fox on this Mac); a window; the other games that read the history (Minecraft Dungeons II, Octopath Traveler 0 and II, Luigi's Mansion 3), which now see a frame shown only once its GPU work has ended and its vsync has come Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Headless, a frame the layer's frame history lists is now shown at the vsync a display would show it at, not at the moment the history first saw it handed on. Star Fox's prologue cinematic goes from 27.7 to 28.7 fps, and its steady frames are back at 33.2 ms.
The problem
Star Fox's prologue cinematic (present interval 2, 30 fps by the game's choice) ran at 27.7 fps headless on a base M4. Its frames took 34.3 ms and never waited at the acquire. Both the GPU (about 11 ms a frame) and the CPU were mostly idle. A host sample during the cinematic had the game's
input stage,sim stageandoutput stagethreads asleep inSleepThread86–99% of the time. The 11 ms ofSyncWaita frame belonged to itsvgpu_timing_thread, not to the frame's critical path.Cause
The engine has a frame throttle (
vthrottle; settingsThrottle Slop Target MS,Throttle Predictive Frame Timing Factor, …). It sleeps out each frame on the output stage forout.desired_frame_timeplus a predictive delta:main+0xb447210and goes through the helpermain+0xba3b0f0, which callsSleepThreadonce;main+0xba3cfc0.The delta steers the "slop": the time from a frame's GPU end to the flip that showed it.
vgpu_timing_thread. They are moved to the CPU clock by an offset it measures again every 32 frames withnvnDeviceGetCurrentTimestampInNanoseconds.nn::vi::ListFrameHistory. The game reads it at the start of each frame (main+0xb75d06c), right after the throttle's sleep.Headless,
hle_vi.cgave a frame with no Metal presented time the time the history first saw it handed on, and that moment was this very read. The game counted its own sleep as slack before the flip. With no fixed vsync to push back, it held its delta at about +1.3 ms a frame.The change
dm/gfx/hle_vi.c. A frame with no presented time is shown at the first tick of the present pacer's grid at or after both its present and the end of its GPU work. The grid is the pacer's target, which it keeps while a frame is late by less than a period; one tick is one vsync of the present tick rate. The frame is not listed as shown before that tick has come, nor while its GPU work is still running. A Metal presented time still wins. UnderDM_DETCLOCK, which has no pacer grid, the first-seen time stays.dm/gfx/nvn/hle_nvn.c. The history is told of each present after the pacer has run, with the present time, the pacer's target and one tick.dm/gfx/nvn/nvn_mtl.m.mtl_gpu_done(frame)returns whether all of a frame's command buffers have completed, and their latest GPU end. The frame's GPU tag is one less than its history number.t_vi_historycovers the new cases, and the weak stub int_host_stubs.ctakes the new arguments.Layer frame historyentry's Fix; Star Fox's note.Measurements
Headless, on a base M4 (10-core GPU, 16 GB). One build on
f75ac06e, run by its own host and by this one. Prologue cinematic from 95 to 195 s:f75ac06eThe frames that remain over 60 ms are scene cuts. On an earlier build they were first texture uploads in the submission (140–400 MB in 41–132 ms) and frames of the game's own loading, and this change does not address them.
6a6a018d, before084f5aa5(768x432 docked): 28.4–28.9 → 29.1–30.0 fps per 15 s window.tutorial-flightroute): 59.3 fps (p95 17.44 ms) withf75ac06e's host. With this change on6a6a018d: 59.5–60.0 fps per 30 s window (p95 17.38 ms), against 59.4–59.9 fps (p95 18.0–19.1 ms) without it.Checks
t_vi_historyon two profiles. Its seven new cases:Three injected bugs each fail it: the frame offset, the rounding to the next vsync, and the wait for the tick.
make -C dm/gfx/tests GAME=0400E88025B1A000 run: 165 test programs, 0 failed.make -C dm/host/tests GAME=0400E88025B1A000 run: 50, 0 failed.t_nvn(298 checks) andt_fstep(78), both 0 failed.test_docspasses.regress.py impact upstream/main..HEAD: every NVN game throughnvnQueuePresentTexture. Breath of the Wild, Tears of the Kingdom and Pokémon Scarlet are named by their usage manifests.Not checked
regress.py checkhas not been run yet: Star Fox is the only game on the Mac this was made on. It will run on a second Mac (Mario Kart World and others), and its report will be added here. Draft until then.🤖 Generated with Claude Code