mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Keep the 240 ceiling while RivaTuner Statistics Server holds the limit, and take the limit back after 200 ms with no host present.
frame_step_cap_ns returned 0 once RTSS held the limit, so pace() set each slot to now and the acquire never waited. RTSS holds frames inside the host present, and a minimised window (gfx_present.c present_image skips the present) or a frame hold gives it none to hold: the game then ran with no limiter but pace_smooth's floor, which follows the intervals down, and the quick menu is where 640-700 fps read address 0 (frame_rate.c LIMIT_FREE_MAX_HZ). gfx_present.c flip_locked now stores the time of each host present that took its picture (gfx_window_hosted_ns), and rtss_decide.h rtss_pace_period gives the pacer the limit's period before the first host present and once none went out for 200 ms (as vk_lowlat.c HOLD_IDLE_NS), and the 240 ceiling's period, never 0, while RTSS holds. The frames RTSS lets through come further apart than the ceiling, so pace() waits for none of them; no new wait site. Not reproduced before the change. frame_step_check (new cases: holding with a host present 10 and 199 ms ago, 250 ms ago, none yet, not holding, 144 minimised, unlimited) and rtss_limit_check (rtss_pace_period) pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -274,8 +274,10 @@ double frame_step_game_hz(void) {
|
||||
return !frame_free_running() && !step_word && hz > 60 ? 60 : hz > LIMIT_MAX_HZ ? LIMIT_MAX_HZ : hz;
|
||||
}
|
||||
|
||||
// RivaTuner Statistics Server (rtss_limit.c) gets the same limit; while it limits this process the pacer holds no
|
||||
// frames, so the two never stack. NX2NAVI_RTSS=0 and frame generation (RTSS would count the generated frames) keep it
|
||||
// RivaTuner Statistics Server (rtss_limit.c) gets the same limit; while it limits this process the pacer holds the
|
||||
// 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. 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.
|
||||
@@ -328,8 +330,8 @@ uint64_t frame_step_cap_ns(void) {
|
||||
if (movie_hz) hz = movie_hz;
|
||||
paced_hz = hz;
|
||||
rtss_limit_want(rtss_out ? rtss_out : (int64_t)(hz * 1000 + 0.5));
|
||||
if (rtss_limit_active()) return 0;
|
||||
return (uint64_t)(1e9 / hz + 0.5);
|
||||
return rtss_pace_period(rtss_limit_active(), kern_now_ns(), gfx_window_hosted_ns(), (uint64_t)(1e9 / hz + 0.5),
|
||||
(uint64_t)(1e9 / LIMIT_FREE_MAX_HZ + 0.5));
|
||||
}
|
||||
|
||||
// The limit as it is set, for the performance overlay: the setting, the monitor's rate for "display", 60 at
|
||||
|
||||
@@ -26,6 +26,7 @@ void gfx_vsync_bind(uint64_t system_event); // nn::vi vsync event
|
||||
uint64_t gfx_vsync_ticks(void); // ticks since start
|
||||
uint64_t gfx_vsync_time_ns(uint64_t tick); // kern_now_ns() time of a tick (pacing)
|
||||
double gfx_window_refresh_hz(void); // refresh rate of the window's monitor; 0 unknown
|
||||
uint64_t gfx_window_hosted_ns(void); // kern_now_ns() of the newest host present; 0 before one
|
||||
int gfx_window_focused(void);
|
||||
// gfx_present.c: who holds the present, queue and window-start locks, read without taking them (the hang watch)
|
||||
typedef struct { uint32_t present, present_depth, queue, queue_depth, start; } GfxLockHolders;
|
||||
|
||||
@@ -439,6 +439,12 @@ static VkPresentModeKHR pick_present_mode(VkPhysicalDevice pd) {
|
||||
|
||||
const char *gfx_window_present_mode(void) { return __atomic_load_n(&g_present_got, __ATOMIC_ACQUIRE); }
|
||||
|
||||
// kern_now_ns when the newest host present returned with its picture taken (flip_locked); 0 before the first. A
|
||||
// minimised window and a frame hold present nothing, so it ages: RTSS, which holds the frames inside the host present,
|
||||
// then holds none, and the game's pacer takes the limit back (rtss_decide.h rtss_pace_period).
|
||||
static uint64_t g_hosted_ns;
|
||||
uint64_t gfx_window_hosted_ns(void) { return __atomic_load_n(&g_hosted_ns, __ATOMIC_ACQUIRE); }
|
||||
|
||||
// Queues the present of a blit vk_present_blit left waiting (g_pl held). A swapchain rebuild and the next blit flush it
|
||||
// first; the device release drops it.
|
||||
static void flip_locked(void) {
|
||||
@@ -459,6 +465,7 @@ static void flip_locked(void) {
|
||||
: vk_fg_active() ? vk_fg_present(&pi) : vkQueuePresentKHR(vk_ctx()->gfx_queue, &pi);
|
||||
vk_queue_unlock();
|
||||
vk_lowlat_present_end(&ll);
|
||||
if (r == VK_SUCCESS || r == VK_SUBOPTIMAL_KHR) __atomic_store_n(&g_hosted_ns, kern_now_ns(), __ATOMIC_RELEASE);
|
||||
vk_device_lost_check(r, "the present");
|
||||
if (r == VK_ERROR_OUT_OF_DATE_KHR || r == VK_SUBOPTIMAL_KHR) plat_xchg32(&g_dirty, 1);
|
||||
}
|
||||
|
||||
@@ -211,6 +211,23 @@ static inline void rtss_why_text(char *out, size_t n, const RtssOut *o, int fail
|
||||
else snprintf(out, n, "RivaTuner Statistics Server has not hooked the game yet");
|
||||
}
|
||||
|
||||
// ---- the game's pacer beside RTSS (frame_rate.c frame_step_cap_ns)
|
||||
#define RTSS_HOLD_IDLE_NS 200000000ull // no host present for this long: RTSS holds nothing (vk_lowlat.c HOLD_IDLE_NS)
|
||||
|
||||
// The period the pacer keeps (now_ns and hosted_ns are kern_now_ns times; hosted_ns of the newest host present,
|
||||
// gfx_present.c gfx_window_hosted_ns, 0 before the first). The limit's own period while RTSS does not hold the game,
|
||||
// before the first host present, and once no host present went out for RTSS_HOLD_IDLE_NS: a minimised window or a
|
||||
// frame hold (vk_backend.c frame_hold) gives RTSS nothing to hold, and a pacer that held nothing then let the quick menu
|
||||
// run at the 640-700 fps that read address 0 (frame_rate.c LIMIT_FREE_MAX_HZ). While RTSS holds, the ceiling's period
|
||||
// and never 0: the frames RTSS lets through come further apart than that, so pace() waits for none of them, and it
|
||||
// still holds the game's frames to the ceiling the game's code needs.
|
||||
static inline uint64_t rtss_pace_period(int holds, uint64_t now_ns, uint64_t hosted_ns, uint64_t period,
|
||||
uint64_t ceiling) {
|
||||
uint64_t idle = now_ns > hosted_ns ? now_ns - hosted_ns : 0;
|
||||
if (!holds || !hosted_ns || idle >= RTSS_HOLD_IDLE_NS) return period;
|
||||
return ceiling < period ? ceiling : period;
|
||||
}
|
||||
|
||||
// ---- RTSS's own settings
|
||||
// RTSS's own [Framerate] settings for the game, as its profile file holds them (rtss_limit.c read_settings): a frame
|
||||
// rate limit (Limit over LimitDenominator), a frame time limit (LimitTime over LimitTimeDenominator) and scanline sync
|
||||
|
||||
@@ -39,7 +39,10 @@ int vk_dlssg_multiplier(void) { return fg_mult; }
|
||||
int vk_dlssg_active(void) { return fg_mult > 1; }
|
||||
int vk_lowlat_on(void) { return 0; }
|
||||
void rtss_limit_want(int64_t millihz) { (void)millihz; }
|
||||
int rtss_limit_active(void) { return 0; }
|
||||
static int rtss_holds; // RivaTuner Statistics Server holds the game to the want (rtss_limit.c)
|
||||
static uint64_t kern_ns, hosted; // kern_now_ns, and its time at the newest host present (0: none yet)
|
||||
int rtss_limit_active(void) { return rtss_holds; }
|
||||
uint64_t gfx_window_hosted_ns(void) { return hosted; }
|
||||
// The time manager the sound job's gate reads its step from (frame_rate.c sound_job_step), while the sound checks run:
|
||||
// [main+0x4c7e890] holds the slot, the slot the manager, the manager +0x10 the step. Nothing else reads.
|
||||
static int fake_manager;
|
||||
@@ -67,7 +70,7 @@ int mem_copy_readable(uint8_t *const *dst, const uint64_t (*src)[2], int n, uint
|
||||
(void)dst, (void)src, (void)n, (void)ok;
|
||||
return 0;
|
||||
}
|
||||
uint64_t kern_now_ns(void) { return 0; }
|
||||
uint64_t kern_now_ns(void) { return kern_ns; }
|
||||
// the floor watch (off here)
|
||||
int floor_watch_wanted(void) { return 0; }
|
||||
int floor_watch_found(const uint64_t *addr, const float *val, const int *unit, int n, const float link[3]) {
|
||||
@@ -280,6 +283,29 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
fg_mult = 1;
|
||||
|
||||
// RivaTuner Statistics Server holds the limit: the pacer keeps the 240 ceiling while host presents go out, and the
|
||||
// limit's own period before the first host present and once none went out for 200 ms (a minimised window, a frame
|
||||
// hold), when RTSS has nothing to hold.
|
||||
{
|
||||
static const struct { const char *l; int holds; uint64_t hosted_ago; double hz; const char *what; } cases[] = {
|
||||
{"60", 1, 10000000, 240, "RTSS holds, a host present 10 ms ago: the ceiling alone"},
|
||||
{"60", 1, 199000000, 240, "199 ms ago: still RTSS's"},
|
||||
{"60", 1, 250000000, 60, "250 ms ago: the limit back"},
|
||||
{"60", 1, 0, 60, "no host present yet: the limit"},
|
||||
{"60", 0, 10000000, 60, "RTSS does not hold: the limit"},
|
||||
{"144", 1, 250000000, 144, "144, minimised: 144"},
|
||||
{"unlimited", 1, 10000000, 240, "unlimited, RTSS holds: the ceiling is the limit"}};
|
||||
kern_ns = 50000000000ull;
|
||||
for (unsigned i = 0; i < sizeof cases / sizeof *cases; i++) {
|
||||
set_limit(cases[i].l);
|
||||
rtss_holds = cases[i].holds;
|
||||
hosted = cases[i].hosted_ago ? kern_ns - cases[i].hosted_ago : 0;
|
||||
double hz = 1e9 / (double)frame_step_cap_ns();
|
||||
CHECK(fabs(hz - cases[i].hz) < 0.01, "%s: paces at %.3f fps, not %.0f", cases[i].what, hz, cases[i].hz);
|
||||
}
|
||||
rtss_holds = 0, hosted = kern_ns = 0;
|
||||
}
|
||||
|
||||
// The water's ripples at 144 fps, a step of 60/144 frame: 64 water objects asked once a frame each run 600 steps
|
||||
// in 1440 frames (the cut gave none), and the day's seconds asked between them keep their own fraction.
|
||||
{
|
||||
|
||||
@@ -380,6 +380,19 @@ static void two(void) {
|
||||
CHECK(!rtss_two(60000, RTSS_FAIL_NOT_LIMITING, 1, 1), "RTSS seen not to hold the game: not counted");
|
||||
}
|
||||
|
||||
// The period the game's pacer keeps beside RTSS: the ceiling while RTSS holds and host presents go out, the limit's own
|
||||
// otherwise; never 0.
|
||||
static void pace_period(void) {
|
||||
const uint64_t p60 = 16666667, ceiling = 4166667, now = 50000000000ull;
|
||||
CHECK(rtss_pace_period(1, now, now - 10000000, p60, ceiling) == ceiling, "holds, a host present 10 ms ago");
|
||||
CHECK(rtss_pace_period(1, now, now - RTSS_HOLD_IDLE_NS + 1, p60, ceiling) == ceiling, "just under 200 ms");
|
||||
CHECK(rtss_pace_period(1, now, now - RTSS_HOLD_IDLE_NS, p60, ceiling) == p60, "200 ms with no host present");
|
||||
CHECK(rtss_pace_period(1, now, 0, p60, ceiling) == p60, "no host present yet");
|
||||
CHECK(rtss_pace_period(0, now, now - 10000000, p60, ceiling) == p60, "RTSS does not hold");
|
||||
CHECK(rtss_pace_period(1, now, now + 1000, p60, ceiling) == ceiling, "a host present stored after now was read");
|
||||
CHECK(rtss_pace_period(1, now, now - 10000000, ceiling, ceiling) == ceiling, "a limit at the ceiling");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
settings();
|
||||
reasons();
|
||||
@@ -394,6 +407,7 @@ int main(void) {
|
||||
movie();
|
||||
unset_and_library();
|
||||
two();
|
||||
pace_period();
|
||||
printf("rtss_limit_check: %s\n", fails ? "FAIL" : "ok");
|
||||
return fails != 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user