diff --git a/docs/README-BuildKit.md b/docs/README-BuildKit.md index 84dbb72b..1a54f9ae 100644 --- a/docs/README-BuildKit.md +++ b/docs/README-BuildKit.md @@ -110,6 +110,10 @@ memory runs short, it ends the process, so while less than the larger of 4 GB an drops stale textures and evicts idle ones. The log names the limits at the first check (`platform: memory limits`). That cannot give back what the game needs after its start: about 10 GB in Kokiri Forest at a 2160p game resolution. +AMD FSR 3.1 loads AMD's `amd_fidelityfx_vk.dll`, which only the Windows build can use: on Linux the menu offers neither +FSR 3.1 upscaling nor FSR 3.1 frame generation, and `taa = fsr3` or `framegen = fsr3` in `nx2navi.ini` leave the +game's own scaling and swapchain as they are (the log says so). + `tools/make_appimage.py` packs this kit into one `NX2navi-Linux--x86_64.AppImage` with its own Python, for players without a source checkout: it checks for the packages above, installs zbic with pip on its first run, picks `--jobs` from the memory, builds into `~/Games/Ocarina` (or the folder given after the dump) and writes `play.sh` @@ -122,10 +126,11 @@ beside the game. "Preparing shaders" once while the driver rebuilds them. With a driver that has Vulkan's graphics pipeline library it lasts at most about 20 seconds once the shaders are translated, and the remaining pipelines are built in the background while you play. -- **Screen** in the menu has Texture filtering (anisotropic, up to 16x, at once), Upscaling (DLSS on NVIDIA RTX cards, FSR 3.1 on any card), Frame generation (NVIDIA DLSS - on RTX 40 and 50 cards, with a DLSS multiplier of 2x, or 3x and 4x on RTX 50 cards; FSR 3.1 on any card; it needs - DLSS or FSR 3.1 upscaling on, and the frame limit then counts the frames on screen; with no limit chosen it is the - display's refresh rate), HDR (the game's picture tone mapped to your display, with its own grading rows, or Native, the game's own HDR; turn HDR on in Windows first) and the +- **Screen** in the menu has Texture filtering (anisotropic, up to 16x, at once), Upscaling (DLSS on NVIDIA RTX cards, + FSR 3.1 on any card where `amd_fidelityfx_vk.dll` loads), Frame generation (NVIDIA DLSS on RTX 40 and 50 cards, with + a DLSS multiplier of 2x, or 3x and 4x on RTX 50 cards; FSR 3.1 on any card where it loads; it needs DLSS or FSR 3.1 + upscaling on, and the frame limit then counts the frames on screen; with no limit chosen it is the display's refresh + rate), HDR (the game's picture tone mapped to your display, with its own grading rows, or Native, the game's own HDR; turn HDR on in Windows first) and the resolution. Those marked "after restart" take effect when the game starts again. - **Controls** in the menu has Button prompts: the game's own, or Xbox, PlayStation or keyboard and mouse ones when the build drew them (a kit with `prompt-art`). It takes effect after a restart. The keyboard and mouse prompts show diff --git a/nx2navi.ini.example b/nx2navi.ini.example index 6e90252d..c09d717e 100644 --- a/nx2navi.ini.example +++ b/nx2navi.ini.example @@ -379,12 +379,12 @@ # The game's own FXAA after the post effects; with DLAA on it is not needed. Changes while the game runs take effect at # once. # fxaa = 1 -# Frame generation: off, fsr3 (AMD FSR 3.1 on any GPU: one generated frame between every two the game draws, the HUD -# kept out of it) or dlss (NVIDIA DLSS Frame Generation on GeForce RTX 40 and 50 GPUs, paced with Reflex; it needs -# nx2navi_dlssg.dll and NVIDIA Streamline's files next to the executable, which the setup puts there). Both need DLSS or -# FSR 3.1 upscaling (dlaa = 1). While frame generation runs, fps_limit counts the frames on screen, generated ones -# included: the game draws the limit divided by the multiplier (2 for fsr3). With fps_limit unset it is the display's -# rate. +# Frame generation: off, fsr3 (AMD FSR 3.1 on any GPU where amd_fidelityfx_vk.dll loads, so not on Linux: one generated +# frame between every two the game draws, the HUD kept out of it) or dlss (NVIDIA DLSS Frame Generation on GeForce RTX +# 40 and 50 GPUs, paced with Reflex; it needs nx2navi_dlssg.dll and NVIDIA Streamline's files next to the executable, +# which the setup puts there). Both need DLSS or FSR 3.1 upscaling (dlaa = 1). While frame generation runs, fps_limit +# counts the frames on screen, generated ones included: the game draws the limit divided by the multiplier (2 for fsr3). +# With fps_limit unset it is the display's rate. # fsr3 takes effect at once, dlss (on or off) at the next start. # framegen = off # DLSS frame generation's multiplier: the frames on screen for each frame the game draws, 2, 3 or 4 (one, two or three diff --git a/runtime/ui/menu.cpp b/runtime/ui/menu.cpp index e44685cc..451a1f13 100644 --- a/runtime/ui/menu.cpp +++ b/runtime/ui/menu.cpp @@ -67,6 +67,8 @@ int vk_hdr_wanted(void); int vk_hdr_active(void); int vk_hdr_needs_restart(void); int vk_dlaa_needs_restart(void); +int vk_ffx_available(void); // vk_ffx.cpp: FSR 3.1 upscaling can run (asked without loading the library) +int vk_ffx_framegen_available(void); // ... and FSR 3.1 frame generation double frame_step_limit_hz(void); int frame_limit_follows_display(void); int frame_limit_adjustable(void); @@ -798,14 +800,15 @@ static bool dlss_offered(void) { // (DLSS or FSR 3.1 on) with NX2NAVI_TAA (which of them) and NX2NAVI_DLSS (the mode, the render scale: widescreen.c // wide_present). A temporal mode fits the picture with bilinear, the upscaler having made it; DLSS's model is NVIDIA's own // for the mode (vk_dlaa.c evaluate, dlss_preset at its default). The list: bilinear, FSR 1, the DLSS modes where DLSS -// runs (dlss_offered), the FSR 3.1 modes; a DLSS mode still set where DLSS does not run stays listed last until another -// choice replaces it, so the row shows what is set. +// runs (dlss_offered), the FSR 3.1 modes where FSR 3.1 loads (vk_ffx_available); a DLSS or FSR 3.1 mode still set where +// it does not run stays listed last until another choice replaces it, so the row shows what is set. // The Upscaling row's name of what is set now, for the performance overlay. static void upscaling_name(char *out, size_t size) { static const char *const modes[] = {"dlaa", "quality", "balanced", "performance", "ultra_performance"}; static const UiTextId dlss[] = {UI_MENU_DLSS_DLAA, UI_MENU_DLSS_QUALITY, UI_MENU_DLSS_BALANCED, UI_MENU_DLSS_PERFORMANCE, UI_MENU_DLSS_ULTRA}; static const UiTextId fsr3[] = {UI_MENU_FSR3_NATIVE, UI_MENU_DLSS_QUALITY, UI_MENU_DLSS_BALANCED, UI_MENU_DLSS_PERFORMANCE, UI_MENU_DLSS_ULTRA}; - if (!cfg_bool("NX2NAVI_DLAA")) { + // FSR 3.1 set where it cannot load runs nothing (vk_dlaa.c vk_dlaa_fsr3): the game scales the picture as the rest says + if (!cfg_bool("NX2NAVI_DLAA") || (!strcmp(cfg_value("NX2NAVI_TAA"), "fsr3") && !vk_ffx_available())) { if (strcmp(cfg_value("NX2NAVI_UPSCALER"), "fsr")) snprintf(out, size, "%s", menu_text(UI_MENU_UPSCALER_BILINEAR)); else snprintf(out, size, menu_text(UI_MENU_OV_FMT_FSR), cfg_int("NX2NAVI_SHARPNESS")); return; @@ -849,18 +852,23 @@ static void row_upscaling(void) { for (int i = 0; i < 5; i++) if (!strcmp(cfg_value("NX2NAVI_DLSS"), modes[i])) now.mode = i; } - const bool dlss = dlss_offered(); + const bool dlss = dlss_offered(), fsr = vk_ffx_available(); add(BILINEAR, 0); add(FSR1, 0); for (int i = 0; dlss && i < 5; i++) add(DLSS, i); - for (int i = 0; i < 5; i++) add(FSR3, i); + for (int i = 0; fsr && i < 5; i++) add(FSR3, i); if (!dlss && now.kind == DLSS) add(DLSS, now.mode); + if (!fsr && now.kind == FSR3) add(FSR3, now.mode); int cur = 0; for (int i = 0; i < n; i++) if (list[i].kind == now.kind && list[i].mode == now.mode) cur = i; + // FSR 3.1 still set where it cannot load: nothing upscales, so the row says so under its value + const char *note = nullptr; + if (!fsr && now.kind == FSR3) note = menu_text(UI_MENU_FSR3_OFF); + else if (dlss && vk_dlaa_needs_restart()) note = menu_text(UI_MENU_AFTER_RESTART); int nv = opt_choice_index("NX2NAVI_UPSCALER", UI_MENU_UPSCALING, names, n, cur, 1, - dlss ? UI_MENU_UPSCALING_DESC : UI_MENU_UPSCALING_DESC_FSR, - dlss && vk_dlaa_needs_restart() ? menu_text(UI_MENU_AFTER_RESTART) : nullptr); + !fsr ? UI_MENU_UPSCALING_DESC_NO_FSR : dlss ? UI_MENU_UPSCALING_DESC : UI_MENU_UPSCALING_DESC_FSR, + note); if (nv < 0 || nv >= n) return; const Entry e = list[nv]; opt_apply("NX2NAVI_UPSCALER", e.kind == FSR1 ? "fsr" : "bilinear"); @@ -935,25 +943,41 @@ static void row_framegen_mult(bool restart) { // Frame generation (vk_fg.cpp, at once; NVIDIA's after a restart); it needs a temporal upscaler's motion vectors static void row_framegen(void) { - // the generated frames are made from DLSS's or FSR 3.1's motion vectors: without one of them it would do nothing - const bool dlss = dlss_offered(), upscaled = cfg_bool("NX2NAVI_DLAA"); - if (!upscaled) opt_disable_next(dlss ? UI_MENU_FRAMEGEN_NEEDS : UI_MENU_FRAMEGEN_NEEDS_FSR); - static const char *const values[] = {"off", "fsr3", "dlss"}; - const char *names[] = {menu_text(UI_MENU_FRAMEGEN_OFF), "AMD FSR 3.1", "NVIDIA DLSS"}; + // the generated frames are made from DLSS's or FSR 3.1's motion vectors: without one of them it would do nothing, + // and FSR 3.1 set where it cannot load (vk_ffx_available) is none + const bool dlss = dlss_offered(), fsr = vk_ffx_framegen_available(); + const bool upscaled = cfg_bool("NX2NAVI_DLAA") && (strcmp(cfg_value("NX2NAVI_TAA"), "fsr3") || vk_ffx_available()); const char *v = cfg_value("NX2NAVI_FRAMEGEN"); int cur = !strcmp(v, "fsr3") ? 1 : !strcmp(v, "dlss") ? 2 : 0; - // NVIDIA DLSS (vk_dlssg.c, through Streamline and nx2navi_dlssg.dll) is listed on NVIDIA GPUs now that Reflex paces it - // from the frame's start and generated frames are presented; elsewhere only while it is set - int n = dlss || cur == 2 ? 3 : 2; + // what the texts promise follows what runs here: DLSS's frame generation, AMD's, both or neither + const UiTextId desc = dlss ? (fsr ? UI_MENU_FRAMEGEN_DESC : UI_MENU_FRAMEGEN_DESC_DLSS) + : (fsr ? UI_MENU_FRAMEGEN_DESC_FSR : UI_MENU_FRAMEGEN_DESC_NONE); + UiTextId note = dlss ? (fsr ? UI_MENU_FRAMEGEN_NOTE : UI_MENU_FRAMEGEN_NOTE_DLSS) + : (fsr ? UI_MENU_FRAMEGEN_NOTE_FSR : UI_MENU_FRAMEGEN_NOTE); // neither: the general note + if (!dlss && !fsr && cur == 0) opt_disable_next(UI_MENU_FRAMEGEN_NEEDS_NONE); + else if (!upscaled) opt_disable_next(dlss ? (fsr ? UI_MENU_FRAMEGEN_NEEDS : UI_MENU_FRAMEGEN_NEEDS_DLSS) + : (fsr ? UI_MENU_FRAMEGEN_NEEDS_FSR : UI_MENU_FRAMEGEN_NEEDS_NONE)); + // Off, AMD's where FSR 3.1 loads and NVIDIA DLSS (vk_dlssg.c, through Streamline and nx2navi_dlssg.dll) on NVIDIA GPUs, + // now that Reflex paces it from the frame's start and generated frames are presented; elsewhere one stays listed + // while it is set + static const char *const all_values[] = {"off", "fsr3", "dlss"}; + const char *all_names[] = {menu_text(UI_MENU_FRAMEGEN_OFF), "AMD FSR 3.1", "NVIDIA DLSS"}; + const char *values[3], *names[3]; + int n = 0, at = 0; + for (int i = 0; i < 3; i++) { + if ((i == 1 && !fsr && cur != 1) || (i == 2 && !dlss && cur != 2)) continue; + if (i == cur) at = n; + values[n] = all_values[i], names[n++] = all_names[i]; + } // AMD's follows the setting; NVIDIA's is set up with the device, so only a change to or from it waits for a restart const bool restart = dlss && (cur == 2) != (vk_dlssg_wanted() != 0); - UiTextId note = restart ? UI_MENU_AFTER_RESTART : dlss ? UI_MENU_FRAMEGEN_NOTE : UI_MENU_FRAMEGEN_NOTE_FSR; + if (restart) note = UI_MENU_AFTER_RESTART; + else if (cur == 1 && !fsr) note = UI_MENU_FSR3_OFF; // set where it cannot load: "in use: off" and why // NVIDIA's set since the start but not running: "in use: off" and why (vk_dlssg_status; the log has the details) const int st = cur == 2 && !restart ? vk_dlssg_status() : DLSSG_RUNS; if (st == DLSSG_NO_GPU) note = UI_MENU_FRAMEGEN_OFF_GPU; else if (st == DLSSG_NO_FILES) note = UI_MENU_FRAMEGEN_OFF_FILES; - int nv = opt_choice_index("NX2NAVI_FRAMEGEN", UI_MENU_FRAMEGEN, names, n, cur, 0, - dlss ? UI_MENU_FRAMEGEN_DESC : UI_MENU_FRAMEGEN_DESC_FSR, menu_text(note)); + int nv = opt_choice_index("NX2NAVI_FRAMEGEN", UI_MENU_FRAMEGEN, names, n, at, 0, desc, menu_text(note)); if (nv >= 0 && nv < n) opt_apply("NX2NAVI_FRAMEGEN", values[nv]); else if (cur == 2 && upscaled && dlss) row_framegen_mult(restart); } @@ -989,7 +1013,7 @@ static void tab_screen(float scale) { row_hdr(); // The sharpening runs under FSR 1 (vk_fsr.c vk_fsr_record, RCAS) and FSR 3.1 (vk_ffx.cpp, its own RCAS); with // bilinear or DLSS the slider would do nothing - int dl = cfg_bool("NX2NAVI_DLAA"), fsr3 = dl && !strcmp(cfg_value("NX2NAVI_TAA"), "fsr3"); + int dl = cfg_bool("NX2NAVI_DLAA"), fsr3 = dl && !strcmp(cfg_value("NX2NAVI_TAA"), "fsr3") && vk_ffx_available(); if (!fsr3 && (strcmp(cfg_value("NX2NAVI_UPSCALER"), "fsr") || dl)) opt_disable_next(UI_MENU_SHARPNESS_NEEDS_FSR); opt_slider("NX2NAVI_SHARPNESS", UI_MENU_SHARPNESS, 0, 100, UI_MENU_SHARPNESS_DESC, 5, "%.0f %%"); // Anisotropic filtering of the surface textures that tile (vk_texture.c aniso_raised), at once (vk_sampler_aniso_live) @@ -1540,7 +1564,8 @@ static void perf_picture(const MenuTarget *t, PerfPanel *p) { const char *hdr = cfg_value("NX2NAVI_HDR"); snprintf(v[8], sizeof v[8], "%s", menu_text(!strcmp(hdr, "native") ? UI_MENU_HDR_NATIVE : cfg_bool("NX2NAVI_HDR") ? UI_MENU_HDR_TONEMAPPED : UI_MENU_FRAMEGEN_OFF)); const char *fg = cfg_value("NX2NAVI_FRAMEGEN"); - snprintf(v[9], sizeof v[9], "%s", !strcmp(fg, "fsr3") ? "AMD FSR 3.1" : !strcmp(fg, "dlss") ? "NVIDIA DLSS" : menu_text(UI_MENU_FRAMEGEN_OFF)); + const bool fsr_fg = !strcmp(fg, "fsr3") && vk_ffx_framegen_available(); // set but not loadable: off + snprintf(v[9], sizeof v[9], "%s", fsr_fg ? "AMD FSR 3.1" : !strcmp(fg, "dlss") ? "NVIDIA DLSS" : menu_text(UI_MENU_FRAMEGEN_OFF)); if (!strcmp(fg, "dlss") && vk_dlssg_active()) // its multiplier as set, clamped to what this GPU allows snprintf(v[9], sizeof v[9], "NVIDIA DLSS %dx", vk_dlssg_multiplier_of(cfg_value("NX2NAVI_FRAMEGEN_MULTIPLIER"), vk_dlssg_multiplier_max(nullptr), nullptr)); diff --git a/runtime/ui_text.c b/runtime/ui_text.c index 8d83cb7b..3314e859 100644 --- a/runtime/ui_text.c +++ b/runtime/ui_text.c @@ -552,6 +552,15 @@ static const char *const text_en[UI_TEXT_COUNT] = { [UI_MENU_FRAMEGEN_NEEDS_FSR] = "choose an FSR 3.1 mode under Upscaling first", [UI_MENU_FRAMEGEN_NOTE_FSR] = "needs FSR 3.1", [UI_MENU_FRAMEGEN_DESC_FSR] = "Adds generated frames for smoother motion. Needs FSR 3.1.", + [UI_MENU_UPSCALING_DESC_NO_FSR] = "How the picture is smoothed and scaled to your window. AMD FSR 3.1 is not offered " + "here: it needs amd_fidelityfx_vk.dll, which cannot be loaded.", + [UI_MENU_FSR3_OFF] = "in use: off, FSR 3.1 is not available", + [UI_MENU_FRAMEGEN_DESC_DLSS] = "Adds generated frames for smoother motion. Needs DLSS.", + [UI_MENU_FRAMEGEN_NOTE_DLSS] = "needs DLSS", + [UI_MENU_FRAMEGEN_NEEDS_DLSS] = "choose a DLSS mode under Upscaling first", + [UI_MENU_FRAMEGEN_DESC_NONE] = "Adds generated frames for smoother motion. Needs DLSS or FSR 3.1, and neither runs " + "here.", + [UI_MENU_FRAMEGEN_NEEDS_NONE] = "needs DLSS or FSR 3.1, which do not run here", [UI_MENU_FRAMEGEN_MULT] = "DLSS multiplier", [UI_MENU_FRAMEGEN_MULT_DESC] = "Frames on screen for each frame the game draws: 2x adds one generated frame, 3x " "two, 4x three. Higher is smoother but adds a little delay. The frame limit counts " diff --git a/runtime/ui_text.h b/runtime/ui_text.h index 657bd2f2..5272920c 100644 --- a/runtime/ui_text.h +++ b/runtime/ui_text.h @@ -275,6 +275,11 @@ typedef enum { // the same rows' texts on a GPU DLSS does not run on (not NVIDIA): the menu leaves DLSS out there UI_MENU_UPSCALING_DESC_FSR, UI_MENU_FRAMEGEN_DESC_FSR, UI_MENU_FRAMEGEN_NOTE_FSR, UI_MENU_FRAMEGEN_NEEDS_FSR, + // and on a build where FSR 3.1 does not load (vk_ffx_available): the menu leaves it out there, and these texts say so + UI_MENU_UPSCALING_DESC_NO_FSR, + UI_MENU_FSR3_OFF, // FSR 3.1 is still set but not loadable: "in use: off" and why + UI_MENU_FRAMEGEN_DESC_DLSS, UI_MENU_FRAMEGEN_NOTE_DLSS, UI_MENU_FRAMEGEN_NEEDS_DLSS, // DLSS only + UI_MENU_FRAMEGEN_DESC_NONE, UI_MENU_FRAMEGEN_NEEDS_NONE, // neither runs // DLSS frame generation (ui/menu.cpp row_framegen, row_framegen_mult): the multiplier row, its notes when this GPU // and driver allow less than 4x, and the frame generation row's notes when DLSS's does not run UI_MENU_FRAMEGEN_MULT, UI_MENU_FRAMEGEN_MULT_DESC, diff --git a/runtime/vk_dlaa.c b/runtime/vk_dlaa.c index b120d055..0a254d3b 100644 --- a/runtime/vk_dlaa.c +++ b/runtime/vk_dlaa.c @@ -138,7 +138,9 @@ int vk_dlaa_wanted(void) { // inputs. The DLSS mode setting (NX2NAVI_DLSS) is the render scale for both. static int fsr3(void) { return !N.ngx_up && !strcmp(cfg_value("NX2NAVI_TAA"), "fsr3"); } static int fsr_frame; // fsr3() as vk_dlaa_init and then vk_dlaa_frame read it: the upscaler this frame runs with -int vk_dlaa_fsr3(void) { return vk_dlaa_wanted() && fsr3(); } +// FSR 3.1 is the upscaler and its library loads (vk_ffx_available): widescreen.c shrinks the scene only for an upscaler +// that runs. +int vk_dlaa_fsr3(void) { return vk_dlaa_wanted() && fsr3() && vk_ffx_available(); } // The upscaler the menu has set cannot start in this run: DLSS when NGX was never tried (a device made without its // extensions), FSR 3.1 while NGX is up and serves the run. A DLSS this PC could not bring up is not waiting for a restart. @@ -306,7 +308,7 @@ void vk_dlaa_init(void) { if (vk_dlaa_wanted() && fsr3()) { fsr_frame = 1; N.available = vk_ffx_load(); - rt_log("dlaa: the temporal upscaler is AMD FSR 3.1%s", N.available ? "" : ", which is missing: the game's own scaling"); + rt_log("dlaa: the temporal upscaler is AMD FSR 3.1%s", N.available ? "" : ", which is not available: the game's own scaling"); return; } if (!vk_dlaa_wanted() || !core_load()) return; diff --git a/runtime/vk_dlaa.h b/runtime/vk_dlaa.h index f381ac0b..1d896d0a 100644 --- a/runtime/vk_dlaa.h +++ b/runtime/vk_dlaa.h @@ -8,7 +8,7 @@ #define DLAA_SCENE_MIN_W 320u int vk_dlaa_wanted(void); // the setting is on (the device may still lack DLSS) -int vk_dlaa_fsr3(void); +int vk_dlaa_fsr3(void); // the setting is on and its upscaler is AMD FSR 3.1, which loads int vk_dlaa_needs_restart(void); // the menu's upscaler cannot start in this run // Frame generation (vk_fg.cpp): the depth and motion vectors of the frame DLSS or FSR 3.1 last ran on, with its jitter // and whether its history reset; 1 once per evaluation (0: none since the last call). The images stay valid until the diff --git a/runtime/vk_ffx.cpp b/runtime/vk_ffx.cpp index 3cc27aca..8f4d3f16 100644 --- a/runtime/vk_ffx.cpp +++ b/runtime/vk_ffx.cpp @@ -9,6 +9,7 @@ #ifdef _WIN32 #include #endif +#include #include #include #include @@ -38,6 +39,19 @@ struct Api { int tried = 0, ok = 0; } A; +// What the settings menu may ask before anything has used FSR 3.1 (vk_ffx_available), from any thread. +std::atomic g_loaded{-1}; // what vk_ffx_load found: 1 or 0; -1 while it has not run +std::atomic g_present{-1}; // whether the library file is there: 1 or 0; -1 while not asked + +#ifdef _WIN32 +// amd_fidelityfx_vk.dll next to the executable +void library_path(char *path, size_t size) { + DWORD n = GetModuleFileNameA(NULL, path, (DWORD)size); + while (n && path[n - 1] != '\\') n--; + snprintf(path + n, size - n, "amd_fidelityfx_vk.dll"); +} +#endif + void message(uint32_t type, const wchar_t *msg) { char s[512]; size_t n = 0; @@ -134,12 +148,11 @@ extern "C" int vk_ffx_load(void) { A.tried = 1; #ifdef _WIN32 char path[MAX_PATH]; - DWORD n = GetModuleFileNameA(NULL, path, MAX_PATH); - while (n && path[n - 1] != '\\') n--; - snprintf(path + n, sizeof path - n, "amd_fidelityfx_vk.dll"); + library_path(path, sizeof path); HMODULE m = LoadLibraryA(path); if (!m) { rt_log("ffx: %s not found: FSR 3.1 off", path); + g_loaded.store(0, std::memory_order_release); return 0; } A.lib = m; @@ -148,12 +161,36 @@ extern "C" int vk_ffx_load(void) { A.configure = (PfnFfxConfigure)(void *)GetProcAddress(m, "ffxConfigure"); A.query = (PfnFfxQuery)(void *)GetProcAddress(m, "ffxQuery"); A.dispatch = (PfnFfxDispatch)(void *)GetProcAddress(m, "ffxDispatch"); -#endif A.ok = A.create && A.destroy && A.configure && A.query && A.dispatch; rt_log("ffx: amd_fidelityfx_vk.dll %s", A.ok ? "loaded" : "lacks the API entry points: FSR 3.1 off"); +#else + // Only the Windows build loads AMD's library: there is nothing to load here + rt_log("ffx: this build has no FSR 3.1 (it needs amd_fidelityfx_vk.dll, which only the Windows build loads): FSR 3.1 off"); +#endif + g_loaded.store(A.ok, std::memory_order_release); return A.ok; } +extern "C" int vk_ffx_available(void) { + int loaded = g_loaded.load(std::memory_order_acquire); + if (loaded >= 0) return loaded; +#ifdef _WIN32 + int present = g_present.load(std::memory_order_relaxed); + if (present < 0) { // the menu asks every frame: look once, and leave the loading to the first use + char path[MAX_PATH]; + library_path(path, sizeof path); + DWORD attr = GetFileAttributesA(path); + present = attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY); + g_present.store(present, std::memory_order_relaxed); + } + return present; +#else + return 0; +#endif +} + +extern "C" int vk_ffx_framegen_available(void) { return vk_ffx_available(); } // the same library, its API calls + extern "C" int vk_ffx_upscale(VkCommandBuffer cb, const VkFfxImage *color, const VkFfxImage *depth, const VkFfxImage *mv, const VkFfxImage *out, float jx, float jy, int reset, float dt_ms, float fov_y) { if (!vk_ffx_load()) return 0; diff --git a/runtime/vk_ffx.h b/runtime/vk_ffx.h index f1f640f1..3feb8e0d 100644 --- a/runtime/vk_ffx.h +++ b/runtime/vk_ffx.h @@ -10,6 +10,12 @@ typedef struct { } VkFfxImage; int vk_ffx_load(void); // 1 when the library is there with its entry points (loaded once) +// Whether FSR 3.1 upscaling can run in this build, for the settings menu: after vk_ffx_load ran, what it found; before, +// whether the library file is there, asked once without loading it. 0 where the build has no FSR 3.1 to load. +int vk_ffx_available(void); +// The same for frame generation (vk_fg.cpp), which runs through the library's own API calls (vk_ffx_create and the +// rest below), not only an upscaler. +int vk_ffx_framegen_available(void); // Records FSR 3.1 into cb: color (the scene light, render size), depth (D32F), mv (R16G16F, render pixels, current to // previous), out (the output size, RGBA16F, storage). jx, jy: the jitter the scene took, render pixels. fov_y in // radians (0: unknown). 0 on failure (the caller falls back as it does when DLSS fails). diff --git a/runtime/vk_fg.cpp b/runtime/vk_fg.cpp index 5673d1bc..107c4717 100644 --- a/runtime/vk_fg.cpp +++ b/runtime/vk_fg.cpp @@ -79,14 +79,21 @@ void fail(const char *what, unsigned r) { } // namespace // Follows the setting: gfx_present.c's window_settings_live remakes the swapchain when this stops matching vk_fg_active. -// It needs DLSS's or FSR 3.1's motion vectors, so it also needs one of them on. Not on a device made through +// It needs DLSS's or FSR 3.1's motion vectors, so it also needs one of them on, and the FSR 3.1 library: a build +// without it keeps the plain swapchain whatever the setting says, with one line in the log. Not on a device made through // Streamline's proxies (NVIDIA's frame generation set at the start, vk_dlssg_dpa): AMD's swapchain there crashed in // NVIDIA's driver when the menu switched from NVIDIA's frame generation to AMD's. That switch waits for a restart, // as the menu says (menu.cpp row_framegen). Not after a failure (fail): the present code then takes AMD's swapchain // down as it does for a switch in the menu, and a plain one follows. extern "C" int vk_fg_wanted(void) { - return !F.failed && !strcmp(cfg_value("NX2NAVI_FRAMEGEN"), "fsr3") && cfg_bool("NX2NAVI_DLAA") && vk_dlaa_wanted() && - !vk_dlssg_dpa(); + if (F.failed || strcmp(cfg_value("NX2NAVI_FRAMEGEN"), "fsr3") || !cfg_bool("NX2NAVI_DLAA") || !vk_dlaa_wanted() || + vk_dlssg_dpa()) + return 0; + if (vk_ffx_framegen_available()) return 1; + static int told; + if (!__atomic_exchange_n(&told, 1, __ATOMIC_RELAXED)) + rt_log("fg: AMD FSR 3.1 frame generation is set, but FSR 3.1 is not available here: off"); + return 0; } // Always the three queues: the setting can be switched on during a run, and idle queues cost nothing. diff --git a/runtime/vk_fg.h b/runtime/vk_fg.h index 23db7e93..32899f42 100644 --- a/runtime/vk_fg.h +++ b/runtime/vk_fg.h @@ -3,7 +3,7 @@ #include #include -int vk_fg_wanted(void); // NX2NAVI_FRAMEGEN = fsr3 with DLSS or FSR 3.1 upscaling on (live) +int vk_fg_wanted(void); // NX2NAVI_FRAMEGEN = fsr3 with DLSS or FSR 3.1 upscaling on (live), where FSR 3.1 loads uint32_t vk_fg_queue_count(void); // graphics-family queues the device creates void vk_fg_queues(VkQueue present, VkQueue acquire); // vk_device.c, after the device exists int vk_fg_active(void); // the frame generation swapchain exists diff --git a/runtime/widescreen.c b/runtime/widescreen.c index 0449c5a8..7427e55f 100644 --- a/runtime/widescreen.c +++ b/runtime/widescreen.c @@ -80,8 +80,9 @@ static unsigned picture_height(void) { // RES_FIXED_AFTER presents after the first, once the controller has filled the fields the fixed path keeps. #define RES_FIXED_AFTER 120 // The share follows the upscaler that runs, not the setting: DLSS starts only after a restart and only where NGX came up -// (vk_dlaa_available), so a mode picked in the menu, or one this PC cannot run, no longer shrinks the scene with nothing -// to scale it back up. FSR 3.1 and a mode change while one runs apply at once. +// (vk_dlaa_available), and FSR 3.1 only where its library loads (vk_dlaa_fsr3), so a mode picked in the menu, or one +// this PC cannot run, no longer shrinks the scene with nothing to scale it back up. FSR 3.1 and a mode change while one +// runs apply at once. // dlaa switched off in the menu stops DLSS at once (vk_dlaa.c vk_dlaa_frame reads it each frame), so the share follows // the setting too: the scene goes back to the game's own scale instead of staying small with nothing to enlarge it. static double dlss_share(void);