diff --git a/recomp/patches-v111.txt b/recomp/patches-v111.txt index 36c48190..9e29e2b2 100644 --- a/recomp/patches-v111.txt +++ b/recomp/patches-v111.txt @@ -38,6 +38,14 @@ site ostep main 6e6cc4 1e201828 bd410148 # fdiv s8, s1, s0 -> ldr s8, [x10, # # never differ. x9 is not read in the function and is free across the call that follows; the store keeps x8. site ostep main 3b4eaf4 5e21d800 @page:x9 # scvtf s0, s0 -> adrp x9, page site ostep main 3b4eaf8 1e201820 bd410120 # fdiv s0, s1, s0 -> ldr s0, [x9, #256] +# The time of day: its update (main+0x3cdf70) adds trunc(2 * frames per update) game seconds a frame to the seconds of +# the day (+0x3c of its object, carried into the day count by the code after it). With the step above below one frame +# per update the sum was 1 from 61 to 119 fps (the day at 83% of its pace at 100 fps) and 0 above 120, where the clock +# stood still and the sun swung back and forth around a frozen hour; at 60 fps a step a little under one frame lost a +# second too. The cut calls the page's +0xf60 instead (runtime/frame_rate.c frame_rate_day_host): the frame's seconds +# with the fraction kept for the next frame, so the day keeps the game's pace at any frame rate. x30 is the function's +# own saved one, and the call at main+0x3cdfc8 sets it again. +site ostep main 3cdfc0 1e380008 952b4be8 # fcvtzs w8, s0 -> bl page+0xf60 # Physics solver steps: the physics system's step setup (main+0x38aeb80, and the same code in main+0x38afc10) gives the # rigid body world its solver step count for the frame, trunc(frames per update * count + 0.001) with the count at diff --git a/runtime/frame_rate.c b/runtime/frame_rate.c index 33f73b18..a4bf5e27 100644 --- a/runtime/frame_rate.c +++ b/runtime/frame_rate.c @@ -476,6 +476,65 @@ static double ocarina_step(double dt) { return step; } +// ---- whole frames out of the real-time step (the set ostep's sites that cut it to a whole number, patches-v111.txt) +// The game adds the step, or the step times a whole number, to a few counters and cuts the sum to a whole number: at the +// console's 60 fps the step is 1 and they count frames. With the step the display interval in 60 Hz frames, under one +// above 60 fps, the cut loses the fraction every frame. whole_frames keeps it per counter (by its guest address) and +// hands out the whole part, so each counter moves at the game's pace at any frame rate; at a step of exactly one it +// gives what the game computes. A value that is not positive, or too large for a count, is cut as fcvtzs cuts it. +#define WHOLE_SLOTS 8 +static struct { uint64_t at; float rem; uint32_t used; } whole_slot[WHOLE_SLOTS]; +static uint32_t whole_calls; +static PlatMutex whole_lock = PLAT_MUTEX_INIT; + +static uint32_t cut_fcvtzs(float f) { // fcvtzs w, s: toward zero, saturating, NaN 0 + if (f != f) return 0; + if (f >= 2147483648.0f) return 0x7fffffffu; + if (f <= -2147483648.0f) return 0x80000000u; + return (uint32_t)(int32_t)f; +} + +static uint32_t whole_frames(uint64_t at, float amount) { + if (!(amount > 0.0f) || amount >= 2147483648.0f) return cut_fcvtzs(amount); + plat_mutex_lock(&whole_lock); + int k = -1, oldest = 0; + for (int i = 0; i < WHOLE_SLOTS; i++) { + if (whole_slot[i].at == at) { k = i; break; } + if (whole_slot[i].used < whole_slot[oldest].used) oldest = i; + } + if (k < 0) k = oldest, whole_slot[k].at = at, whole_slot[k].rem = 0.0f; + whole_slot[k].used = ++whole_calls; + float sum = whole_slot[k].rem + amount; + uint32_t whole = cut_fcvtzs(sum); + whole_slot[k].rem = sum - (float)whole; + plat_mutex_unlock(&whole_lock); + return whole; +} + +// The time of day (main+0x3cdf70, patches-v111.txt): s0 holds twice the step, the game seconds of this frame; w8 gets +// the whole ones, the counter being the seconds of the day at +0x3c of the object in x19. +// NX2NAVI_TRACE=perf: every 10 s, the game seconds the day was given beside those the cut alone would have given. +void frame_rate_day_host(Cpu *c) { + uint32_t bits = (uint32_t)c->v[0]; + float secs; + memcpy(&secs, &bits, 4); + uint32_t whole = whole_frames(c->x[19] + 0x3c, secs); + c->x[8] = whole; + if (rt_trace & RT_TRACE_PERF) { + static uint64_t t0, given, cut; + static double exact; + uint64_t now = plat_now_ns(); + given += whole, cut += cut_fcvtzs(secs), exact += secs; + if (!t0) t0 = now; + if (now - t0 >= 10000000000ull) { + rt_log("frame rate: the time of day got %llu game seconds in %.1f s (the frames' step makes %.1f; cut " + "to whole seconds each frame, as the game cuts them, %llu)", (unsigned long long)given, + (now - t0) / 1e9, exact, (unsigned long long)cut); + t0 = now, given = cut = 0, exact = 0; + } + } +} + // The 60 Hz frames the game steps at each update where the set ostep gives them (Ocarina); 1 elsewhere, the game's own // nominal frame. The mouse camera divides by it (mouse_direct.c): the game multiplies camera input by this step. float frame_step_frames(void) { diff --git a/runtime/host.h b/runtime/host.h index 39d02899..3b5928db 100644 --- a/runtime/host.h +++ b/runtime/host.h @@ -393,6 +393,7 @@ int frame_limit_adjustable(void); // the frame limit set int frame_free_running(void); // no fps60/realtime patch set: the game steps by the clock double frame_step_limit_hz(void); // the frame rate the limit gives (overlay); 0 unknown double frame_step_game_hz(void); // the rate the game draws at under the limit; 0 unknown +void frame_rate_day_host(Cpu *c); // the time of day's seconds a frame (set ostep, page +0xf60) float frame_step_frames(void); // the 60 Hz frames the game steps per update (set ostep), else 1 void frame_step_flight(float *page, float *game, uint64_t *presents); // flight.c: the step at a host present // rtss_limit.c: RivaTuner Statistics Server takes the frame limit where it runs diff --git a/runtime/patches.c b/runtime/patches.c index 4933100e..cd40c09b 100644 --- a/runtime/patches.c +++ b/runtime/patches.c @@ -32,6 +32,10 @@ uint8_t rt_patch_on[RT_PATCH_COUNT > 0 ? RT_PATCH_COUNT : 1]; // read by the g #ifdef RT_PATCH_PAGE #include "debug_tools.h" __attribute__((constructor)) static void freecam_register(void) { rt_register_host(RT_PATCH_PAGE + 0xf70, dbg_freecam_host); } +// The time of day's site of the set ostep (patches-v111.txt) calls the page's +0xf60 (frame_rate.c). +__attribute__((constructor)) static void day_register(void) { + if (RT_V111) rt_register_host(RT_PATCH_PAGE + 0xf60, frame_rate_day_host); +} #endif #if RT_PATCH_COUNT