mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 17:09:57 -04:00
Describe RivaTuner Statistics Server's hand-off, the pacer's fallbacks and rtss-profile.txt as they behave now.
docs/architecture.md said the pacer holds nothing while RTSS holds the limit, through movies and minimised windows, that the pacer stands aside once RTSS lists the process, that RTSS fails at 1.2 times the limit over six polls, and that a killed run leaves its limit for the next run to take as the player's. The one-limiter and RTSS paragraphs now name the pacer's fallbacks (before the first host present, a movie, 200 ms with no host present, a limiter seen letting frames through, any failure) and its 240 ceiling beside RTSS, the settle rule, a movie's profile, the proof RTSS must give before the pacer stands aside, the 1.05 rule over three new periods, the rounding down, and the state file with rtss_limit_crash. nx2navi.ini.example's rtss lines, README.md's frame generation item, docs/building.md's rtss_limit_check and frame_step_check paragraphs and rtss_limit.c's header follow, and the comments at main.c crash_handler's and dispatch.c fatal_end's rtss_limit_crash say when RTSS's library is still called there (no line in the state file, never on RTSS's own thread). No code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,8 +38,10 @@ Report problems as issues, following [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
display; in a window of another shape than the picture, frames are generated inside the picture only, so the black
|
||||
bars stay clean. The frame limit counts the frames the game draws, and frame generation adds its
|
||||
frames on top of it. The frame limit can also be
|
||||
handed to RivaTuner Statistics Server, which then limits the game in its place; where frame generation or low
|
||||
latency keeps it out, the game says when a limit set in RivaTuner itself holds the game as well
|
||||
handed to RivaTuner Statistics Server, which then limits the game in its place: one limiter at a time, and the
|
||||
game takes the limit back while a movie plays, while it is minimised and whenever RivaTuner is not seen holding it;
|
||||
where frame generation or low latency keeps it out, the game says when a limit set in RivaTuner itself holds the
|
||||
game as well
|
||||
- HDR on HDR displays, two kinds: the game's scene tone mapped to the display's peak, matched to the game's mid grey and tone, with true blacks, or the game's own native HDR; peak, game and HUD brightness in the menu
|
||||
- The game's known shaders prepared when the game is built, and the pipelines met in play built again in the
|
||||
background at each start, so play rarely stops to compile them; a shader or pipeline met for the first time can
|
||||
|
||||
@@ -257,11 +257,15 @@ keeps per-core data without locks and relies on that, so the host models it (`ru
|
||||
3%.
|
||||
- One limiter is the goal: the driver's low latency (`vk_lowlat.c`), RivaTuner Statistics Server (`rtss_limit.c`) or
|
||||
the game's pacer holds the frame limit, and the log's "frame rate:" and "lowlat:" lines name the one in charge.
|
||||
Frame generation keeps RTSS out (it would count the generated frames), and the game's pacer holds the drawn frames
|
||||
beside it. The exceptions as they stand: `pace_smooth` still waits on the present thread while RTSS holds the limit;
|
||||
DLSS Frame Generation's Reflex sleeps where the frame starts while the pacer holds the limit; while RTSS holds it the
|
||||
pacer holds nothing at all, through movies and while no picture goes out (a minimised window, `frame_hold`) too; and
|
||||
a limit set in RTSS itself still holds the game where the game keeps RTSS out, which the log ("two limiters") and the
|
||||
The pacer is the fallback wherever the other limiter's hold is not proven: before the first host present, while a
|
||||
movie plays (the pacer keeps the movie's slots, which no other limiter knows), once no picture went out for 200 ms (a
|
||||
minimised window, `frame_hold`), for the rest of the run once the frames show the other limiter letting them through
|
||||
faster than the limit, and after any failure. While RTSS holds the limit the pacer keeps only the 240-frame ceiling
|
||||
the game's code needs (`rtss_decide.h` `rtss_pace_period`). Frame generation keeps RTSS out (it would count the
|
||||
generated frames), and the game's pacer holds the drawn frames beside it. The exceptions as they stand: `pace_smooth`
|
||||
still waits on the present thread while RTSS holds the limit; DLSS Frame Generation's Reflex sleeps where the frame
|
||||
starts while the pacer holds the limit; and a limit set in RTSS itself still holds the game where the game keeps RTSS
|
||||
out, which the log ("two limiters") and the
|
||||
frame rate row of the menu point out, and the same where RTSS is out of it for the run because its profile cannot be
|
||||
written or its hooks library does not load, which the log points out the same way (the menu shows the administrator
|
||||
note for the first and nothing for the second).
|
||||
@@ -269,19 +273,29 @@ keeps per-core data without locks and relies on that, so the host models it (`ru
|
||||
first present starts a thread that looks RTSS up (`InstallDir` under `HKLM\SOFTWARE\WOW6432Node\Unwinder\RTSS`,
|
||||
`RTSSHooks64.dll`) and reads its shared memory (`RTSSSharedMemoryV2`) every 500 ms. While RTSS runs, the limit goes
|
||||
into RTSS's profile for the executable (`Profiles\<exe>.cfg`, through the hooks library's profile calls, read back
|
||||
from the saved file; a fractional rate over a denominator where RTSS knows one, else rounded to whole frames), and
|
||||
once RTSS lists the process the pacer stands aside (`rtss_limit_active`). The pacer keeps the limit while RTSS does
|
||||
not run or has not hooked the game, for a limit RTSS refuses, and for the rest of the run when the profile cannot be
|
||||
written (no administrator rights), the hooks library does not load, or RTSS's own frame count of the game stays
|
||||
above 1.2 times the limit for six polls; the menu names the first and the last of these. Frame generation, the
|
||||
from the saved file; a fractional rate over a denominator where RTSS knows one, else rounded down to whole frames)
|
||||
once the first picture went out and the limit stayed the same for two polls, so a want that flips (low latency at a
|
||||
swapchain rebuild) writes nothing. A movie leaves the play limit in the profile; only a play limit below the movie's
|
||||
rate gives the profile no limit for the movie's length, so that RTSS does not space out the frames the pacer sends at
|
||||
once to catch up. The pacer stands aside (`rtss_limit_active`) only once RTSS is seen holding the game: it lists the
|
||||
process with its Vulkan presents, has read the profile (its update flag clear) and measured a period of the game's
|
||||
frames lately, no movie plays and a picture went out within 200 ms. The pacer keeps the limit while RTSS does not run
|
||||
or has not hooked the game, for a limit RTSS refuses, and for the rest of the run when the profile cannot be written
|
||||
(no administrator rights), the hooks library does not load, or RTSS's own frame count of the game is above 1.05
|
||||
times the limit in three new periods in a row (a period RTSS no longer moves counts neither way); the menu names the
|
||||
first and the last of these. Frame generation, the
|
||||
driver's low latency and `NX2NAVI_RTSS=0` keep RTSS out (`RTSS_OUT_*`): the profile gets back what it held before
|
||||
the run changed it, and while RTSS is kept out its thread does not load the hooks library and only reads RTSS's own
|
||||
settings for the game; a later limit (frame generation or low latency switched off) loads it and hands RTSS the limit
|
||||
as usual, and `NX2NAVI_RTSS=0` keeps the thread read-only for the run. While RTSS lists the game the log names those
|
||||
settings (limit, frame time limit, scanline sync, display sync, Reflex sleep, passive wait) once per change. Every
|
||||
orderly exit puts the profile back as it was before the run (`rtss_limit_restore`), and no want reaches it after
|
||||
that; a kill, the device-lost watchdog or a crash inside the crash handler leaves the game's limit in the profile,
|
||||
and the next run takes it for the player's own.
|
||||
that. What the profile held is also kept in `rtss-profile.txt` next to the executable, one line per profile, written
|
||||
before the run's first change of it and removed once it is back: a start that finds a line left by a run that did
|
||||
not close (a kill, a hang, the device-lost watchdog, a crash or a fatal error, whose ends make no call into RTSS's
|
||||
library: `rtss_limit_crash`) puts that profile back first, unless the player changed it since. A file that cannot be
|
||||
written leaves the run as before, with a line in the log; profiles that versions before the file left with the
|
||||
game's limit cannot be told from the player's own.
|
||||
- Measuring pacing: `NX2NAVI_TRACE=present` logs a line per picture (the host present's interval, the game's present
|
||||
interval, the wait for the slot and how far past it the present went), `NX2NAVI_TRACE=perf` each wait's share of the
|
||||
frame ("bound:"), and `tools/perf_summary.py` condenses both. Both measure before the host present, so a wait RTSS
|
||||
|
||||
@@ -469,7 +469,9 @@ A change to the step Ocarina is given each frame (`runtime/frame_rate.c` `ocarin
|
||||
game's counters get out of it (`whole_take`, `frame_rate_day_host`, `frame_rate_water_host`, `frame_rate_sim_host`), passes
|
||||
`python tests\frame_step_check.py`, which builds `frame_rate.c` with the rest of the runtime stubbed and feeds it
|
||||
display times: a held 60 fps limit gives exactly one frame every frame, a 240 fps limit never less than a quarter
|
||||
frame, uneven frame times one steady step, and game time follows the clock. It calls the two hooks as their patch
|
||||
frame, uneven frame times one steady step, and game time follows the clock. While RivaTuner Statistics Server holds
|
||||
the limit the pacer keeps the 240 ceiling, the limit's own period once no host present went out for 200 ms, and a
|
||||
movie's rate. It calls the two hooks as their patch
|
||||
sites do: at 144 fps each of 64 water objects gets its ripple steps and the day its seconds, at 120, 144 and 240 fps
|
||||
the water managers and floors of the set `owater` get 60 fixed steps a second with only s0 changed, and a step of one
|
||||
frame gives what the game computes. It also checks that each of those sites in `recomp/patches-v111.txt` ends in one
|
||||
@@ -483,13 +485,20 @@ velocity at the time scales where the game cuts it at 60 fps and nowhere else. N
|
||||
A change to how the frame limit goes to RivaTuner Statistics Server (`runtime/rtss_limit.c`, `runtime/rtss_decide.h`)
|
||||
passes `python tests\rtss_limit_check.py`. It builds `rtss_decide.h` alone and checks its reader of RTSS's shared
|
||||
memory against a fake `RTSSSharedMemoryV2` (a bad header, short entries, an entry past the memory or past the 4096th
|
||||
find nothing) and the worker's decisions poll by poll against a fake profile: RTSS's injection delay, RTSS quitting and
|
||||
coming back, a read-only profile, a refused limit, the frame count rule, kept-out wants and their reasons, low latency
|
||||
switching at every poll, a movie, a closed run, a hooks library that does not load, and two limiters. On Windows it
|
||||
also builds the real `rtss_limit.c` against a fake RTSS (its registry key, hooks library, profile files and shared
|
||||
memory) and runs each scenario in a process of its own, among them a want after the exit's restore and one the worker
|
||||
read before it, a worker polling without pause while the want switches, and the registry and file calls a present
|
||||
must not make. No GPU, no RTSS, no game files.
|
||||
find nothing; the flags and the period where RTSS keeps them) and the worker's decisions poll by poll against a fake
|
||||
profile and a fake RTSS clock: RTSS's injection delay through listed with no API, the profile not read yet and no
|
||||
period measured, RTSS quitting and coming back, a read-only profile, a refused limit, the frame count rule (1.05 times
|
||||
the limit over three new periods, a frozen period never counted), no picture going out, kept-out wants and their
|
||||
reasons, a want that must stay two polls and low latency switching at every poll, a movie at 144 and at 40, a closed
|
||||
run, a hooks library that does not load, and two limiters. It also checks the pacer's period beside RTSS
|
||||
(`rtss_pace_period`), the limit's rounding without a denominator, and the state file `rtss-profile.txt`: its lines,
|
||||
its rewrite and what a start does with a line a run that did not close left. Statically, a crash and a fatal error
|
||||
(`main.c` `crash_handler`, `dispatch.c` `fatal_end`) go through `rtss_limit_crash`. On Windows it also builds the real
|
||||
`rtss_limit.c` against a fake RTSS (its registry key, hooks library, profile files and shared memory) and runs each
|
||||
scenario in a process of its own, among them a want after the exit's restore and one the worker read before it, a
|
||||
worker polling without pause while the want switches, the registry and file calls a present must not make, and a run
|
||||
killed while RTSS held its limit followed by the next start, each in a process of its own. No GPU, no RTSS, no game
|
||||
files.
|
||||
|
||||
A change to how the mixer applies volumes (`runtime/audio_mixer.c` `accumulate_ramp`, the voice and splitter paths
|
||||
of `audio_mixer_render`) or to what it owes the device after a stall (`ring_pull`, `audio_mixer_owed`) passes
|
||||
|
||||
@@ -24,14 +24,15 @@
|
||||
# game draws the monitor's rate divided by the multiplier.
|
||||
# fps_limit = 60
|
||||
# Where RivaTuner Statistics Server is installed and running, the limit goes into its profile for the game's
|
||||
# executable and RTSS does the limiting instead of the game; closing the game puts back what the profile held.
|
||||
# Changing that profile needs the game run as administrator (RTSS keeps its profiles under Program Files). Without it
|
||||
# the game limits by itself, as it does with frame generation on (RTSS would count the generated frames) and for the
|
||||
# rest of the run when RTSS's own frame counter shows the game well past the limit (RTSS then hooks the game without
|
||||
# holding it back); with low latency on, the driver holds the limit. rtss = 0 always leaves it to the game. With
|
||||
# rtss = 0, frame generation or low latency, a limit set in RTSS itself (its global profile or the game's, a frame time
|
||||
# limit or scanline sync) limits the game a second time: the log and the settings menu say so, and setting it to 0 in
|
||||
# RTSS leaves one limiter.
|
||||
# executable and RTSS does the limiting instead of the game, once RTSS is seen holding the game to it; closing the game
|
||||
# puts back what the profile held, and after a crash or a kill the next start does (rtss-profile.txt next to the game
|
||||
# keeps what it held). Changing that profile needs the game run as administrator (RTSS keeps its profiles under Program
|
||||
# Files). Without it the game limits by itself, as it does with frame generation on (RTSS would count the generated
|
||||
# frames), while a movie plays or the window is minimised, and for the rest of the run when RTSS's own frame counter
|
||||
# shows the game past the limit (RTSS then hooks the game without holding it back); with low latency on, the driver
|
||||
# holds the limit. rtss = 0 always leaves it to the game. With rtss = 0, frame generation or low latency, a limit set in
|
||||
# RTSS itself (its global profile or the game's, a frame time limit or scanline sync) limits the game a second time:
|
||||
# the log and the settings menu say so, and setting it to 0 in RTSS leaves one limiter.
|
||||
# rtss = 1
|
||||
# 1: asks RTSS's EnumProfiles whether the game's profile exists, instead of looking for its file. EnumProfiles crashes
|
||||
# the game (RTSS keeps a pointer of the game's in 32 bits); only for testing an RTSS that fixed it.
|
||||
|
||||
@@ -123,8 +123,8 @@ static void fatal_end(int code, const char *fmt, va_list ap) {
|
||||
fs_save_crash_wait(); // a save the game already reported as done finishes first (bounded)
|
||||
prof_sampler_write(); // NX2NAVI_PROFILE: what was sampled until now, else a crash loses the whole profile
|
||||
// RivaTuner Statistics Server's profile goes back at the next start, from the line the run keeps for it in
|
||||
// rtss-profile.txt (rtss_limit.c rtss_limit_crash, as in main.c crash_handler): no call into RTSS's library here,
|
||||
// from any thread
|
||||
// rtss-profile.txt (rtss_limit.c rtss_limit_crash, as in main.c crash_handler), with no call into RTSS's library
|
||||
// here; only where that line could not be written is it put back now, and never from RTSS's own thread
|
||||
rtss_limit_crash();
|
||||
rt_rumble_stop_bounded(); // a controller on HD rumble: nothing renews or stops its vibration after the end
|
||||
// "frametime: at a fatal error" and the stutter map, the session's last period: after what a kill of the process
|
||||
|
||||
@@ -278,10 +278,10 @@ double frame_step_game_hz(void) {
|
||||
// frames only to LIMIT_FREE_MAX_HZ, the ceiling the game's code needs, so the two never stack, and it takes the limit
|
||||
// back once no picture went out for 200 ms (rtss_decide.h rtss_pace_period): a minimised window or a frame hold leaves
|
||||
// RTSS no present to hold. While a movie plays the pacer holds the movie's rate and keeps its slots whatever RTSS does.
|
||||
// NX2NAVI_RTSS=0 and frame generation (RTSS would count the generated frames) keep it
|
||||
// out, and so does a driver's low latency while it is in effect (vk_lowlat.c): the driver holds the limit where the
|
||||
// frame starts, and RTSS would hold the finished picture, the wait that mode is there to remove. RTSS gets the reason
|
||||
// (RTSS_OUT_*), for the log and for its check of a limit set in RTSS itself.
|
||||
// NX2NAVI_RTSS=0 and frame generation (RTSS would count the generated frames) keep it out, and so does a driver's low
|
||||
// latency while it is in effect (vk_lowlat.c): the driver holds the limit where the frame starts, and RTSS would hold
|
||||
// the finished picture, the wait that mode is there to remove. RTSS gets the reason (RTSS_OUT_*), for the log and for
|
||||
// its check of a limit set in RTSS itself.
|
||||
static double paced_hz; // the game frames a second the newest frame_step_cap_ns paces at (ocarina_step); 0: none
|
||||
static int movie_paced; // the movie rate the newest frame_step_cap_ns paces at (ocarina_step, frame_step_movie_hz); 0: none
|
||||
int frame_step_movie_hz(void) { return movie_paced; }
|
||||
|
||||
@@ -437,7 +437,8 @@ static void crash_handler(const PlatCrash *x) {
|
||||
kern_free_cores_note();
|
||||
fs_save_crash_wait(); // a save the game already reported as done finishes first (bounded; fs_host.c)
|
||||
// after the report: RivaTuner Statistics Server's profile goes back at the next start, from the line the run keeps
|
||||
// for it (rtss_limit.c rtss_limit_crash): no call into RTSS's library from a process that failed
|
||||
// for it in rtss-profile.txt (rtss_limit.c rtss_limit_crash), with no call into RTSS's library from a process that
|
||||
// failed; only where that line could not be written is it put back here, and never from RTSS's own thread
|
||||
rtss_limit_crash();
|
||||
rt_rumble_stop_bounded(); // a controller on HD rumble stops (dispatch.c: 300 ms at most, SDL may be the crash)
|
||||
fflush(stderr);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// RivaTuner Statistics Server as the frame limiter (frame_rate.c frame_step_cap_ns): where RTSS is installed and running,
|
||||
// the frame limit goes into RTSS's profile for this executable and the game's own pacer holds no frames, so there is
|
||||
// one limiter. Anywhere else, or when any step fails, the game's own limiter works as it always did.
|
||||
// the frame limit goes into RTSS's profile for this executable and, while RTSS is seen holding the game to it, the
|
||||
// game's own pacer keeps only the 240-frame ceiling (rtss_decide.h rtss_pace_period), so there is one limiter.
|
||||
// Anywhere else, or when any step fails, the game's own limiter works as it always did.
|
||||
//
|
||||
// RTSS's hooks library (RTSSHooks64.dll in the folder its installer records under
|
||||
// HKLM\SOFTWARE\WOW6432Node\Unwinder\RTSS, InstallDir) exports the profile calls, loaded here at run time:
|
||||
|
||||
Reference in New Issue
Block a user