9 Commits

Author SHA1 Message Date
chrg6
9065d1485f Let FSR 3.1 and FSR 4 destroy a context they let go of once the GPU is past its last upscale, instead of waiting for the device.
vk_ffx.cpp context_for (two places) and vk_ffx4.cpp context_for (three) waited for the whole device to be idle, with
the queue lock held, on the thread that replays the game's submissions, before they destroyed an upscale context of
another render size, the least recently used one for a new output size, or (FSR 4) every idle one after a refused
creation: a stall of the present's flip, the completion thread and upload submits at every pause-screen size, reload,
window size, dynamic resolution step or mode change with FSR on.

A context that goes is now let go of (retire_up): it goes into a list (vk_up_defer.h) with the timeline value of the
submission of its last upscale (Up.fence, set after each dispatch) and is destroyed on the same thread once the GPU is
past it, at the points where vk_dlaa.c takes its own list (every evaluation, every pass while none runs, and after
something is let go of). The 90-second trims, vk_ffx_trim_idle and vk_ffx_release_idle (the upscaler off, which no
longer needs retire_idle's wait) let go of theirs the same way. FSR 4's create descriptors, which the API asks to stay
live until the context is destroyed, move from the slot into a calloc'd Made that goes with the context, so the slot
can take a new context while the old one waits. A refused FSR 4 creation now waits on the timeline from the host for
the contexts let go of, no queue touched (vk_upscaler_gpu_wait), and tries again when any went; with
NX2NAVI_UPSCALER_IDLE_WAIT=1 that wait and every let go wait for the device as before. docs/architecture.md says
where the upscalers' releases go.

C-5 is narrowed: on the default path no upscaler waits for the device any more, so these waits no longer touch the
frame generation swapchain's own queues; vk_fg.cpp, vk_fg4.cpp, gfx_present.c, vk_hdr.c, vk_fsr.c and vk_color.c keep
theirs.

Not reproduced before the change (no FSR session log here). Every runtime file compiles, vk_dlaa.c, vk_ffx.cpp and
vk_ffx4.cpp with -Wall -Wextra and no warning; vk_ffx4.cpp's non-Windows stubs compile against vk_ffx.h;
up_defer_check passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 20:13:01 -05:00
chrg6
c6b29834eb Give the upscalers' device-idle waits and their releases perf marks of their own, so a slow frame's line names them.
The twelve places where DLSS (vk_dlaa.c) and FSR 3.1 and FSR 4 (vk_ffx.cpp, vk_ffx4.cpp context_for) wait for the
whole device to be idle before they free what the GPU may still use go through one function, vk_upscaler_device_idle,
which NX2NAVI_TRACE=perf times as "upscaler device waits". The releases of DLSS features and of FSR contexts are timed
as "upscaler releases". These waits run on the replaying thread with the queue lock held, so the present's flip, the
completion thread's hold release and upload submits wait with them at each pause-screen size, reload, window size or
mode change; until now only the "DLSS feature creates" mark saw one of them, the wait in release_kept, which it summed
with the create. That mark now times the create alone.

Diagnostics only: nothing waits or frees differently. stutter_map_check, stutter_hitch_check, frame_step_check and
fg_reset_check passed; every runtime file compiles, the changed ones with -Wall -Wextra and no warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 20:12:50 -05:00
chrg6
d9429b0c00 Break the memory report down past VMA, by owner in system memory, and by growth over a session.
Every report adds a line for video memory outside VMA (the driver's usage and budget per heap against VMA's blocks, and counts of pipelines by kind and by last use, stage libraries, compute pipelines, layouts and descriptor pools, with the swapchain, DLSS and FSR sizes), a line that splits the process's commit by owner (the game's heap and maps, device-local memory, VMA's host blocks, the C runtime heap with the shader caches, pipeline tables, texture cache, log queue and flight recorder in it, the driver's pipeline cache data and the rest), and a line naming what grew by more than 64 MiB since the report before and since play began. Every ten minutes of play and at exit a ranking line orders the categories by growth and marks those that only climbed; tests/mem_growth_check.py checks that bookkeeping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 06:46:31 -05:00
chrg6
c7de484a72 Free what DLSS and FSR hold some presents after the upscaler is switched off, and the HUD-less picture once frame generation is off.
Switched off in the menu or failed, the upscaler kept everything it had made for the process: per scene size its
output, motion vector, mask and sharpening images and a DLSS feature (81 MiB of images at 2560x1440 with DLAA, 182 MiB
at 2160 lines, and about 0.5 GB per DLSS feature), the reactive mask's snapshots, the HUD-less pair and the FSR 3.1
and FSR 4 upscale contexts. Eight presents after it goes off they are freed on the replay thread, which makes every
evaluation, after a wait for the device to be idle, as the code already does for a size it replaces. Switched on
again, it makes them anew with a new history, as any switch on does.

The HUD-less picture was made and copied every frame, and kept, after frame generation went off, since the flag that
asks for it was never cleared on Windows: the frame generator's release clears it now, and the pair goes once no frame
generation swapchain is up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 00:44:01 -05:00
epicmeme546
7e938ec897 Make an FSR context for each render size, as AMD's own sample does, instead of one per output size for every mode.
One FSR 4 context took every render scale of the menu, with another render size at each switch and without the dynamic resolution flag. The first frame after a switch from 67 % to 100 % ended the game with an access violation in AMD's driver inside FSR 4's dispatch. AMD's sample waits for the GPU and creates the context anew on every render resolution change, and sets FFX_UPSCALE_ENABLE_DYNAMIC_RESOLUTION only for its dynamic resolution mode.

FSR 3.1 and FSR 4 now keep a context per render size and output size. The previous one is destroyed once no command buffer can hold it (eight upscales later), so a mode switch costs one creation of about 50 ms and two contexts for a few frames. With the game's dynamic resolution on (NX2NAVI_DYN_RES), one context made with the flag takes every step. NX2NAVI_FFX_DEBUG=1 (test) turns on the library's own checks of each dispatch.

Tested on an RX 7900 XT with switches every 15 frames and every 90 frames between every mode and between FSR 4 and FSR 3.1 (32 switches): one context made per switch, the old one gone a few frames later, no failure and no crash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 10:21:47 -05:00
tangerineturtle1
e1cb6301ab Give frame generation and FSR the scene's own near and far planes, field of view and units.
DLSS frame generation (Streamline and the NGX path) and FSR 3.1 and 4 upscaling and frame generation were told the depth came from a camera with near 0.1 and no far plane. Ocarina's projection has a finite far plane that changes with the camera: in Hyrule Field its z row is (-1.00001, -0.200001), near 0.1 and far about 25,000; the camera before it had far about 1,000. With the far plane taken as missing, a surface 10,000 units away was read as 16,650. The planes, and whether the depth is inverted, now come from the frame's projection and the device's depth mode every frame (vk_dlaa.c proj_planes), with one log line when they change. The stored depth is already 0 near and 1 far: the -w..w clip depth the pipelines keep maps to exactly what a 0..w projection with the same planes stores, and a frame dump's scene depth spans 0.975 to 1.0.

FSR's contexts lose their infinite-depth flag and take the far plane from each dispatch. FSR frame generation now gets the camera's field of view instead of a fixed 1 radian, and the upscaler gets it too (its caller passed 0). The view's units are metres, not the centimetres FSR was told: the camera orbits Link about 4 units behind him at about 3 units a second.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:09:15 -05:00
epicmeme546
b5789c6bec Keep one FSR 4 and one FSR 3.1 context per output size, free the ones no upscale used for 90 seconds, and let FSR 4 recover from failed creations.
Each upscaling mode made a context of its own (about 217 MiB of video memory for FSR 4 at 3440x1440, 50 to 70 ms each), up to six were kept beside FSR 3.1's, and three FSR 4 creations that failed in a row turned FSR 4 off for the run: the menu then showed FSR 3.1 and no longer listed FSR 4. A player's log had the third failure (error 3) right after stepping through the modes with ten contexts alive. A context now takes every render size up to its output size, which the API allows at each dispatch; a creation that fails frees every context not in use, FSR 3.1's idle ones too, and is tried again; FSR 3.1 serves the frames between tries two seconds apart; only four failures in a row turn FSR 4 off, and choosing it in the menu again tries it anew.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 12:33:36 -05:00
epicmeme546
5bdf583331 Add frame generation through AMD's FSR SDK 2.4 as a setting to try with FSR 4, and never generate a frame without the HUD-less picture.
runtime/vk_fg4.cpp makes the frame generation swapchain and context with the libraries FSR 4 comes with
(amd_fidelityfx_loader_vk.dll and amd_fidelityfx_framegeneration_vk.dll beside the executable). vk_fg.cpp takes
that path only with fsr4_fg = 1 while FSR 4 is the upscaler; the default stays the FSR 3.1 library. The loader
picks the version for the card: the machine-learned FSR Frame Generation 4 on Radeon RX 9000 cards with Windows 11,
which is untested, and 3.1.6 elsewhere. On an RX 7900 XT (driver 26.10.1) it lists frame generation 3.1.6 and the
swapchains 4.0.0, which the driver brings and the loader chooses, and 3.1.7. The swapchain in place is wanted only
while its library is the one to use, so a change of the setting or of the upscaler, or a failure of this path,
takes it down as switching frame generation off does and the next present makes it with the other library.

The preparation gives this library the camera's position and axes, which FSR Frame Generation 4 requires, and a
scene scale of one metre a unit: with the 0.01 the FSR 3.1 path uses, version 3.1.6 drew grey translucent shapes
into the sky of its frames while the camera turned (160 captures of the screen during a camera sweep; clean with 1).

Both paths now generate a frame only when the picture without the HUD is there for it: generated from the
presented picture alone, the HUD would be warped with the scene behind it. In four configurations (21:9 with either
library, a 16:9 picture in a 21:9 window, a 1080 picture without HDR) every generated frame had it, and the log
counts the frames without. On screen, of the 6,404 pixels of the HUD's item button that stayed fixed through a
camera sweep with the FSR 3.1 library, 6,162 stayed fixed with this one.

Standing in Hyrule Field at 3440x1440 with FSR 4 Quality the game draws 43.0 frames a second with this path and
42.2 with the FSR 3.1 library, each shown twice over. One run changed the present mode, HDR, fullscreen, frame
generation, FSR 4, this setting and the upscaler in turn: 216 screenshots without a bad frame, and no failure.

The SDK's frame generation headers join runtime/third_party/ffx_sdk, unmodified. tools/make_kit.py and
tools/make_launcher.py take the third library from the --ffx4 folder when it is there, and tools/make_game.py
copies it beside the game.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 04:49:01 -05:00
epicmeme546
4d5454d380 Add AMD FSR 4 upscaling through the libraries of AMD's FSR SDK 2.4, as an Upscaling choice beside FSR 3.1.
runtime/vk_ffx4.cpp loads amd_fidelityfx_loader_vk.dll from beside the executable and asks it which upscaler
versions the device can run. Where a 4.x version is listed (Radeon RX 6000, RX 7000 and RX 9000 cards with AMD's
driver 26.10.1 or newer) the menu's Upscaling row gains AMD FSR 4 Native AA, Quality, Balanced, Performance and
Ultra Performance, and fsr4 = 1 runs it in FSR 3.1's place with the same inputs. Its contexts are made with that
version asked for by its id and the provider is asked afterwards which version it runs, so no other upscaler can
be shown under the name. When a context or a dispatch fails, FSR 3.1 makes the frame, and after three failures
FSR 4 is off for the run. Each upscaler starts its history anew on the frame after a change from one to the other.
The setting is off by default and the Linux build compiles stubs.

On an RX 7900 XT (driver 26.10.1, 3440x1440, HDR) the library runs FSR 4.1.2: a context takes 54 ms to make and
217 MiB of video memory. Standing in Hyrule Field: Quality 47.8 fps where FSR 3.1 gives 51.5, Native AA 41.4.
Screenshots of 601 consecutive frames standing, 801 during a camera sweep, 701 each in Kakariko and the
graveyard, and 1,301 across changes between FSR 3.1 and FSR 4, their modes and off show no flicker that FSR 3.1
does not have, and it runs together with FSR 3.1 frame generation.

The SDK's API headers are in runtime/third_party/ffx_sdk, unmodified, with the licence. tools/make_kit.py and
tools/make_launcher.py take AMD's two signed libraries with --ffx4, and tools/make_game.py copies them beside the
game when the kit has both.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 03:22:08 -05:00