diff --git a/docs/building.md b/docs/building.md index cd8a2956..6ac588d3 100644 --- a/docs/building.md +++ b/docs/building.md @@ -815,7 +815,9 @@ A change to where the upscalers free what they let go of (`runtime/vk_dlaa.c` "w GPU may still use it", `runtime/vk_ffx.cpp` and `runtime/vk_ffx4.cpp` `context_for`) passes `python tests\upscaler_idle_check.py`, which reads the sources: `vkDeviceWaitIdle` only in `vk_upscaler_device_idle` and the `--probe-ffx-cache` path, that function only behind the rollback switch, a full list and a refused creation, -every place that lets something go calling `vk_upscaler_let_go`, `upscaler_idle_wait`'s `sections[]` entry and +every place that lets something go calling `vk_upscaler_let_go`, every creation of a DLSS feature or an FSR context +(`vk_dlaa.c` `evaluate`, `vk_ffx.cpp` and `vk_ffx4.cpp` `context_for`) trying a refusal once more after a wait for the +GPU to pass what was let go of (`vk_upscaler_gpu_wait`), `upscaler_idle_wait`'s `sections[]` entry and `nx2navi.ini.example` line, a name in `perf.c` for each perf mark kind, and `pb_images` noting the images' last use after it makes them. Any Python, no game files. diff --git a/runtime/vk_ffx.cpp b/runtime/vk_ffx.cpp index 39d444ec..b40c7572 100644 --- a/runtime/vk_ffx.cpp +++ b/runtime/vk_ffx.cpp @@ -125,7 +125,8 @@ ffxReturnCode_t create_counted(ffxContext *ctx, ffxCreateContextDescHeader *desc // a switch keeps at most two of an output size for a few frames, and the game's second scenes, of another output size, // keep their own. When all slots are taken, the least recently used one goes for the new size. A context that goes is // let go of (retire_up) and destroyed once the GPU is past the submission of its last upscale (g_dead, vk_dlaa.c "what -// the upscalers let go of"), without a wait for the device. +// the upscalers let go of"), without a wait for the device; a creation refused while some are still in flight waits +// for the GPU to pass them, and tries once more once they are destroyed (context_for). struct Up { ffxContext ctx = nullptr; uint32_t mw = 0, mh = 0, ow = 0, oh = 0; // the render size it was made for (the largest it takes with drs); the output size @@ -259,6 +260,20 @@ Up *context_for(uint32_t w, uint32_t h, uint32_t ow, uint32_t oh) { d.maxUpscaleSize = {ow, oh}; d.fpMessage = message; ffxReturnCode_t r = create_counted(&slot->ctx, &d.header); + if (r != FFX_API_RETURN_OK && vk_ffx_dead_fence()) { + // Refused while contexts let go of (this file's and FSR 4's) are still in flight: most often their video + // memory, which a wait for the device freed before the creation up to 0.10.1. The GPU finishes with them + // (vk_upscaler_gpu_wait: those submissions only, not the device), they go, and the creation is tried once + // more, as vk_ffx4.cpp context_for and vk_dlaa.c evaluate do, rather than the upscaler failing for the run. + const ffxReturnCode_t r0 = r; + const uint64_t w0 = plat_now_ns(); + const int gone2 = vk_ffx_dead_take(vk_upscaler_gpu_wait(vk_ffx_dead_fence())); + const double waited = (double)(plat_now_ns() - w0) / 1e6; + if (gone2) slot->ctx = nullptr, r = create_counted(&slot->ctx, &d.header); + rt_log("ffx: FSR 3.1 context creation refused (%u) with contexts let go of still in flight: waited %.1f ms for " + "the GPU, %d destroyed, %s", (unsigned)r0, waited, gone2, + !gone2 ? "not tried again" : r == FFX_API_RETURN_OK ? "made at the second try" : "refused again"); + } if (r != FFX_API_RETURN_OK) { rt_log("ffx: FSR 3.1 context creation for %ux%u (render sizes up to %ux%u) failed (%u)", ow, oh, mw, mh, (unsigned)r); slot->ctx = nullptr; diff --git a/tests/upscaler_idle_check.py b/tests/upscaler_idle_check.py index 827d57e1..5fafa5f3 100644 --- a/tests/upscaler_idle_check.py +++ b/tests/upscaler_idle_check.py @@ -10,6 +10,9 @@ It fails when: - one of the places that let something go (vk_dlaa.c free_size, free_snap, stale_sizes, release_kept, hudless_from, pb_images, retire_idle and evaluate, vk_ffx.cpp and vk_ffx4.cpp context_for) does not call vk_upscaler_let_go, which frees what the GPU is past and which the rollback switch turns into a wait for the device; +- one of the places that create a DLSS feature or an FSR context (vk_dlaa.c evaluate, vk_ffx.cpp and vk_ffx4.cpp + context_for) does not call vk_upscaler_gpu_wait: a creation refused while what was let go of is still in flight + then fails for want of its video memory, where the wait for the device of 0.10.1 had freed it first; - NX2NAVI_UPSCALER_IDLE_WAIT has no sections[] entry in cfg.c or no line in nx2navi.ini.example (its known[] name and its metas[] row, and a // that would hide them, are tests/cfg_rows_check.py's); - perf.c's mark_name has another count of names than host.h has PERF_M_* kinds: a kind without a name prints a null @@ -35,6 +38,11 @@ LET_GO = { # functions that let something go and must call vk_upscaler_let_go 'vk_ffx.cpp': ['context_for'], 'vk_ffx4.cpp': ['context_for'], } +REFUSED = { # functions that create a feature or context and must retry a refusal after vk_upscaler_gpu_wait + 'vk_dlaa.c': ['evaluate'], + 'vk_ffx.cpp': ['context_for'], + 'vk_ffx4.cpp': ['context_for'], +} # A function definition: at column 0, a return type, the name, the parameters and the body's brace (the runtime's # style); declarations end with ';' before any '{'. Read in code whose string literals are blanked: extern "C" is # extern " " there. @@ -141,6 +149,14 @@ def main(): fails.append(f'{f}: no function {name}') elif not re.search(r'\bvk_upscaler_let_go\s*\(', b): fails.append(f'{f} {name}: lets something go without vk_upscaler_let_go') + for f, names in REFUSED.items(): + code, funcs = sources[f] + for name in names: + b = body(code, funcs, name) + if b is None: + fails.append(f'{f}: no function {name}') + elif not re.search(r'\bvk_upscaler_gpu_wait\s*\(', b): + fails.append(f'{f} {name}: no retry of a refused creation after vk_upscaler_gpu_wait') key = 'UPSCALER_IDLE_WAIT' cfg = (rt / 'cfg.c').read_text(encoding='utf-8-sig')