mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Tie RivaTuner Statistics Server's hold to the want it was decided for, so a stale store no longer keeps the pacer aside.
The worker checked that the want was still the one it had read and then stored active = 1, two steps on two atomics. A rtss_limit_want between them (frame generation or low latency switched on, a movie, a new limit) stored the new want and active = 0, and the worker's store then set active back to 1: frame_step_cap_ns returned 0 for the kept-out want until the next poll, up to 500 ms in which nothing limited the game. The worker now stores the want RTSS holds the game to (active_for, or RTSS_UNSET for none), and rtss_limit_active is true only while it equals the newest want, so a change of the want stops matching at once and rtss_limit_want no longer clears anything. The frame count check against the limit keeps its test that the want has not changed. tests/rtss_worker_check.c gains active_race: the worker polls without pause while the want switches 400000 times between 60 fps and frame generation, and after each switch to frame generation rtss_limit_active must read 0 until the worker has seen it. Before the change 128440 switches read 1; after it none do. The other scenarios and a compile of rtss_limit.c with -Wall pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,7 @@ enum { FAIL_NONE, FAIL_LIBRARY, FAIL_READONLY, FAIL_NOT_LIMITING };
|
||||
|
||||
static PlatMutex lock = PLAT_MUTEX_INIT; // the hooks calls and the state below
|
||||
static _Atomic int64_t want = RTSS_UNSET; // millihertz from the pacer: >0 a limit, 0 none; RTSS_OUT_* kept out
|
||||
static _Atomic int active; // RTSS limits this process now
|
||||
static _Atomic int64_t active_for = RTSS_UNSET; // the want RTSS holds the game to now; RTSS_UNSET none
|
||||
static _Atomic int failed; // FAIL_*: RTSS is out of it for the rest of the run
|
||||
static _Atomic int closed; // rtss_limit_restore ran: nothing reaches the profile any more
|
||||
static _Atomic int two; // kept out, and RTSS limits the game by a setting of its own (watch)
|
||||
@@ -229,10 +229,12 @@ static void put_back(void) {
|
||||
update_profiles();
|
||||
}
|
||||
|
||||
// Logs which limiter is in use: the first decision, then each change.
|
||||
static void set_active(int on, const char *why) {
|
||||
// Whether RTSS holds the game to the want w this poll read, and the log of which limiter is in use: the first decision,
|
||||
// then each change. The want may have changed since the poll read it: rtss_limit_active compares the two, so a stale
|
||||
// store never lets the game's limiter stand aside for a newer want.
|
||||
static void set_active(int64_t w, int on, const char *why) {
|
||||
static int logged = -1;
|
||||
atomic_store(&active, on);
|
||||
atomic_store(&active_for, on ? w : RTSS_UNSET);
|
||||
if (logged == on) return;
|
||||
logged = on;
|
||||
if (on) rt_log("frame rate: RivaTuner Statistics Server does the limiting; the game's own limiter stands aside");
|
||||
@@ -333,24 +335,24 @@ static void worker(void *arg) {
|
||||
if (dll) put_back();
|
||||
char why[96];
|
||||
snprintf(why, sizeof why, "RivaTuner Statistics Server kept out: %s", rtss_out_text(w));
|
||||
set_active(0, why);
|
||||
set_active(w, 0, why);
|
||||
} else if (f) {
|
||||
set_active(0, fail_reason(f));
|
||||
set_active(w, 0, fail_reason(f));
|
||||
} else if (!run) {
|
||||
set_active(0, "RivaTuner Statistics Server is not running");
|
||||
set_active(w, 0, "RivaTuner Statistics Server is not running");
|
||||
} else if (!load_hooks()) {
|
||||
atomic_store(&failed, FAIL_LIBRARY);
|
||||
set_active(0, fail_reason(FAIL_LIBRARY));
|
||||
set_active(w, 0, fail_reason(FAIL_LIBRARY));
|
||||
} else if (w == refused) {
|
||||
set_active(0, "RivaTuner Statistics Server refused this limit");
|
||||
set_active(w, 0, "RivaTuner Statistics Server refused this limit");
|
||||
} else if (w != applied) {
|
||||
int r = apply(w);
|
||||
over = 0;
|
||||
if (r < 0) atomic_store(&failed, FAIL_READONLY), set_active(0, fail_reason(FAIL_READONLY));
|
||||
else if (r == 0) set_active(0, "RivaTuner Statistics Server refused this limit");
|
||||
else if (atomic_load(&want) == w) set_active(hooked, "RivaTuner Statistics Server has not hooked the game yet");
|
||||
} else if (atomic_load(&want) == w) { // a newer limit waits for the next poll (rtss_limit_want)
|
||||
set_active(hooked, "RivaTuner Statistics Server has not hooked the game yet");
|
||||
if (r < 0) atomic_store(&failed, FAIL_READONLY), set_active(w, 0, fail_reason(FAIL_READONLY));
|
||||
else if (r == 0) set_active(w, 0, "RivaTuner Statistics Server refused this limit");
|
||||
else set_active(w, hooked, "RivaTuner Statistics Server has not hooked the game yet");
|
||||
} else if (atomic_load(&want) == w) { // a newer want waits for the next poll: these frames are not its
|
||||
set_active(w, hooked, "RivaTuner Statistics Server has not hooked the game yet");
|
||||
// RTSS's own count of the game's frames while the game's limiter stands aside: well above the limit for
|
||||
// three seconds means RTSS does not hold the game to it, and the game takes the limiting back for the run
|
||||
if (!hooked || w == 0 || fps <= w / 1000.0 * CHECK_SLACK) over = 0;
|
||||
@@ -359,7 +361,7 @@ static void worker(void *arg) {
|
||||
w / 1000.0);
|
||||
atomic_store(&failed, FAIL_NOT_LIMITING);
|
||||
put_back();
|
||||
set_active(0, fail_reason(FAIL_NOT_LIMITING));
|
||||
set_active(w, 0, fail_reason(FAIL_NOT_LIMITING));
|
||||
}
|
||||
}
|
||||
watch(w, run && hooked);
|
||||
@@ -401,12 +403,14 @@ void rtss_limit_want(int64_t mhz) {
|
||||
if (atomic_load_explicit(&closed, memory_order_relaxed)) return; // the exit put the profile back
|
||||
if (!started) start(mhz); // a first want that keeps RTSS out starts the worker too, which then only reads (watch)
|
||||
// a new limit: the game's own limiter holds it until the worker had RTSS apply it, up to POLL_MS later (a movie's
|
||||
// cap that waited for that ran the movie's 60 Hz count fast for half a second)
|
||||
if (atomic_exchange(&want, mhz) != mhz) atomic_store(&active, 0);
|
||||
// cap that waited for that ran the movie's 60 Hz count fast for half a second): active_for no longer matches
|
||||
atomic_store(&want, mhz);
|
||||
}
|
||||
|
||||
int rtss_limit_active(void) {
|
||||
return !atomic_load_explicit(&closed, memory_order_relaxed) && atomic_load_explicit(&active, memory_order_relaxed);
|
||||
if (atomic_load_explicit(&closed, memory_order_relaxed)) return 0;
|
||||
int64_t a = atomic_load_explicit(&active_for, memory_order_relaxed);
|
||||
return a >= 0 && a == atomic_load_explicit(&want, memory_order_relaxed);
|
||||
}
|
||||
|
||||
int rtss_limit_state(void) {
|
||||
|
||||
@@ -190,7 +190,9 @@ DWORD stub_GetCurrentProcessId(void) { return PID; }
|
||||
// ---- host.h and platform.h, as far as rtss_limit.c uses them
|
||||
static char logged_lines[1 << 16];
|
||||
static size_t logged_at;
|
||||
static int quiet; // the stress scenario: no lines kept or printed
|
||||
void rt_log(const char *fmt, ...) {
|
||||
if (quiet) return;
|
||||
char line[512];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
@@ -221,6 +223,8 @@ int plat_mutex_trylock(PlatMutex *m) { return TryAcquireSRWLockExclusive((PSRWLO
|
||||
|
||||
static PlatThreadFn worker_fn;
|
||||
static volatile LONG spawned;
|
||||
static volatile LONG free_run; // the worker polls without waiting
|
||||
static volatile LONG64 polls;
|
||||
static DWORD WINAPI worker_main(void *arg) {
|
||||
worker_tid = GetCurrentThreadId();
|
||||
worker_fn(arg);
|
||||
@@ -237,6 +241,8 @@ void plat_sleep_ms(uint32_t ms) {
|
||||
Sleep(ms);
|
||||
return;
|
||||
}
|
||||
InterlockedIncrement64(&polls);
|
||||
if (free_run) return;
|
||||
SetEvent(done); // a poll ended
|
||||
WaitForSingleObject(tick, INFINITE); // the next one starts when the scenario says
|
||||
}
|
||||
@@ -389,6 +395,35 @@ static void kept_framegen(void) {
|
||||
CHECK(!loads && !saves, "kept out, the hooks library is never loaded");
|
||||
}
|
||||
|
||||
// Frame generation switched on while the worker polls without pause: from the switch until the worker sees it, the
|
||||
// game's limiter paces, never RTSS's hold of the limit before (the worker's store for a want that changed after it
|
||||
// read it must not count for the new one).
|
||||
static void active_race(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
CHECK(rtss_limit_active(), "RTSS limits at 60");
|
||||
quiet = 1, free_run = 1;
|
||||
SetEvent(tick);
|
||||
long hits = 0, n = 400000, stuck = 0;
|
||||
srand(7);
|
||||
for (long i = 0; i < n && !stuck; i++) {
|
||||
for (volatile int k = rand() % 200; k > 0; k--) {}
|
||||
rtss_limit_want(RTSS_OUT_FRAMEGEN);
|
||||
LONG64 p0 = polls;
|
||||
int seen = 0;
|
||||
while (polls < p0 + 2) seen |= rtss_limit_active();
|
||||
hits += seen;
|
||||
rtss_limit_want(60000);
|
||||
ULONGLONG t0 = GetTickCount64();
|
||||
while (!rtss_limit_active() && !stuck) stuck = GetTickCount64() - t0 > 5000;
|
||||
}
|
||||
quiet = 0;
|
||||
printf(" %ld of %ld switches read RTSS's hold afterwards (worker polls %lld)\n", hits, n, (long long)polls);
|
||||
CHECK(!stuck, "RTSS did not take the limit back");
|
||||
CHECK(!hits, "the game's limiter stood aside for a kept-out want %ld times", hits);
|
||||
}
|
||||
|
||||
static const struct { const char *name; void (*run)(void); } scenarios[] = {
|
||||
{"restore_race", restore_race},
|
||||
{"restore_player", restore_player},
|
||||
@@ -398,6 +433,7 @@ static const struct { const char *name; void (*run)(void); } scenarios[] = {
|
||||
{"kept_two", kept_two},
|
||||
{"kept_lowlat_then_limit", kept_lowlat_then_limit},
|
||||
{"kept_framegen", kept_framegen},
|
||||
{"active_race", active_race},
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
Reference in New Issue
Block a user