Log dynamic resolution's target against the one the log last named, from the start line on.

The review found that dynres.c dynres_frame compared a changed target with a static that started at 0 and outlived a
restart: with fps_limit = display the first refresh-rate wander under 1 % after the start was logged, against the
comment's rule, and after dyn_res off and on a move back to the earlier run's last logged target said nothing, so the
start line and the live lines disagreed. A test of the menu's path found a third case: a limit or dyn_res_target
changed in the menu bumps cfg_version, dynres_read takes the new target first, and dynres_frame, which logged only
when its own reading differed from D.target_ns, never named it. The last logged target now lives in D (logged_ns),
the start line sets it, and dynres_frame logs any move of more than 1 % from it, naming the set target as "as set".
No lock and no wait; the target and the steps are what they were. The review's stale "(dyn_res_target 16.7 in
1..100)" in cfg.c misfit now reads 0..100, as the row's set has been since the commit that changed the default
target.

tests/dynres_check.py gains these cases (a menu limit, a menu target, a wander after the start, the earlier run's
target after off and on); they fail on the old dynres.c and pass. The -Wall compile of dynres.c and cfg.c shows no
warning, and cfg_rows_check reports 0 problems.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 19:37:48 -05:00
parent 4fe1339189
commit 1ae00ed802
4 changed files with 50 additions and 12 deletions

View File

@@ -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`

View File

@@ -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);

View File

@@ -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;

View File

@@ -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}};