diff --git a/docs/building.md b/docs/building.md index 2ab39662..d37eec55 100644 --- a/docs/building.md +++ b/docs/building.md @@ -795,9 +795,10 @@ refused, and two images of one frame never evict each other. No GPU, no game fil A change to dynamic resolution's target or step hold (`runtime/dynres.c`) passes `python tests\dynres_check.py`, which builds that file with stand-ins for the settings, the GPU's busy time, the clock and the game's rate under the limit: the default target is the frame time of 60, 144 and 240 fps and 16.7 ms for an unknown or unlimited rate, a target -that is set wins, a limit changed while it runs moves the target with one log line, and each step is held for -`dyn_res_step` 60ths of a second at 60, 144 and 240 fps, down while the GPU is over the target and back up once it is -under three quarters of it. No GPU, no game files. +that is set wins, a limit or target changed while it runs (in the menu too) moves the target with one log line, a +refresh rate wandering under 1 % after the start line logs none, and each step is held for `dyn_res_step` 60ths of a +second at 60, 144 and 240 fps, down while the GPU is over the target and back up once it is under three quarters of +it. No GPU, no game files. A change to the ASTC decoder's scratch trim (`runtime/vk_astc.c` `vk_astc_scratch_trim`, with the upload lock it only tries, `runtime/vk_memory.c` `vk_defer_buffer_try`) passes `python tests\astc_trim_check.py`, which builds `vk_astc.c` diff --git a/runtime/cfg.c b/runtime/cfg.c index 68f9c32e..456088e1 100644 --- a/runtime/cfg.c +++ b/runtime/cfg.c @@ -644,7 +644,7 @@ static const char *misfit(const char *set, const char *v, char *buf, size_t cap) if (*end) return NULL; double hi = strtod(dots + 2, &end); if (end == dots + 2 || *end) return NULL; - // any number: some rows with whole ends take fractions (dyn_res_target 16.7 in 1..100) + // any number: some rows with whole ends take fractions (dyn_res_target 16.7 in 0..100) double x = strtod(v, &end); if (end != v && !*end && x == x && x >= lo && x <= hi) return NULL; snprintf(buf, cap, "is not a number in %s", set); diff --git a/runtime/dynres.c b/runtime/dynres.c index 39073ca9..96cd8d1e 100644 --- a/runtime/dynres.c +++ b/runtime/dynres.c @@ -40,6 +40,7 @@ static struct { double avg_ns; // moving average of the GPU's busy time per frame double target_ms; // NX2NAVI_DYN_RES_TARGET; under 1: the frame time of the game's rate double target_ns; // the target in use + double logged_ns; // the target the log last named (the start line's, or dynres_frame's last line) uint64_t last_busy; // vk_gpu_busy_all at the previous presented frame unsigned cfg_seen; // cfg_version the settings were read at char base_mode[32]; @@ -73,6 +74,7 @@ static void dynres_read(void) { D.base = D.level = base; D.low = base > 2 ? base : 2; D.held_from = plat_now_ns(), D.avg_ns = 0, D.last_busy = vk_gpu_busy_all(); + D.logged_ns = D.target_ns; rt_log("resolution: dynamic resolution on: target %.1f ms (%s), each step held %.2f s, DLSS %s down to %s", D.target_ns / 1e6, D.target_ms >= 1 ? "as set" : "the frame limit's", D.hold_ns / 1e9, DYN_MODE[D.base], DYN_MODE[D.low]); @@ -94,15 +96,18 @@ const char *dynres_override(void) { return D.on && D.level != D.base ? DYN_MODE[ void dynres_frame(void) { if (cfg_version() != D.cfg_seen) dynres_read(); if (!D.on) return; - double target = target_now_ns(); // the limit, the monitor's rate and frame generation change without a setting - if (target != D.target_ns) { // logged when it moves by more than 1 %: a measured refresh rate wanders a little - static double logged_ns; + // The limit, the monitor's rate and frame generation move the target without a dyn_res setting changing, so it is + // read every frame. A move of more than 1 % from the target the log last named is logged, whether it came from the + // frame, from the menu (dynres_read takes a changed limit or dyn_res_target already) or since the start line: a + // measured refresh rate wanders a little. At most 20 lines a run. + D.target_ns = target_now_ns(); + if (fabs(D.target_ns - D.logged_ns) > D.logged_ns * 0.01) { static int said; - if (fabs(target - logged_ns) > logged_ns * 0.01 && said < 20) { - said++, logged_ns = target; - rt_log("resolution: dynamic resolution: target %.2f ms (the frame limit's)", target / 1e6); + if (said < 20) { + said++, D.logged_ns = D.target_ns; + rt_log("resolution: dynamic resolution: target %.2f ms (%s)", D.target_ns / 1e6, + D.target_ms >= 1 ? "as set" : "the frame limit's"); } - D.target_ns = target; } uint64_t now = vk_gpu_busy_all(), d = now - D.last_busy; D.last_busy = now; diff --git a/tests/dynres_check.py b/tests/dynres_check.py index 6bf0ec5f..5cafc17b 100644 --- a/tests/dynres_check.py +++ b/tests/dynres_check.py @@ -4,7 +4,9 @@ and checks that - the default target is the frame time of the game's rate (1000 / the limit: 60, 144 and 240 fps), 16.7 ms while that rate is unknown or unlimited, and a target that is set wins at any rate; - - a limit changed while it runs moves the target on the next frame, with one log line; + - a limit changed while it runs moves the target on the next frame, with one log line, also when the menu changed + it (or dyn_res_target) and dynres_read took the new target first; a refresh rate wandering under 1 % after the + start line logs nothing, and after dyn_res off and on a move back to the earlier run's last target is logged; - each step is held for dyn_res_step 60ths of a second of the clock at 60 and 240 fps (45: 0.75 s; 90: 1.5 s), down while the GPU is over the target and back up once it is under three quarters of it. @@ -110,6 +112,36 @@ int main(void) { before = logs; for (int k = 0; k < 100; k++) frame(144, 1); CHECK(logs == before, "a steady limit logged %%d lines", logs - before); + // A limit or a target changed in the menu (a new cfg_version, so dynres_read takes the new target first): one line. + before = logs; + game_hz = 120, ver++; + frame(120, 1); + CHECK(NEAR(D.target_ns, 1e9 / 120) && logs == before + 1 && strstr(last_log, "8.33 ms (the frame limit's)"), + "the menu's limit: %%.3f ms, %%d lines (%%s)", D.target_ns / 1e6, logs - before, last_log); + before = logs; + set_target_ms = 20, ver++; + frame(120, 1); + CHECK(NEAR(D.target_ns, 20e6) && logs == before + 1 && strstr(last_log, "20.00 ms (as set)"), + "the menu's target: %%.3f ms, %%d lines (%%s)", D.target_ns / 1e6, logs - before, last_log); + set_target_ms = 0; + // A refresh rate wandering under 1 %% after the start line: no line. Off and on again, then back to the target the + // earlier run logged last: one line, measured from the new start line. + game_hz = 60; + restart(); + before = logs; + game_hz = 59.9; + frame(59.9, 1); + CHECK(logs == before, "a wander of 0.17 %%%% after the start logged %%d lines (%%s)", logs - before, last_log); + game_hz = 144; + frame(144, 1); + dyn = "0", ver++, frame(144, 1); + game_hz = 60; + dyn = "1", ver++, frame(60, 1); + before = logs; + game_hz = 144; + frame(144, 1); + CHECK(logs == before + 1 && strstr(last_log, "6.94"), "back to the earlier run's last target: %%d lines (%%s)", + logs - before, last_log); // Each step is held for a time, whatever the frame rate. static const struct { double hz; int step; double hold_s; } holds[] = {{60, 45, 0.75}, {240, 45, 0.75}, {240, 90, 1.5}, {144, 8, 8 / 60.0}};