feat(menu): FPS counter levels: frame time, a graph and where the time goes #2181
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2181
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/fps-counter-levels"
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?
The FPS counter shows one number. It tells you the frame rate dropped, but not why, and not whether it was one long frame (a stutter) or a slow stretch. This turns the row into levels that add the frame time, a graph of the last few seconds and where each frame's time goes.
What changed
The Gameplay tab's FPS counter row now has five values, and a new FPS counter position row picks its corner. An options file from before keeps working: its
fps_counter=1(On) is FPS, the counter as it was.Like the counter, it has no plate: the same Dear ImGui foreground draw list, text with a soft shadow, tabular digits so the numbers don't jitter, text refreshed 4 times a second, sized with the Menu size and the window (checked at 720p, 900p and 4K). While the menu is open, the counter stays at the top right under the banner with the number and frame time only, as before, so it doesn't cover the menu.
Where the numbers come from
The parts are those of the game's presenting thread, present to present, and they add up to the frame time:
gpu/vk/vk_fps.cppFpsRender).st_wait_ns, already measured), the semaphore waits ofnvnSyncWaitand finish, the swapchain frame's fence, and OoT's own present poll for its frame's GPU work (itsSleepThreadat 0x80590520, timed by the host).vkQueuePresentKHR(vsync), and the Reflex / Anti-Lag 2 wait.gpu_time_collect). They are read when the recording's slot comes round again, so nothing waits for them and the number trails by a few frames.st_pipelines,st_pending_skips).The host gives the back end its sleeps through a new
MkwVkHost.slept_ns, timed only while Breakdown shows (MkwVkHost.fps_timing). That makes the API version 15 (main is 14). The control bridge #926 also takes 15 with other fields: whichever of #926 and this PR merges second needs one more bump. The ring of frames, averages, 1% low, graph columns and scale are plain functions ingpu/vk/fps_stats.h.Cost
Off and FPS measure nothing new: a relaxed load per present and per renderer entry, and a load of a flag per guest sleep in the host. Frame time and Graph read a clock once per present. Breakdown adds a TSC read at each renderer entry and around its waits, and the host times its sleeps. The graph is rebuilt from the frames at most 60 times a second, and the text is drawn in runs, not glyph by glyph, so the cost stays small even with the cap at Uncapped.
Measured on an RTX 3090 (driver 620.70), Linux, 2560x1440 window, Kokiri Forest standing still, frame rate cap 240 so the game is not paced, each run a fresh start, 45 s from the renderer's frame log (
RX_VK_FRAME_LOG), with nothing else on the GPU:(Measured before the self-review below, whose changes make the counter cheaper: fewer text draws, the graph rebuilt at most
60 times a second, the host's timers off below Breakdown.)
No measurable loss: runs of the same setting differ by more (other work on the PC) than the two settings do.
Screenshots
The renderer's own frames (headless runs with
RX_VK_SHOT_MENU=1), 1600x900 unless noted. Bright scene: Desert Colossus. Dark scene: Bottom of the Well.Breakdown at 1280x720 and at 3840x2160, and in the bottom-left corner:
(The frame rates in the shots are from a busy shared PC, and a frame that takes a screenshot is itself slow.)
Self-review
HOS_FPS_MAXover the row. Uncapped has no target line and grades colours against 60, and its graph scale starts at 4 ms.Tests
tests/test_fps_stats.py/tests/test_fps_stats.cpp: the levels and the old value's mapping, the corners, the split into parts, the ring, the 1% low, the summaries, the graph's columns and marks, the scale (a spike doesn't set it), the colour grades and the text. It also checks the wiring: the rows, a frame stamped at each present, the timers gated on Breakdown, and the host's sleeps.python -I -B tests/run_all.pyon Linux, after merging main: 357 passed. The 4 failures need packages not installed on this PC (zstandardfor two of them,tkinter), or are the libdecor hint check, which fails on main here too.github-sync referenced this pull request2026-11-12 06:22:54 +00:00