mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Let a want reach RivaTuner Statistics Server's profile only once it stayed two polls, and write nothing before the first host present.
A want that flips between a limit and kept-out created and removed RTSS's profile at each flip: with a driver's low latency vk_lowlat_on() is 0 before the first swapchain and during every swapchain rebuild, so a warm start without the boot screen wrote the profile and removed it a poll later, and a poll that fell into a rebuild did the same (lane A's probe: 6 creations and 5 removals for a want flipping each poll, 1 and 1 at start before RTSS hooked the game). rtss_decide now counts the polls in a row a want stayed: a limit goes into the profile and a kept-out want puts it back only after RTSS_SETTLE_POLLS (2), while standing aside still ends at once (rtss_limit_active compares the want), and nothing is written before the first host present (RTSS_WHY_NO_PRESENT). A limit change reaches the profile up to a second later; the game's limiter holds the new limit meanwhile. The created and removed lines were numbered before. Not reproduced before the change. rtss_limit_check (T8: kept out for one poll and for two, a second reason, a limit again; T9: 20 flips after a written limit give no write or put back, 20 from the start none, flips every two polls act; no write while no host present) and rtss_worker_check (churn_run and every scenario that now settles) pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,7 @@ static inline double rtss_fps(const RtssApp *a) {
|
||||
#define RTSS_OVER_SLACK 1.05 // above limit * this, RTSS is taken not to hold the game to it
|
||||
#define RTSS_FRESH_NS 2500000000ull // a period is stale after this, or 2.5 times its own length if longer
|
||||
#define RTSS_HOLD_IDLE_NS 200000000ull // no host present for this long: RTSS holds nothing (vk_lowlat.c HOLD_IDLE_NS)
|
||||
#define RTSS_SETTLE_POLLS 2 // polls in a row a want must stay before it acts on the profile
|
||||
|
||||
enum { RTSS_FAIL_NONE, RTSS_FAIL_LIBRARY, RTSS_FAIL_READONLY, RTSS_FAIL_NOT_LIMITING }; // out of it for the run
|
||||
enum { RTSS_DO_NOTHING, RTSS_DO_APPLY, RTSS_DO_PUT_BACK };
|
||||
@@ -97,6 +98,8 @@ enum {
|
||||
RTSS_WHY_UPDATE, // RTSS has not read the profile the game wrote yet
|
||||
RTSS_WHY_NOT_COUNTING, // RTSS has measured no period of the game's frames lately
|
||||
RTSS_WHY_IDLE, // no picture went out lately (a minimised window, a frame hold): RTSS has none to hold
|
||||
RTSS_WHY_SETTLING, // a new want: it reaches the profile once it stayed RTSS_SETTLE_POLLS polls
|
||||
RTSS_WHY_NO_PRESENT, // no picture went out yet: nothing is written before the first
|
||||
};
|
||||
|
||||
// What the worker keeps between polls.
|
||||
@@ -109,8 +112,10 @@ typedef struct {
|
||||
int held; // the poll before stood aside: a fresh period now shows how RTSS holds the game
|
||||
uint32_t t1, span; // the end and length (ms) of the newest period seen
|
||||
uint64_t fresh_ns; // when a new period was last seen; 0 never
|
||||
int64_t seen; // the want of the poll before
|
||||
int polls; // polls in a row with that want
|
||||
} RtssState;
|
||||
#define RTSS_STATE_INIT {-1, -1, 0, 0, RTSS_FAIL_NONE, 0, 0, 0, 0}
|
||||
#define RTSS_STATE_INIT {-1, -1, 0, 0, RTSS_FAIL_NONE, 0, 0, 0, 0, RTSS_UNSET, 0}
|
||||
|
||||
// One poll's view.
|
||||
typedef struct {
|
||||
@@ -144,10 +149,13 @@ static inline int rtss_wants_hooks(const RtssState *s, const RtssIn *in) {
|
||||
return in->want >= 0 && !s->fail && in->app.running;
|
||||
}
|
||||
|
||||
// The decision of one poll, in this order: no want does nothing; a kept-out want puts back what this run wrote (once
|
||||
// the hooks are loaded); a failure lasts the run; RTSS not running leaves the profile for its return; a library that
|
||||
// did not load fails the run; a refused limit stays with the game; a new limit (rtss_target) goes into the profile at
|
||||
// once. The game's limiter stands aside only once RTSS is seen to hold the game: the profile held the limit at the poll
|
||||
// The decision of one poll, in this order: no want does nothing; a want acts on the profile only once it stayed
|
||||
// RTSS_SETTLE_POLLS polls in a row, so a want that flips (a driver's low latency on and off at each swapchain rebuild,
|
||||
// vk_lowlat.c) does not write and remove the profile at each flip, while the game's limiter takes a new want at once
|
||||
// (rtss_limit_active). A settled kept-out want puts back what this run wrote (once the hooks are loaded); a failure
|
||||
// lasts the run; RTSS not running leaves the profile for its return; a library that did not load fails the run;
|
||||
// nothing is written before the first picture went out; a refused limit stays with the game; a settled new limit
|
||||
// (rtss_target) goes into the profile. The game's limiter stands aside only once RTSS is seen to hold the game: the profile held the limit at the poll
|
||||
// before (RTSS reads it after the write), RTSS lists the game with its Vulkan presents, has read the profile
|
||||
// (RTSS_UPDATE_ASKED clear) and measured a period of the game's frames lately (a fresh one, rtss_fresh), no movie plays
|
||||
// (rtss_target) and a picture went out within RTSS_HOLD_IDLE_NS. Each fresh period after a poll that stood aside is
|
||||
@@ -163,16 +171,21 @@ static inline RtssOut rtss_decide(RtssState *s, const RtssIn *in) {
|
||||
if (fresh) s->t1 = a->t1, s->span = a->t1 - a->t0, s->fresh_ns = in->now_ns;
|
||||
int held = s->held;
|
||||
s->held = 0; // until this poll stands aside again
|
||||
s->polls = w == s->seen ? s->polls + 1 : 1;
|
||||
s->seen = w;
|
||||
int settled = s->polls >= RTSS_SETTLE_POLLS;
|
||||
if (w < 0) {
|
||||
if (in->hooks == 1 && s->changed) o.act = RTSS_DO_PUT_BACK;
|
||||
if (settled && in->hooks == 1 && s->changed) o.act = RTSS_DO_PUT_BACK;
|
||||
s->over = 0;
|
||||
return o.why = RTSS_WHY_OUT, o;
|
||||
}
|
||||
if (s->fail) return o.why = RTSS_WHY_FAILED, o;
|
||||
if (!a->running) return o.why = RTSS_WHY_NOT_RUNNING, o;
|
||||
if (in->hooks == 0) return s->fail = RTSS_FAIL_LIBRARY, o.why = RTSS_WHY_FAILED, o;
|
||||
if (!in->hosted_ns) return o.why = RTSS_WHY_NO_PRESENT, o;
|
||||
int64_t t = o.mhz = rtss_target(w, in->movie_mhz);
|
||||
if (t == s->refused) return o.why = RTSS_WHY_REFUSED, o;
|
||||
if (t != s->applied && !settled) return o.why = RTSS_WHY_SETTLING, o;
|
||||
if (t != s->applied) { // RTSS reads it after the write: a later poll sees whether it holds
|
||||
o.act = RTSS_DO_APPLY;
|
||||
o.why = in->movie_mhz ? RTSS_WHY_MOVIE : a->listed ? RTSS_WHY_UPDATE : RTSS_WHY_NOT_LISTED;
|
||||
@@ -266,6 +279,8 @@ static inline void rtss_why_text(char *out, size_t n, const RtssOut *o, int fail
|
||||
else if (o->why == RTSS_WHY_NOT_COUNTING)
|
||||
snprintf(out, n, "RivaTuner Statistics Server has measured no frames of the game lately");
|
||||
else if (o->why == RTSS_WHY_IDLE) snprintf(out, n, "no picture went out lately: RivaTuner has none to hold");
|
||||
else if (o->why == RTSS_WHY_SETTLING) snprintf(out, n, "a new limit: RivaTuner Statistics Server gets it once it stays");
|
||||
else if (o->why == RTSS_WHY_NO_PRESENT) snprintf(out, n, "no picture has gone out yet");
|
||||
else snprintf(out, n, "RivaTuner Statistics Server has not hooked the game yet");
|
||||
}
|
||||
|
||||
|
||||
@@ -190,14 +190,17 @@ static void why_is(const RtssOut *o, const RtssState *s, const char *want, int l
|
||||
#define WHY(o, r, text) why_is(&(o), &(r).s, text, __LINE__)
|
||||
|
||||
// RTSS's injection delay: it runs but has not hooked the game for 30 polls. The limit goes into the profile at the
|
||||
// first poll, with no second write. Once RTSS lists the game the game's limiter stands aside only after RTSS has seen
|
||||
// its Vulkan presents, read the profile and measured a period of its frames lately.
|
||||
// second poll (the first sees the want new), with no second write. Once RTSS lists the game the game's limiter stands
|
||||
// aside only after RTSS has seen its Vulkan presents, read the profile and measured a period of its frames lately.
|
||||
static void injection_delay(void) {
|
||||
Run r;
|
||||
run_init(&r);
|
||||
r.listed = 0;
|
||||
RtssOut o = step(&r, 60000);
|
||||
CHECK(o.act == RTSS_DO_APPLY && !o.holds && o.why == RTSS_WHY_NOT_LISTED, "written at once, not held yet");
|
||||
CHECK(o.act == RTSS_DO_NOTHING && !o.holds && o.why == RTSS_WHY_SETTLING, "a new want: not written yet");
|
||||
WHY(o, r, "a new limit: RivaTuner Statistics Server gets it once it stays");
|
||||
o = step(&r, 60000);
|
||||
CHECK(o.act == RTSS_DO_APPLY && !o.holds && o.why == RTSS_WHY_NOT_LISTED, "written, not held yet");
|
||||
CHECK(r.st.firsts == 1 && r.s.applied == 60000 && r.s.changed, "the first write of the run");
|
||||
for (int i = 0; i < 30; i++) o = step(&r, 60000);
|
||||
CHECK(r.st.applies == 1 && !o.holds && o.why == RTSS_WHY_NOT_LISTED, "no write while not listed");
|
||||
@@ -227,6 +230,7 @@ static void quit(void) {
|
||||
Run r;
|
||||
run_init(&r);
|
||||
step(&r, 60000);
|
||||
step(&r, 60000);
|
||||
RtssOut o = step(&r, 60000);
|
||||
CHECK(o.holds, "held");
|
||||
r.running = 0;
|
||||
@@ -243,6 +247,7 @@ static void readonly(void) {
|
||||
Run r;
|
||||
run_init(&r);
|
||||
r.st.readonly = 1;
|
||||
step(&r, 60000);
|
||||
RtssOut o = step(&r, 60000);
|
||||
CHECK(o.act == RTSS_DO_APPLY && !o.holds && o.why == RTSS_WHY_FAILED && r.s.fail == RTSS_FAIL_READONLY, "failed");
|
||||
CHECK(!r.s.changed, "nothing of the run in the profile");
|
||||
@@ -252,27 +257,32 @@ static void readonly(void) {
|
||||
}
|
||||
|
||||
// A limit out of RTSS's range: refused and kept by the game; the earlier limit of this run is cleared to none; a limit
|
||||
// in range goes to RTSS at once.
|
||||
// in range goes to RTSS once it stays.
|
||||
static void refused(void) {
|
||||
Run r;
|
||||
run_init(&r);
|
||||
r.st.refuse_above = 200000;
|
||||
step(&r, 60000);
|
||||
step(&r, 60000);
|
||||
step(&r, 240000);
|
||||
RtssOut o = step(&r, 240000);
|
||||
CHECK(o.act == RTSS_DO_APPLY && !o.holds && o.why == RTSS_WHY_REFUSED, "refused");
|
||||
CHECK(r.s.refused == 240000 && r.s.applied == 0 && r.st.clears == 1 && r.st.holds == 0, "the 60 cleared to none");
|
||||
for (int i = 0; i < 5; i++) o = step(&r, 240000);
|
||||
CHECK(r.st.applies == 2 && o.why == RTSS_WHY_REFUSED && !o.holds, "not written again");
|
||||
o = step(&r, 144000);
|
||||
CHECK(o.act == RTSS_DO_APPLY && !o.holds && r.st.holds == 144000, "144 at once");
|
||||
CHECK(o.act == RTSS_DO_NOTHING && o.why == RTSS_WHY_SETTLING, "144 settles first");
|
||||
o = step(&r, 144000);
|
||||
CHECK(o.act == RTSS_DO_APPLY && !o.holds && r.st.holds == 144000, "144 written");
|
||||
o = step(&r, 144000);
|
||||
CHECK(o.holds, "held from the next poll");
|
||||
CHECK(r.st.firsts == 1, "one first write in the run (%d)", r.st.firsts);
|
||||
o = step(&r, 240000);
|
||||
CHECK(o.act == RTSS_DO_NOTHING && o.why == RTSS_WHY_REFUSED, "the refused limit stays with the game");
|
||||
CHECK(o.act == RTSS_DO_NOTHING && o.why == RTSS_WHY_REFUSED, "the refused limit stays with the game at once");
|
||||
// refused as the run's first write: the profile is the run's from then on
|
||||
run_init(&r);
|
||||
r.st.refuse_above = 200000;
|
||||
step(&r, 240000);
|
||||
o = step(&r, 240000);
|
||||
CHECK(o.why == RTSS_WHY_REFUSED && r.s.changed && r.st.clears == 1, "a first refusal clears too, as it stands");
|
||||
}
|
||||
@@ -319,7 +329,7 @@ static void frame_count(void) {
|
||||
// two periods over, then one under: the count starts again
|
||||
run_init(&r);
|
||||
r.frames = 144;
|
||||
for (int i = 0; i < 6; i++) step(&r, 60000);
|
||||
for (int i = 0; i < 7; i++) step(&r, 60000);
|
||||
CHECK(r.s.over == 2, "two periods over (%d)", r.s.over);
|
||||
r.frames = 60;
|
||||
for (int i = 0; i < 2; i++) step(&r, 60000);
|
||||
@@ -327,22 +337,25 @@ static void frame_count(void) {
|
||||
// a want that changed while the poll ran: nothing decided, the count waits
|
||||
run_init(&r);
|
||||
r.frames = 144;
|
||||
for (int i = 0; i < 4; i++) step(&r, 60000);
|
||||
for (int i = 0; i < 5; i++) step(&r, 60000);
|
||||
o = step_ex(&r, 60000, 0);
|
||||
CHECK(o.why == RTSS_WHY_NEWER && !o.holds && o.act == RTSS_DO_NOTHING && r.s.over == 1, "a newer want: no count");
|
||||
}
|
||||
|
||||
// No picture went out for 200 ms (a minimised window, a frame hold) or none yet: RTSS has nothing to hold, and the
|
||||
// game's limiter takes the limit back until pictures go out again.
|
||||
// game's limiter takes the limit back until pictures go out again. Before the first picture nothing is written.
|
||||
static void idle(void) {
|
||||
Run r;
|
||||
run_init(&r);
|
||||
r.presenting = 0;
|
||||
step(&r, 60000);
|
||||
RtssOut o = step(&r, 60000);
|
||||
CHECK(!o.holds && o.why == RTSS_WHY_IDLE, "no host present yet");
|
||||
RtssOut o;
|
||||
for (int i = 0; i < 4; i++) o = step(&r, 60000);
|
||||
CHECK(!o.holds && o.why == RTSS_WHY_NO_PRESENT && !r.st.applies, "no host present yet: nothing written");
|
||||
WHY(o, r, "no picture has gone out yet");
|
||||
r.presenting = 1;
|
||||
o = step(&r, 60000);
|
||||
CHECK(o.act == RTSS_DO_APPLY && !o.holds, "written at the first picture, the want having stayed");
|
||||
o = step(&r, 60000);
|
||||
CHECK(o.holds, "held once pictures go out");
|
||||
r.presenting = 0;
|
||||
r.hosted_ns = r.now_ns + 500000000ull - 199000000ull; // the next poll comes 199 ms after the last present
|
||||
@@ -362,16 +375,22 @@ static void kept_out(void) {
|
||||
Run r;
|
||||
run_init(&r);
|
||||
step(&r, 60000);
|
||||
step(&r, 60000);
|
||||
RtssOut o = step(&r, RTSS_OUT_FRAMEGEN);
|
||||
CHECK(o.act == RTSS_DO_NOTHING && o.why == RTSS_WHY_OUT && !o.holds && r.st.holds == 60000,
|
||||
"the game's limiter at once, the profile once the want stays");
|
||||
o = step(&r, RTSS_OUT_FRAMEGEN);
|
||||
CHECK(o.act == RTSS_DO_PUT_BACK && o.why == RTSS_WHY_OUT && !o.holds && r.st.holds == -1, "put back");
|
||||
CHECK(r.st.last_put_back_why == RTSS_WHY_OUT, "the put back says why");
|
||||
o = step(&r, RTSS_OUT_FRAMEGEN);
|
||||
CHECK(o.act == RTSS_DO_NOTHING && r.st.put_backs == 1, "once");
|
||||
o = step(&r, RTSS_OUT_LOWLAT);
|
||||
CHECK(o.act == RTSS_DO_NOTHING && o.mhz == RTSS_OUT_LOWLAT, "a second reason: nothing more");
|
||||
for (int i = 0; i < 3; i++) o = step(&r, RTSS_OUT_LOWLAT);
|
||||
CHECK(o.act == RTSS_DO_NOTHING && o.mhz == RTSS_OUT_LOWLAT && r.st.put_backs == 1, "a second reason: nothing more");
|
||||
WHY(o, r, "RivaTuner Statistics Server kept out: the driver's low latency");
|
||||
o = step(&r, 60000);
|
||||
CHECK(o.act == RTSS_DO_APPLY && !o.holds && r.st.firsts == 2, "written at once, the run's first write again");
|
||||
CHECK(o.act == RTSS_DO_NOTHING && o.why == RTSS_WHY_SETTLING, "a limit again: settles first");
|
||||
o = step(&r, 60000);
|
||||
CHECK(o.act == RTSS_DO_APPLY && !o.holds && r.st.firsts == 2, "written, the run's first write again");
|
||||
o = step(&r, 60000);
|
||||
CHECK(o.holds, "held from the next poll");
|
||||
// kept out from the start: the hooks library is never wanted, and nothing is written or put back
|
||||
@@ -380,13 +399,31 @@ static void kept_out(void) {
|
||||
CHECK(!r.st.loaded && !r.st.applies && !r.st.put_backs && o.why == RTSS_WHY_OUT, "read only");
|
||||
}
|
||||
|
||||
// Low latency switching on and off at every poll: each switch writes or puts back, as it stands.
|
||||
// Low latency switching on and off at every poll (a swapchain rebuild, the time before the first swapchain): the want
|
||||
// never stays two polls, so the profile keeps the limit written before, and the game's limiter holds the frames while
|
||||
// the want is new. From the start, the same flips write nothing.
|
||||
static void churn(void) {
|
||||
Run r;
|
||||
run_init(&r);
|
||||
step(&r, 60000);
|
||||
step(&r, 60000);
|
||||
RtssOut o;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
o = step(&r, i & 1 ? 60000 : RTSS_OUT_LOWLAT);
|
||||
CHECK(!(i & 1) ? !o.holds : 1, "kept out at poll %d: the game's limiter", i);
|
||||
}
|
||||
CHECK(r.st.applies == 1 && !r.st.put_backs && r.st.holds == 60000, "%d writes, %d put backs", r.st.applies,
|
||||
r.st.put_backs);
|
||||
o = step(&r, 60000);
|
||||
CHECK(o.holds && r.st.applies == 1, "held once the limit stays");
|
||||
run_init(&r);
|
||||
for (int i = 0; i < 20; i++) step(&r, i & 1 ? 60000 : RTSS_OUT_LOWLAT);
|
||||
CHECK(r.st.applies == 11 && r.st.put_backs == 10, "%d writes, %d put backs", r.st.applies, r.st.put_backs);
|
||||
CHECK(!r.st.applies && !r.st.put_backs, "from the start: nothing written");
|
||||
// a flip every two polls is a want that stays: each one acts
|
||||
run_init(&r);
|
||||
for (int i = 0; i < 8; i++) step(&r, i & 2 ? RTSS_OUT_LOWLAT : 60000);
|
||||
CHECK(r.st.applies == 2 && r.st.put_backs == 2, "two polls each: %d writes, %d put backs", r.st.applies,
|
||||
r.st.put_backs);
|
||||
}
|
||||
|
||||
// A movie under RTSS: the want stays the play limit and the game's pacer holds the movie. At a limit of 144 the profile
|
||||
@@ -396,6 +433,7 @@ static void movie(void) {
|
||||
Run r;
|
||||
run_init(&r);
|
||||
step(&r, 144000);
|
||||
step(&r, 144000);
|
||||
r.movie_mhz = 60000;
|
||||
RtssOut o = step(&r, 144000);
|
||||
CHECK(o.act == RTSS_DO_NOTHING && !o.holds && o.why == RTSS_WHY_MOVIE, "a movie at 144: no write, the pacer's");
|
||||
@@ -407,6 +445,7 @@ static void movie(void) {
|
||||
CHECK(o.act == RTSS_DO_NOTHING && o.holds && o.why == RTSS_WHY_HOLDS && r.st.applies == 1, "held again after it");
|
||||
run_init(&r);
|
||||
step(&r, 40000);
|
||||
step(&r, 40000);
|
||||
r.movie_mhz = 60000;
|
||||
o = step(&r, 40000);
|
||||
CHECK(o.act == RTSS_DO_APPLY && o.mhz == 0 && !o.holds && o.why == RTSS_WHY_MOVIE && r.st.holds == 0,
|
||||
|
||||
@@ -379,6 +379,11 @@ static void ask(int64_t mhz) { // a want at a present; the first one that star
|
||||
CHECK(WaitForMultipleObjects(2, h, FALSE, 5000) != WAIT_TIMEOUT, "the worker did not finish its first poll");
|
||||
}
|
||||
}
|
||||
static void settle(int64_t mhz) { // a new want, then the polls it takes to reach the profile (RTSS_SETTLE_POLLS)
|
||||
ask(mhz);
|
||||
poll();
|
||||
poll();
|
||||
}
|
||||
static void show(const char *what) {
|
||||
printf(" %s: profile %s %lu/%lu; active %d; state %d; saves %ld deletes %ld updates %ld loads %ld\n", what,
|
||||
own.exists ? "exists," : "absent,", own.limit, own.denom, rtss_limit_active(), rtss_limit_state(), saves,
|
||||
@@ -498,8 +503,7 @@ static void kept_lowlat_then_limit(void) {
|
||||
"the driver's low latency"),
|
||||
"the two-limiter line names the game's profile and low latency");
|
||||
CHECK(!loads, "kept out, the hooks library is never loaded");
|
||||
ask(60000);
|
||||
poll();
|
||||
settle(60000);
|
||||
show("low latency off, limit 60");
|
||||
CHECK(loads == 1 && own.exists && own.limit == 60, "the limit goes into the profile once RTSS is let in");
|
||||
poll();
|
||||
@@ -525,6 +529,7 @@ static void active_race(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
poll();
|
||||
CHECK(rtss_limit_active(), "RTSS limits at 60");
|
||||
quiet = 1, free_run = 1;
|
||||
SetEvent(tick);
|
||||
@@ -555,6 +560,7 @@ static void active_logged(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
during_save = framegen_on;
|
||||
ask(60000);
|
||||
poll();
|
||||
show("frame generation switched on during the first write");
|
||||
CHECK(own.exists && own.limit == 60, "the limit went into the profile");
|
||||
CHECK(!rtss_limit_active(), "the game's limiter paces the kept-out want");
|
||||
@@ -589,11 +595,9 @@ static void round_logged_whole(void) {
|
||||
show("display at 59.94 Hz, RTSS without a denominator");
|
||||
CHECK(own.limit == 60, "rounded to whole frames");
|
||||
CHECK(logged("profile ocarina.exe limits to 60/1 (60.000 fps; the game asked 59.940)"), "the log names 60/1");
|
||||
ask(143856);
|
||||
poll();
|
||||
settle(143856);
|
||||
CHECK(logged("profile ocarina.exe limits to 144/1 (144.000 fps; the game asked 143.856)"), "the log names 144/1");
|
||||
ask(60000);
|
||||
poll();
|
||||
settle(60000);
|
||||
CHECK(logged("profile ocarina.exe limits to 60.000 fps\n"), "a whole limit as it is");
|
||||
}
|
||||
|
||||
@@ -712,9 +716,9 @@ static void refused_run(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
CHECK(own.limit == 60 && rtss_limit_active(), "60 taken");
|
||||
ask(240000);
|
||||
poll();
|
||||
CHECK(own.limit == 60 && rtss_limit_active(), "60 taken");
|
||||
settle(240000);
|
||||
show("240 refused");
|
||||
CHECK(own.exists && own.limit == 0, "the earlier limit of this run is cleared to none");
|
||||
CHECK(!rtss_limit_active() && rtss_limit_state() == 0, "the game's limiter keeps 240");
|
||||
@@ -722,15 +726,15 @@ static void refused_run(void) {
|
||||
poll();
|
||||
poll();
|
||||
CHECK(saves == s0, "the refused limit is not written again");
|
||||
ask(144000);
|
||||
poll();
|
||||
settle(144000);
|
||||
show("144 after the refusal");
|
||||
CHECK(own.limit == 144, "144 written at once");
|
||||
CHECK(own.limit == 144, "144 written once it stayed");
|
||||
poll();
|
||||
CHECK(rtss_limit_active(), "and held from the next poll");
|
||||
ask(240000);
|
||||
CHECK(!rtss_limit_active(), "240 with the game at once");
|
||||
poll();
|
||||
CHECK(!rtss_limit_active(), "240 stays with the game");
|
||||
CHECK(!rtss_limit_active() && own.limit == 144, "240 stays with the game");
|
||||
}
|
||||
|
||||
// RTSS lists the game but lets it run well over the limit: three fresh periods in a row over 1.05 times the limit, each
|
||||
@@ -738,15 +742,14 @@ static void refused_run(void) {
|
||||
static void over_run(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 144);
|
||||
ask(60000);
|
||||
for (int i = 0; i < 3; i++) poll();
|
||||
for (int i = 0; i < 4; i++) poll();
|
||||
CHECK(rtss_limit_active() && own.exists, "two periods over: RTSS still limits");
|
||||
poll();
|
||||
show("three periods at 144 fps with the limit at 60");
|
||||
CHECK(rtss_limit_state() == 3 && !rtss_limit_active(), "the menu says RTSS does not limit the game");
|
||||
CHECK(!own.exists, "the profile is put back");
|
||||
CHECK(logged("RivaTuner Statistics Server measures 144.0 fps with its limit at 60.000 fps"), "logged");
|
||||
ask(30000);
|
||||
poll();
|
||||
settle(30000);
|
||||
CHECK(saves == 1 && rtss_limit_state() == 3, "out for the run");
|
||||
}
|
||||
// 1.05 times the limit is not caught; a period RTSS no longer moves (the limit lowered, then the window minimised)
|
||||
@@ -796,6 +799,7 @@ static void idle_run(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
poll();
|
||||
CHECK(rtss_limit_active(), "RTSS limits");
|
||||
presenting = 0;
|
||||
poll();
|
||||
@@ -843,26 +847,36 @@ static void library_run(void) {
|
||||
CHECK(logged("RivaTuner Statistics Server's hooks library did not load"), "logged");
|
||||
}
|
||||
|
||||
// Low latency switching on and off at every poll (the want flips between a limit and kept out): each switch writes or
|
||||
// puts back the profile, as it stands, and the log numbers each creation and removal so a play-test can count them.
|
||||
// Low latency switching on and off at every poll (the want flips between a limit and kept out, as at each swapchain
|
||||
// rebuild): a want acts on the profile only once it stayed two polls, so the flips write and remove nothing, and the
|
||||
// game's limiter holds each kept-out want at once. A want that stays two polls each time does act, and the log numbers
|
||||
// each creation and removal so a play-test can count them.
|
||||
static void churn_run(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
poll();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ask(i & 1 ? 60000 : RTSS_OUT_LOWLAT);
|
||||
if (!(i & 1)) CHECK(!rtss_limit_active(), "kept out at flip %d: the game's limiter at once", i);
|
||||
poll();
|
||||
}
|
||||
show("ten polls with the want flipping");
|
||||
CHECK(created == 1 && removed == 0, "%ld creations, %ld removals", created, removed);
|
||||
poll();
|
||||
CHECK(created == 6 && removed == 5, "%ld creations, %ld removals", created, removed);
|
||||
CHECK(rtss_limit_active(), "RTSS limits once the limit stays");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
settle(RTSS_OUT_LOWLAT);
|
||||
settle(60000);
|
||||
}
|
||||
show("four flips of two polls each");
|
||||
CHECK(created == 5 && removed == 4, "%ld creations, %ld removals", created, removed);
|
||||
CHECK(logged("created; it goes away when the game closes (creation 1 this run)\n") &&
|
||||
logged("(creation 6 this run)\n") && !logged("(creation 7"),
|
||||
logged("(creation 5 this run)\n") && !logged("(creation 6"),
|
||||
"the created lines are numbered");
|
||||
CHECK(logged("removed, as before the game started (removal 1 this run)\n") && logged("(removal 5 this run)\n") &&
|
||||
!logged("(removal 6"),
|
||||
CHECK(logged("removed, as before the game started (removal 1 this run)\n") && logged("(removal 4 this run)\n") &&
|
||||
!logged("(removal 5"),
|
||||
"the removed lines are numbered");
|
||||
CHECK(rtss_limit_active(), "RTSS limits after the last want of a limit");
|
||||
}
|
||||
|
||||
// The state file: the run's first write records the profile as it was, each later one the value written, and the exit's
|
||||
@@ -874,8 +888,7 @@ static void state_record(void) {
|
||||
show("playing at 60");
|
||||
CHECK(own.exists && own.limit == 60 && state_has("\nrtss1 0 0 1 60 1 ocarina.exe\n"), "recorded:\n%s", state());
|
||||
CHECK(!strncmp(state(), "# RivaTuner Statistics Server's profiles", 40), "the header first");
|
||||
ask(144000);
|
||||
poll();
|
||||
settle(144000);
|
||||
CHECK(own.limit == 144 && state_has("\nrtss1 0 0 1 144 1 ocarina.exe\n"), "the line follows:\n%s", state());
|
||||
rtss_limit_restore();
|
||||
show("after the restore");
|
||||
@@ -887,11 +900,9 @@ static void state_player(void) {
|
||||
ask(60000);
|
||||
poll();
|
||||
CHECK(own.limit == 60 && state_has("\nrtss1 1 30 1 60 1 ocarina.exe\n"), "recorded:\n%s", state());
|
||||
ask(RTSS_OUT_LOWLAT); // kept out: put back during the run, and the line goes with it
|
||||
poll();
|
||||
settle(RTSS_OUT_LOWLAT); // kept out: put back during the run, and the line goes with it
|
||||
CHECK(own.limit == 30 && !*state(), "put back, no line:\n%s", state());
|
||||
ask(60000);
|
||||
poll();
|
||||
settle(60000);
|
||||
CHECK(own.limit == 60 && state_has("\nrtss1 1 30 1 60 1 ocarina.exe\n"), "recorded again:\n%s", state());
|
||||
rtss_limit_restore();
|
||||
CHECK(own.exists && own.limit == 30 && !*state(), "the player's 30 back, no line");
|
||||
@@ -904,6 +915,7 @@ static void state_unwritable(void) {
|
||||
app_set(1, 0xA, 1000, 2000, 60);
|
||||
ask(60000);
|
||||
poll();
|
||||
poll();
|
||||
show("no state file");
|
||||
CHECK(own.exists && own.limit == 60 && rtss_limit_active(), "RTSS limits as before");
|
||||
CHECK(logged("rtss-profile.txt could not be written; a crash would leave the game's limit in RivaTuner Statistics "
|
||||
|
||||
Reference in New Issue
Block a user