From a9a983facae1b103c31d159ce1bdf7a195f304c3 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 9 Nov 2026 20:29:35 +0000 Subject: [PATCH 1/9] AMD FSR 4 as a second engine on #841's frame chain The inputs #841 builds for DLSS (scene camera and depth, the colour as the network samples it, camera and actor motion, the host camera jitter) are not DLSS's own, so they move out of vk_dlss.cpp into vk_temporal.cpp, a frame chain with engines behind a small interface (TemporalEngine: start, prepare, evaluate, release, memory). vk_dlss.cpp keeps NGX, the feature and its evaluation, unchanged in what they do. vk_fsr4.cpp is the second engine, through AMD's FSR API (MIT headers in third_party/fidelityfx; AMD's signed DLLs are the player's, loaded at run time). - Upscaler row: Game's own, NVIDIA DLSS, NVIDIA DLAA, AMD FSR 4, AMD FSR 4 Native AA. FSR uses DLSS's Render resolution steps (same ratios). Saved key stays dlss; opts.dlss is now opts.upscaler. - Build switch HOOKSHOT_FSR=1 (make_game.py --fsr, a Builder tick) compiles vk_fsr4.cpp with AMD's headers and puts the camera jitter into the game, as an NGX build does. Default builds are unchanged. - RX_VK_FSR=0/1/2, RX_VK_FSR_DIR for AMD's library. - The DLSS CI job also builds FSR, and runs when the chain changes. Co-authored-by: NoBoDyZ Co-authored-by: hckrman101 <239247847+hckrman101@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 --- .github/workflows/vulkan-dlss.yml | 14 +- builder/HookshotBuilder/MainWindow.xaml | 5 + builder/HookshotBuilder/MainWindow.xaml.cs | 5 +- gpu/PROVENANCE.md | 6 + gpu/vk/status_line.h | 4 +- gpu/vk/upscaler_rows.h | 67 +- gpu/vk/vk_dlss.cpp | 904 ++-------------- gpu/vk/vk_dlss.h | 61 +- gpu/vk/vk_draw.cpp | 40 +- gpu/vk/vk_fsr4.cpp | 462 ++++++++ gpu/vk/vk_fsr4.h | 25 + gpu/vk/vk_main.cpp | 6 + gpu/vk/vk_menu.cpp | 98 +- gpu/vk/vk_menu.h | 5 +- gpu/vk/vk_replay.cpp | 12 +- gpu/vk/vk_temporal.cpp | 926 ++++++++++++++++ gpu/vk/vk_temporal.h | 114 ++ host/rx_backend.c | 8 +- nx2/build.py | 12 +- nx2/make_game.py | 24 +- nx2/oot_jitter.py | 14 +- readme.md | 42 +- tests/test_dlss_actor.py | 6 +- tests/test_dlss_checks.py | 8 +- tests/test_make_game_ngx.py | 18 + tests/test_rumble_menu.py | 4 +- tests/test_shader_stamp_cache.py | 15 +- tests/test_status_line.py | 8 +- tests/test_upscaler_rows.cpp | 46 +- tests/test_upscaler_rows.py | 31 +- third_party/fidelityfx/LICENSE.md | 992 ++++++++++++++++++ third_party/fidelityfx/README.md | 10 + third_party/fidelityfx/api/include/ffx_api.h | 213 ++++ .../fidelityfx/api/include/ffx_api_types.h | 257 +++++ .../fidelityfx/api/include/vk/ffx_api_vk.h | 308 ++++++ .../upscalers/include/ffx_upscale.h | 230 ++++ 36 files changed, 3958 insertions(+), 1042 deletions(-) create mode 100644 gpu/vk/vk_fsr4.cpp create mode 100644 gpu/vk/vk_fsr4.h create mode 100644 gpu/vk/vk_temporal.cpp create mode 100644 gpu/vk/vk_temporal.h create mode 100644 third_party/fidelityfx/LICENSE.md create mode 100644 third_party/fidelityfx/README.md create mode 100644 third_party/fidelityfx/api/include/ffx_api.h create mode 100644 third_party/fidelityfx/api/include/ffx_api_types.h create mode 100644 third_party/fidelityfx/api/include/vk/ffx_api_vk.h create mode 100644 third_party/fidelityfx/upscalers/include/ffx_upscale.h diff --git a/.github/workflows/vulkan-dlss.yml b/.github/workflows/vulkan-dlss.yml index 043a7f103..b3753bb9b 100644 --- a/.github/workflows/vulkan-dlss.yml +++ b/.github/workflows/vulkan-dlss.yml @@ -3,6 +3,8 @@ # pull request only when a file DLSS's build depends on changed (the paths below), not on every push. scripts/ngx_sdk.py # fetches the SDK at its pinned release, SHA-256 checked, without DLSS's runtime: NVIDIA's license is accepted for that run # alone (--accept-nvidia-license), and nothing of it is uploaded or cached. +# AMD FSR 4 (HOOKSHOT_FSR=1: AMD's MIT headers, nothing downloaded) is built in the same run: both engines share +# vk_temporal.cpp. name: Vulkan back end with NVIDIA DLSS on: @@ -10,6 +12,9 @@ on: branches: [main, dev] paths: - 'gpu/vk/vk_dlss*' + - 'gpu/vk/vk_temporal*' + - 'gpu/vk/vk_fsr4*' + - 'third_party/fidelityfx/**' - 'gpu/vk/vk_reflex*' - 'gpu/vk/vk_ngx_msvc_rt.cpp' - 'gpu/vk/upscaler_rows.h' @@ -25,6 +30,9 @@ on: pull_request: paths: - 'gpu/vk/vk_dlss*' + - 'gpu/vk/vk_temporal*' + - 'gpu/vk/vk_fsr4*' + - 'third_party/fidelityfx/**' - 'gpu/vk/vk_reflex*' - 'gpu/vk/vk_ngx_msvc_rt.cpp' - 'gpu/vk/upscaler_rows.h' @@ -52,7 +60,7 @@ defaults: jobs: vulkan-dlss: - name: Windows Vulkan back end with NVIDIA DLSS (llvm-mingw, link check) + name: Windows Vulkan back end with NVIDIA DLSS and AMD FSR 4 (llvm-mingw, link check) runs-on: windows-2022 timeout-minutes: 45 steps: @@ -96,15 +104,17 @@ jobs: run: echo "HOOKSHOT_MINGW=$(python -I scripts/release_parts.py llvm-mingw "$RUNNER_TEMP/llvm")" >> "$GITHUB_ENV" - name: NVIDIA's DLSS SDK (headers and nvsdk_ngx_s.lib) run: python -I scripts/ngx_sdk.py --platform windows --build-only --accept-nvidia-license "$RUNNER_TEMP/ngx-sdk" - - name: hookshot_vk.dll with NVIDIA DLSS + - name: hookshot_vk.dll with NVIDIA DLSS and AMD FSR 4 env: RX_VK_ONLY: '1' RX_RELEASE: '1' + HOOKSHOT_FSR: '1' run: | OOT_WORK="$RUNNER_TEMP/vk-work" RX_CPM="$GITHUB_WORKSPACE/_deps/cpm" HOOKSHOT_NGX_SDK="$RUNNER_TEMP/ngx-sdk" \ python -I nx2/build.py --standalone --jobs "$NUMBER_OF_PROCESSORS" | tee "$RUNNER_TEMP/vk-build.log" test "${PIPESTATUS[0]}" -eq 0 grep -q "with NVIDIA DLSS" "$RUNNER_TEMP/vk-build.log" + grep -q "with AMD FSR 4" "$RUNNER_TEMP/vk-build.log" # (what it loads is only Windows' own: the system DLLs and the UCRT's api-ms-win-crt-*. NVIDIA's runtime, nvngx_dlss.dll, # is loaded by NGX at run time from the game folder's ngx folder, and its core, _nvngx.dll, from the driver's) - name: Imports diff --git a/builder/HookshotBuilder/MainWindow.xaml b/builder/HookshotBuilder/MainWindow.xaml index 3d281e968..52f12efa0 100644 --- a/builder/HookshotBuilder/MainWindow.xaml +++ b/builder/HookshotBuilder/MainWindow.xaml @@ -181,6 +181,11 @@ Content="NVIDIA DLSS (downloads NVIDIA's SDK under NVIDIA's license)"/> + + + diff --git a/builder/HookshotBuilder/MainWindow.xaml.cs b/builder/HookshotBuilder/MainWindow.xaml.cs index 5dfc532d0..e3f1c4a16 100644 --- a/builder/HookshotBuilder/MainWindow.xaml.cs +++ b/builder/HookshotBuilder/MainWindow.xaml.cs @@ -260,6 +260,7 @@ sealed class BuilderSettings public string? Romfs { get; set; } public string? Exefs { get; set; } public bool Dlss { get; set; } // (NVIDIA DLSS ticked: NVIDIA's license was accepted) + public bool Fsr { get; set; } // (AMD FSR 4 ticked) } static string SettingsPath => System.IO.Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "HookshotBuilder", "builder-settings.json"); @@ -271,6 +272,7 @@ void ApplySettings() if (s == null) return; if (!string.IsNullOrEmpty(s.Out)) OutPath.Text = s.Out; DlssTick.IsChecked = s.Dlss; + FsrTick.IsChecked = s.Fsr; if (s.Romfs != null && s.Exefs != null && Directory.Exists(s.Romfs) && File.Exists(System.IO.Path.Combine(s.Exefs, "main"))) { romfs = s.Romfs; @@ -293,7 +295,7 @@ void SaveSettings() try { var s = new BuilderSettings { Out = OutPath.Text, Nup = romfs == null ? chosenNup : null, Romfs = romfs, Exefs = exefs, - Dlss = DlssTick.IsChecked == true }; + Dlss = DlssTick.IsChecked == true, Fsr = FsrTick.IsChecked == true }; Directory.CreateDirectory(System.IO.Path.GetDirectoryName(SettingsPath)!); File.WriteAllText(SettingsPath, System.Text.Json.JsonSerializer.Serialize(s)); } @@ -692,6 +694,7 @@ async void StartBuild(object sender, RoutedEventArgs e) else if (chosenNup != null) args.AddRange(new[] { "--nup", chosenNup }); else return; args.AddRange(new[] { "--out", OutPath.Text, "--language", lang.Code }); + if (FsrTick.IsChecked == true) args.Add("--fsr"); // (an --update of the folder keeps it: make_game.py's mark) Run(tools, args, dlss: DlssTick.IsChecked == true); } diff --git a/gpu/PROVENANCE.md b/gpu/PROVENANCE.md index 3d90532e5..5e6da7955 100644 --- a/gpu/PROVENANCE.md +++ b/gpu/PROVENANCE.md @@ -28,3 +28,9 @@ mkw-recomp's changes, for the Switch 2: `mkw_gpu.cpp` / `mkw_gpu.h` / `build-gpu.bat` are the earlier Vulkan stand-in (a window and a swapchain; no longer loaded). `shadertool.cpp` runs dumped NVN2 programs through Eglantine's recompiler offline (SPIR-V; it prints what each program reads from the constant banks). + +Hookshot's AMD FSR 4 (`vk/vk_fsr4.cpp`) was first written for Lichii's OoT branch (madebyjordan/Lichii-Hyrule, +`renderer_vulkan/vk_fsr4_upscaler.cpp`; GPL-3.0-or-later), with its Linux path (a Vulkan FSR 4 library through dlopen) by +N3oTraX. It draws with the frame chain of hckrman101's NVIDIA DLSS (#841), moved into `vk/vk_temporal.cpp` so both +upscalers share it. `third_party/fidelityfx/` is AMD's FSR API headers from FidelityFX SDK v2.4.0 (MIT, `LICENSE.md`); +AMD's signed DLLs are loaded from the user's PC at run time and are not vendored. diff --git a/gpu/vk/status_line.h b/gpu/vk/status_line.h index 0fec42361..4e967719c 100644 --- a/gpu/vk/status_line.h +++ b/gpu/vk/status_line.h @@ -29,10 +29,12 @@ inline bool crossed(bool& over, uint64_t used, uint64_t allowed) { return tell; } -/// What the warning adds for the upscaler drawing (1 DLSS, 2 DLAA; 0 neither: nothing) +/// What the warning adds for the upscaler drawing (upscaler_rows.h: 1 DLSS, 2 DLAA, 3 FSR 4, 4 its native AA; 0 none: nothing) inline const char* over_advice(int dlss_mode) { if (dlss_mode == 2) return ": DLAA may stutter here; Quality or Balanced uses less"; if (dlss_mode == 1) return ": DLSS may stutter here; a lower Render resolution mode uses less"; + if (dlss_mode == 4) return ": FSR's native AA may stutter here; FSR 4's Quality or Balanced uses less"; + if (dlss_mode == 3) return ": FSR may stutter here; a lower Render resolution mode uses less"; return ""; } inline int over_text(char* b, size_t n, uint64_t used, uint64_t allowed, int dlss_mode) { diff --git a/gpu/vk/upscaler_rows.h b/gpu/vk/upscaler_rows.h index 225d6c44a..1eb09c742 100644 --- a/gpu/vk/upscaler_rows.h +++ b/gpu/vk/upscaler_rows.h @@ -4,28 +4,39 @@ namespace mkwvk::upscaler_rows { -/// The Upscaler row (opts.dlss, RX_VK_DLSS): the game's own network, NVIDIA DLSS, NVIDIA DLAA -enum { GAME = 0, DLSS = 1, DLAA = 2 }; +/// The Upscaler row (opts.upscaler, saved as dlss; RX_VK_DLSS and RX_VK_FSR over it): the game's own network, NVIDIA DLSS, +/// NVIDIA DLAA, AMD FSR 4, AMD FSR 4's native anti-aliasing (FSR_AA). Each engine has two: one from a smaller 3D, and one +/// with the 3D at full scale (anti-aliasing alone) +enum { GAME = 0, DLSS = 1, DLAA = 2, FSR = 3, FSR_AA = 4, kUpscalers = 5 }; +inline bool is_dlss(int u) { return u == DLSS || u == DLAA; } +inline bool is_fsr(int u) { return u == FSR || u == FSR_AA; } +/// The 3D drawn at full scale, the engine anti-aliasing alone: the Render resolution row does not apply +inline bool full_scale(int u) { return u == DLAA || u == FSR_AA; } +/// From a smaller 3D, by an engine: the Render resolution row steps through its modes +inline bool scaled(int u) { return u == DLSS || u == FSR; } /// opts.resolution: 0 Auto (the game's dynamic resolution), else a share of the output in percent. 1 to 4 are the values from /// before (100, 75, 67, 50: saved options keep their meaning); 5 and 6 came with DLSS's Balanced and Ultra Performance. Each -/// is a twelfth of the output (vk_menu.cpp twelfths): 8/12, 7/12, 6/12 and 4/12 are DLSS's own ratios +/// is a twelfth of the output (vk_menu.cpp twelfths): 8/12, 7/12, 6/12 and 4/12 are DLSS's own ratios, and FSR's (1.5, 1.7, +/// 2 and 3 times) constexpr int kPercent[] = {0, 100, 75, 67, 50, 58, 33}; constexpr int kValues = sizeof kPercent / sizeof kPercent[0]; inline int percent(int v) { return v > 0 && v < kValues ? kPercent[v] : 0; } -/// The steps the Render resolution row takes, best first after Auto. DLSS: its Quality, Balanced, Performance and Ultra -/// Performance; Auto too, since DLSS follows the game's dynamic size (vk_dlss.cpp makes its feature again when the size -/// leaves the mode's range). DLAA: none (the 3D is drawn at full size) +/// The steps the Render resolution row takes, best first after Auto. DLSS and FSR: their Quality, Balanced, Performance and +/// Ultra Performance; Auto too, since they follow the game's dynamic size (vk_dlss.cpp makes its feature again when the size +/// leaves the mode's range; FSR's context takes any size up to the output). DLAA, FSR's native AA: none (the 3D is drawn at +/// full size) constexpr int kGameSteps[] = {0, 1, 2, 3, 4}; -constexpr int kDlssSteps[] = {0, 3, 5, 4, 6}; +constexpr int kEngineSteps[] = {0, 3, 5, 4, 6}; inline const int* steps(int upscaler, int& n) { - if (upscaler == DLSS) return n = sizeof kDlssSteps / sizeof kDlssSteps[0], kDlssSteps; + if (scaled(upscaler)) return n = sizeof kEngineSteps / sizeof kEngineSteps[0], kEngineSteps; return n = sizeof kGameSteps / sizeof kGameSteps[0], kGameSteps; } -/// DLSS's name for a Render resolution value: nullptr for Auto, or a share that is not one of its modes -inline const char* dlss_mode_name(int v) { +/// DLSS's and FSR's name for a Render resolution value (the same four modes): nullptr for Auto, or a share that is not one +/// of them +inline const char* mode_name(int v) { switch (percent(v)) { case 67: return "Quality"; case 58: return "Balanced"; @@ -36,9 +47,9 @@ inline const char* dlss_mode_name(int v) { } /// The Render resolution row's next value from v (dir +1 Right, -1 Left), wrapping. A value that is not one of the upscaler's -/// steps (one saved with another upscaler) moves to its neighbour by size. DLAA: v, unchanged +/// steps (one saved with another upscaler) moves to its neighbour by size. DLAA, FSR's native AA: v, unchanged inline int step(int upscaler, int v, int dir) { - if (upscaler == DLAA) return v; + if (full_scale(upscaler)) return v; int n = 0; const int* s = steps(upscaler, n); for (int i = 0; i < n; i++) @@ -51,9 +62,9 @@ inline int step(int upscaler, int v, int dir) { } /// v as one of the upscaler's steps: itself when it is one, else the step nearest in size (the larger on a tie). Auto stays -/// Auto. DLAA: v, unchanged (its row does not apply; v comes back with the next upscaler) +/// Auto. DLAA, FSR's native AA: v, unchanged (its row does not apply; v comes back with the next upscaler) inline int snap(int upscaler, int v) { - if (upscaler == DLAA || percent(v) == 0) return percent(v) == 0 ? 0 : v; + if (full_scale(upscaler) || percent(v) == 0) return percent(v) == 0 ? 0 : v; int n = 0; const int* s = steps(upscaler, n); int best = v, best_d = 1000; @@ -67,11 +78,15 @@ inline int snap(int upscaler, int v) { } /// The Upscaler row's next value from v (dir +1 / -1), wrapping over the choices there are: DLSS and DLAA only when -/// dlss_ok (a build with NVIDIA's SDK, on a GPU DLSS runs on). Without them it stays on the game's own -inline int step_upscaler(int v, int dir, bool dlss_ok) { - if (!dlss_ok) return GAME; - const int n = 3; - return ((v < 0 || v >= n ? GAME : v) + (dir > 0 ? 1 : n - 1)) % n; +/// dlss_ok (a build with NVIDIA's SDK, on a GPU DLSS runs on), FSR 4 and its native AA only when fsr_ok (a build with FSR, +/// AMD's library found with an FSR 4 for this GPU). Without either it stays on the game's own +inline int step_upscaler(int v, int dir, bool dlss_ok, bool fsr_ok) { + if (v < 0 || v >= kUpscalers) v = GAME; + for (int i = 0; i < kUpscalers; i++) { + v = (v + (dir > 0 ? 1 : kUpscalers - 1)) % kUpscalers; + if (v == GAME || (is_dlss(v) && dlss_ok) || (is_fsr(v) && fsr_ok)) return v; + } + return GAME; } /// The DLSS model row (opts.dlss_model, saved as dlss_model): which of NVIDIA's networks DLSS and DLAA draw with, by its @@ -100,18 +115,18 @@ inline unsigned dlss_preset(int model, const char* env) { /// A preset's letter for the log ('A' + preset - 1), or '0' for NVIDIA's default inline char preset_letter(unsigned preset) { return preset ? (char)('A' + preset - 1) : '0'; } -/// The Upscaler row is there only in a build with DLSS (built: dlss_built()): elsewhere the game's own is all there is, and -/// the Render resolution row is the game's own's +/// The Upscaler row is there only in a build with DLSS or FSR (built: dlss_built() || fsr4_built()): elsewhere the game's +/// own is all there is, and the Render resolution row is the game's own's inline bool upscaler_shown(bool built) { return built; } /// Rows with nothing else to choose here (shown without arrows; Left and Right leave the saved value alone): the Upscaler -/// where DLSS cannot run (dlss_ok: dlss_why() == DLSS_OK), the Render resolution with DLAA (the 3D at full size), the DLSS -/// model while DLSS and DLAA are not drawing -inline bool upscaler_fixed(bool dlss_ok) { return !dlss_ok; } -inline bool resolution_fixed(int upscaler) { return upscaler == DLAA; } +/// where neither engine can run (dlss_ok: dlss_why() == DLSS_OK; fsr_ok: fsr4_why() == FSR_OK), the Render resolution with +/// DLAA or FSR's native AA (the 3D at full size), the DLSS model while DLSS and DLAA are not drawing +inline bool upscaler_fixed(bool dlss_ok, bool fsr_ok) { return !dlss_ok && !fsr_ok; } +inline bool resolution_fixed(int upscaler) { return full_scale(upscaler); } /// The DLSS model row: shown where the Upscaler is (a build with DLSS), with nothing to choose while the game's own upscales inline bool model_shown(bool built) { return built; } /// (env_set: RX_VK_DLSS_PRESET names a preset, which wins over the row for this run) -inline bool model_fixed(int upscaler, bool env_set) { return upscaler == GAME || env_set; } +inline bool model_fixed(int upscaler, bool env_set) { return !is_dlss(upscaler) || env_set; } /// The Window scaling filter does something only when the window's size is not the game's picture's (the Output resolution); /// a size not known yet (0) counts as different inline bool filter_needed(unsigned picture_w, unsigned picture_h, unsigned window_w, unsigned window_h) { diff --git a/gpu/vk/vk_dlss.cpp b/gpu/vk/vk_dlss.cpp index 3f879b394..84b5aa379 100644 --- a/gpu/vk/vk_dlss.cpp +++ b/gpu/vk/vk_dlss.cpp @@ -1,26 +1,6 @@ -// (hookshot) NVIDIA DLSS Super Resolution in place of OoT's gameplay upscale network (vk_dlss.h). -// -// OoT has no DLSS of its own: its scene is drawn at a lower size and a network of its own (the program -// fd029a317a9a4b94 at 3/4, another for each other share; one dispatch, cooperative matrices) scales it up, from the -// square root of the scene's tonemapped colour, into the frame's B10G11R11 picture (before the HUD). Here that dispatch is replaced: -// - the scene's camera is taken from the draws into the scene's colour target: their vertex stage's uniform buffer 3 -// holds the target's size at 0x120, this frame's view-projection at 0x30 (row major, clip = M * position; the projection's w row ends -1, 0 at 0xa8). The largest such target in a frame is -// the scene (reflections and half-size passes have the same layout). The scene's depth is that draw's depth target. -// - shaders/dlss_prep.comp makes DLSS's inputs at the render size: the colour as the network samples it (LDR), the -// depth, and motion vectors from the depth reprojected through the two cameras. The game has no motion vectors of its -// own (no velocity target: the scene pass draws into one B10G11R11 target, and its upscale network samples the colour alone), -// so that is the camera's motion, and what moves by itself would ghost. The scene camera's sub-pixel jitter -// (nx2/oot_jitter.py, while DLSS draws) is read back from its projection, given to DLSS and taken off the motion. -// - actor motion (actor_motion, shaders/dlss_actor.vert): the skinned draws into the scene (vk_draw.cpp's dlss_skin_check: -// Link, the other characters, the animals, what the game animates with bones) are drawn again over that motion, each -// vertex skinned with this frame's bones and the frame before's, so their pixels get their own motion. Their poses are -// copied on the GPU from where the game has them into two buffers that take turns; a pixel is the draw's where its depth is -// the scene's (within RX_VK_DLSS_ACTOR_TOL steps, default 64). What moves without bones (particles, water, foliage moved in -// its vertex shader, mesh-shader draws) keeps the camera's motion. RX_VK_DLSS_ACTORS=0: camera motion alone. -// - DLSS (NGX, Vulkan) scales them to the output's size; shaders/dlss_out.comp squares that back to linear colour -// into the network's output. -// Anything unexpected (no camera this frame, other sizes, DLSS not there or failing) leaves the network to run, the -// reason in the log when it changes. +// (hookshot) NVIDIA DLSS Super Resolution, one of the temporal upscaler's engines (vk_dlss.h). The frame chain it draws +// with (the scene's camera and depth, the inputs, the actor motion, the jitter, the picture into the network's output) is +// vk_temporal.cpp's; here are NGX, DLSS's feature and its evaluation. // // RX_VK_DLSS=0 / 1 / 2: the game's own, DLSS, DLAA over the Insert menu's Upscaler row (0 also leaves NGX's extensions // off). DLSS's runtime (Windows: nvngx_dlss.dll; Linux: libnvidia-ngx-dlss.so.*) is looked for in RX_VK_DLSS_DIR= @@ -29,11 +9,7 @@ // works. On Linux NGX takes only ASCII paths: a folder named otherwise goes to it as /proc/self/fd/ (wide()), and the // runtime itself has to be in a folder whose real path is ASCII (the log and the menu say so when it is not). The Insert // menu's DLSS model row (opts.dlss_model, upscaler_rows.h) chooses DLSS's preset: K by default, M, L, or NVIDIA's default -// (0). RX_VK_DLSS_PRESET= (or 0) over it. -// RX_VK_DLSS_DEBUG=motion: the motion vectors shown instead of the picture (|x|, |y| over 16 px as red and green, the actors' -// in it); =alternate: every other frame the game's own upscale (a test's reference beside DLSS's). RX_VK_DLSS_LOG=1: NGX's -// own log in ours. -// RX_VK_DLSS_SQUARE=0: the output not squared. +// (0). RX_VK_DLSS_PRESET= (or 0) over it. RX_VK_DLSS_LOG=1: NGX's own log in ours. // // NVIDIA's NGX SDK is not part of Hookshot (NVIDIA's license): a local build fetches it (scripts/ngx_sdk.py) and builds with // HOOKSHOT_NGX_SDK, which compiles this file with HOOKSHOT_NGX. Without it only the stubs at the end are here. @@ -41,38 +17,28 @@ // libnvidia-ngx.so). It is never put beside the game: a copy there would be loaded in place of the driver's. #include "vk_dlss.h" -#include +#include +#include #include #include #include -#include -#include -#include #include #include -#include #ifndef _WIN32 #include // (wide: a folder's descriptor for NGX) #endif -#include "dlss_actor.h" #include "dlss_checks.h" -#include "dlss_math.h" #include "upscaler_rows.h" -#include "vk_draw.h" #include "vk_menu.h" #include "vk_paths.h" +#include "vk_temporal.h" #include "vk_texture.h" #ifdef HOOKSHOT_NGX #include "nvsdk_ngx_vk.h" #include "nvsdk_ngx_helpers.h" #include "nvsdk_ngx_helpers_vk.h" - -#include "shaders/dlss_actor_frag_spv.h" -#include "shaders/dlss_actor_vert_spv.h" -#include "shaders/dlss_out_comp_spv.h" -#include "shaders/dlss_prep_comp_spv.h" #endif #include "vk_work.h" // (after the others: counts the commands that do GPU work, for full_barrier) @@ -96,40 +62,15 @@ DlssWhy dlss_why() { return w < 0 ? DLSS_OK : (DlssWhy)w; } -int dlss_mode() { - if (dlss_why() != DLSS_OK) return 0; - static const int env = env_switch("RX_VK_DLSS"); - return std::clamp(env >= 0 ? env : opts.dlss, 0, 2); -} -bool dlss_wanted() { return dlss_mode() != 0; } -bool dlss_full_scale() { return dlss_mode() == 2; } - #ifdef HOOKSHOT_NGX namespace { -struct Image { - VkImage image = VK_NULL_HANDLE; - VmaAllocation memory = nullptr; - VkImageView view = VK_NULL_HANDLE; - VkFormat format = VK_FORMAT_UNDEFINED; - uint32_t width = 0, height = 0; -}; - -struct Camera { - float current[16]{}; // (its view-projection) - float jitter[2]{}; // (the projection's offset of the picture's centre, in clip space: nx2/oot_jitter.py) - uint32_t width = 0, height = 0; - Texture* depth = nullptr; - uint64_t depth_gen = 0; // (texture_destroy_generation when taken: a depth texture destroyed since is not used) - uint64_t frame = 0; // (texture_frame_no when taken) - bool valid = false; -}; - +/// NGX and DLSS's feature. The feature is used with the temporal upscaler's lock held; NGX's start (ngx_init) also from the +/// menu's thread (dlss_probe), under mu struct State { std::mutex mu; bool ngx_tried = false, ngx_ok = false; - std::atomic failed{false}; // (atomic: dlss_jitter_size reads it on the game's thread, without the lock) NVSDK_NGX_Parameter* caps = nullptr; NVSDK_NGX_Parameter* params = nullptr; NVSDK_NGX_Handle* feature = nullptr; @@ -137,140 +78,13 @@ struct State { uint32_t min_rw = 0, min_rh = 0; unsigned feature_preset = ~0u; // (the preset it was made with: another one from the menu makes it again) uint64_t features_made = 0; // (how many times: a feature made again in play costs a stall, so each says why) - Image color, depth, motion, output; - VkDescriptorSetLayout prep_set = VK_NULL_HANDLE, out_set = VK_NULL_HANDLE; - VkPipelineLayout prep_layout = VK_NULL_HANDLE, out_layout = VK_NULL_HANDLE; - VkPipeline prep = VK_NULL_HANDLE, out = VK_NULL_HANDLE; - VkSampler point = VK_NULL_HANDLE, linear = VK_NULL_HANDLE; - Camera cam; // (this frame's, since the last upscale) - // (the gameplay upscale's output, as last seen: the scene sizes DLSS can take follow from it, dlss_scene_size. Read on - // the game's draws without the lock) - std::atomic out_w{0}, out_h{0}; - std::atomic last_frame{0}; // (the frame DLSS last drew: read on the game's thread too) - float last_jitter[2]{}; // (its camera's jitter: the frame before's, for the next one) - float last_vp[16]{}; // (its camera's view-projection: the frame before's) - std::atomic jitter_w{0}, jitter_h{0}; // (the scene's size while DLSS draws: the host's jitter's scale) - std::atomic jitter_phases{8}; // (its sequence's length, for DLSS's scale: dlss_jitter_phases) - std::unordered_map depth_views; - uint64_t depth_views_gen = 0; - const char* reason = nullptr; - int reason_repeats = 0; - bool told_first = false; - // (actor motion: the skinned draws into the scene since the last upscale; the poses DLSS last drew, by draw: the - // frame before's, where they were copied to in one of the two pose buffers (each frame's poses go to the other one)) - struct Skin { - DlssSkin k; - uint64_t key = 0; - uint32_t nbones = 0; - }; - std::vector skins; - struct Pose { - uint32_t at = 0, nbones = 0; // (bytes into its buffer: the origin, then the bones from at + 16) - uint64_t frame = 0; - }; - std::unordered_map poses; - std::unordered_map model_bones; // (by model: the bones its vertices use, 0 when unreadable) - struct PoseBuffer { - VkBuffer buffer = VK_NULL_HANDLE; - VmaAllocation memory = nullptr; - uint64_t address = 0; - } pose_buffers[2]; - int pose_at = 0; // (this frame's) - VkPipelineLayout actor_layout = VK_NULL_HANDLE; - VkPipeline actor = VK_NULL_HANDLE; - bool actor_tried = false; - struct Upload { - VkBuffer buffer = VK_NULL_HANDLE; - VmaAllocation memory = nullptr; - void* map = nullptr; - } uploads[6]; // (the cameras: a ring, one in use stays so for a few frames, as what later() frees) - int upload_at = 0; - uint64_t actor_draws = 0, actor_frames = 0, actor_skipped = 0; - int64_t actor_ns = 0; // (the CPU's time in it, since the last status line) - uint64_t status_draws = 0, status_frames = 0; // (actor_draws and _frames at the last status line: dlss_status) - uint64_t status_drawn = 0, status_passed = 0; // (since the last status line: frames DLSS drew, and frames a network - // of the upscale's shape ran without it (its scene not this frame's)) - float status_jitter[2]{}; // (the last evaluation's jitter, in render pixels, and whether it had motion) - bool status_motion = false; - struct Dead { uint64_t frame; std::function free; }; - std::vector dead; // (what work in flight may still use: freed a few frames on) - uint64_t asked = 0; // (the frame an upscale last asked for DLSS: dlss_idle) - std::atomic holding{false}; // (a feature, images or frees still to do: dlss_idle has something to let go) }; State& st() { static State s; return s; } -/// The gameplay upscale's output size, as it is seen: the scene sizes DLSS can take (dlss_scene_size). Until the first one, -/// none: that frame keeps the game's upscale -void note_output(const Texture* out, uint32_t level) { - State& s = st(); - s.out_w.store(std::max(1u, out->width >> level), std::memory_order_relaxed); - s.out_h.store(std::max(1u, out->height >> level), std::memory_order_relaxed); -} - -bool fail(const char* why) { // (the network runs: why, when it changes and every 600th time) - State& s = st(); - if (why != s.reason || ++s.reason_repeats % 600 == 0) logf("vk: NVIDIA DLSS: the game's upscale runs (%s)", why); - if (why != s.reason) s.reason = why, s.reason_repeats = 0; - return false; -} - -void later(std::function f) { st().dead.push_back({texture_frame_no(), std::move(f)}); } -void free_dead(bool all) { - State& s = st(); - const uint64_t now = texture_frame_no(); - for (size_t i = 0; i < s.dead.size();) { - if (all || now - s.dead[i].frame > 4) { - s.dead[i].free(); - s.dead[i] = std::move(s.dead.back()); - s.dead.pop_back(); - } else { - i++; - } - } -} - -void drop_image(Image& im) { - if (!im.image) return; - const Image old = im; - later([old] { - vkDestroyImageView(dev.device, old.view, nullptr); - destroy_image(old.image, old.memory); - }); - im = {}; -} - -bool make_image(VkCommandBuffer cmd, Image& im, VkFormat format, uint32_t w, uint32_t h, VkImageUsageFlags extra = 0) { - if (im.image && im.format == format && im.width == w && im.height == h) return true; - drop_image(im); - VkImageCreateInfo ic{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; - ic.imageType = VK_IMAGE_TYPE_2D, ic.format = format, ic.extent = {w, h, 1}, ic.mipLevels = 1, ic.arrayLayers = 1; - ic.samples = VK_SAMPLE_COUNT_1_BIT, ic.tiling = VK_IMAGE_TILING_OPTIMAL; - ic.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | extra; - if (!create_image(ic, im.image, im.memory)) return im = {}, false; - VkImageViewCreateInfo vc{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; - vc.image = im.image, vc.viewType = VK_IMAGE_VIEW_TYPE_2D, vc.format = format; - vc.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - if (vkCreateImageView(dev.device, &vc, nullptr, &im.view) != VK_SUCCESS) { - destroy_image(im.image, im.memory); - return im = {}, false; - } - im.format = format, im.width = w, im.height = h; - VkImageMemoryBarrier2 b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2}; // (UNDEFINED -> GENERAL, once) - b.srcStageMask = VK_PIPELINE_STAGE_2_NONE, b.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; - b.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; - b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, b.newLayout = VK_IMAGE_LAYOUT_GENERAL; - b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - b.image = im.image, b.subresourceRange = vc.subresourceRange; - VkDependencyInfo di{VK_STRUCTURE_TYPE_DEPENDENCY_INFO}; - di.imageMemoryBarrierCount = 1, di.pImageMemoryBarriers = &b; - vkCmdPipelineBarrier2(cmd, &di); - return true; -} - -NVSDK_NGX_Resource_VK resource(const Image& im, bool write) { +NVSDK_NGX_Resource_VK resource(const TemporalImage& im, bool write) { return NVSDK_NGX_Create_ImageView_Resource_VK(im.view, im.image, {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, im.format, im.width, im.height, write); } @@ -357,6 +171,7 @@ constexpr const fs::path::value_type* kRuntimeName = "libnvidia-ngx-dlss.so"; /// NGX on this device, once: false when DLSS cannot run here (the reason logged) bool ngx_init() { State& s = st(); + std::lock_guard lk(s.mu); if (s.ngx_tried) return s.ngx_ok; s.ngx_tried = true; // where DLSS's runtime (kRuntime) is looked for (NGX takes the newest it finds), and its own files go @@ -447,290 +262,21 @@ bool ngx_init() { return true; } -/// One compute pipeline with push descriptors; what it made is destroyed again when a step fails -bool make_pipeline(const uint32_t* code, size_t size, const VkDescriptorType* types, uint32_t n, uint32_t push, - VkDescriptorSetLayout& set, VkPipelineLayout& layout, VkPipeline& pipe) { - const auto undo = [&] { - if (layout) vkDestroyPipelineLayout(dev.device, layout, nullptr); - if (set) vkDestroyDescriptorSetLayout(dev.device, set, nullptr); - set = VK_NULL_HANDLE, layout = VK_NULL_HANDLE, pipe = VK_NULL_HANDLE; - return false; - }; - VkDescriptorSetLayoutBinding b[8]{}; - for (uint32_t i = 0; i < n; i++) b[i] = {i, types[i], 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}; - VkDescriptorSetLayoutCreateInfo li{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; - li.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR; - li.bindingCount = n, li.pBindings = b; - if (vkCreateDescriptorSetLayout(dev.device, &li, nullptr, &set) != VK_SUCCESS) return set = VK_NULL_HANDLE, undo(); - const VkPushConstantRange pr{VK_SHADER_STAGE_COMPUTE_BIT, 0, push}; - VkPipelineLayoutCreateInfo pi{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; - pi.setLayoutCount = 1, pi.pSetLayouts = &set, pi.pushConstantRangeCount = 1, pi.pPushConstantRanges = ≺ - if (vkCreatePipelineLayout(dev.device, &pi, nullptr, &layout) != VK_SUCCESS) return layout = VK_NULL_HANDLE, undo(); - VkShaderModuleCreateInfo mi{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; - mi.codeSize = size, mi.pCode = code; - VkShaderModule m{}; - if (vkCreateShaderModule(dev.device, &mi, nullptr, &m) != VK_SUCCESS) return undo(); - VkComputePipelineCreateInfo ci{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; - ci.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0, VK_SHADER_STAGE_COMPUTE_BIT, m, "main", nullptr}; - ci.layout = layout; - const bool ok = vkCreateComputePipelines(dev.device, VK_NULL_HANDLE, 1, &ci, nullptr, &pipe) == VK_SUCCESS; - vkDestroyShaderModule(dev.device, m, nullptr); - return ok || undo(); -} - -/// The two passes' pipelines and the samplers, once (dlss_upscale stops trying when this fails) -bool make_pipelines() { - State& s = st(); - if (s.prep && s.out && s.point && s.linear) return true; - if (!dev.push_descriptor || !vkCmdPushDescriptorSetKHR) return false; - const VkDescriptorType S = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, I = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; - const VkDescriptorType prep_types[5] = {S, S, I, I, I}, out_types[2] = {S, I}; - if (!s.prep && !make_pipeline(DLSS_PREP_COMP_SPV, sizeof DLSS_PREP_COMP_SPV, prep_types, 5, 84, s.prep_set, s.prep_layout, s.prep)) - return false; - if (!s.out && !make_pipeline(DLSS_OUT_COMP_SPV, sizeof DLSS_OUT_COMP_SPV, out_types, 2, 8, s.out_set, s.out_layout, s.out)) - return false; - VkSamplerCreateInfo si{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; - si.magFilter = si.minFilter = VK_FILTER_NEAREST; - si.addressModeU = si.addressModeV = si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - if (!s.point && vkCreateSampler(dev.device, &si, nullptr, &s.point) != VK_SUCCESS) return s.point = VK_NULL_HANDLE, false; - si.magFilter = si.minFilter = VK_FILTER_LINEAR; - if (!s.linear && vkCreateSampler(dev.device, &si, nullptr, &s.linear) != VK_SUCCESS) return s.linear = VK_NULL_HANDLE, false; - return true; -} - -/// A view of a depth texture's depth alone, for sampling (made once per image) -VkImageView depth_view(Texture* t) { - State& s = st(); - const uint64_t gen = texture_destroy_generation(); - if (gen != s.depth_views_gen) { // (a texture was destroyed: its image's view may be stale) - auto old = std::move(s.depth_views); - s.depth_views.clear(); - later([old] { - for (const auto& [image, view] : old) vkDestroyImageView(dev.device, view, nullptr); - }); - s.depth_views_gen = gen; - } - if (auto it = s.depth_views.find(t->image); it != s.depth_views.end()) return it->second; - VkImageViewCreateInfo vc{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; - vc.image = t->image, vc.viewType = VK_IMAGE_VIEW_TYPE_2D, vc.format = t->format; - vc.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1}; - VkImageView v = VK_NULL_HANDLE; - if (vkCreateImageView(dev.device, &vc, nullptr, &v) != VK_SUCCESS) v = VK_NULL_HANDLE; - s.depth_views[t->image] = v; - return v; -} - -// ---- actor motion: the skinned draws drawn again into the motion, each pixel they show moved by its own bones ---- - -using ActorPush = dlss_actor::Push; - -/// Its pipeline, once (false: none, the motion stays the camera's) -bool make_actor_pipeline() { - State& s = st(); - if (s.actor) return true; - if (s.actor_tried) return false; - s.actor_tried = true; - VkPhysicalDeviceFeatures2 f{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2}; - vkGetPhysicalDeviceFeatures2(dev.physical, &f); - if (!dev.vk12.bufferDeviceAddress || !f.features.shaderInt64) { // (its shaders read the game's buffers by their addresses) - logf("vk: NVIDIA DLSS: no buffer device addresses or 64-bit integers in shaders: camera motion alone"); - return false; - } - VkDescriptorSetLayoutBinding b[2]{}; - b[0] = {0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}; - b[1] = {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}; - VkDescriptorSetLayoutCreateInfo li{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; - li.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR; - li.bindingCount = 2, li.pBindings = b; - VkDescriptorSetLayout set{}; - if (vkCreateDescriptorSetLayout(dev.device, &li, nullptr, &set) != VK_SUCCESS) return false; - const VkPushConstantRange pr{VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ActorPush)}; - VkPipelineLayoutCreateInfo pi{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; - pi.setLayoutCount = 1, pi.pSetLayouts = &set, pi.pushConstantRangeCount = 1, pi.pPushConstantRanges = ≺ - if (vkCreatePipelineLayout(dev.device, &pi, nullptr, &s.actor_layout) != VK_SUCCESS) return false; - VkShaderModule m[2]{}; - const uint32_t* code[2] = {DLSS_ACTOR_VERT_SPV, DLSS_ACTOR_FRAG_SPV}; - const size_t size[2] = {sizeof DLSS_ACTOR_VERT_SPV, sizeof DLSS_ACTOR_FRAG_SPV}; - for (int i = 0; i < 2; i++) { - VkShaderModuleCreateInfo mi{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; - mi.codeSize = size[i], mi.pCode = code[i]; - if (vkCreateShaderModule(dev.device, &mi, nullptr, &m[i]) != VK_SUCCESS) return false; - } - VkPipelineShaderStageCreateInfo stages[2]{}; - for (int i = 0; i < 2; i++) { - stages[i] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; - stages[i].stage = i ? VK_SHADER_STAGE_FRAGMENT_BIT : VK_SHADER_STAGE_VERTEX_BIT; - stages[i].module = m[i], stages[i].pName = "main"; - } - VkPipelineVertexInputStateCreateInfo vi{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; - VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; - ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; - VkPipelineViewportStateCreateInfo vp{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; - vp.viewportCount = vp.scissorCount = 1; - VkPipelineRasterizationStateCreateInfo rs{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; - rs.polygonMode = VK_POLYGON_MODE_FILL, rs.cullMode = VK_CULL_MODE_NONE, rs.lineWidth = 1.0f; - VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; - ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState ba{}; - ba.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT; - VkPipelineColorBlendStateCreateInfo cb{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; - cb.attachmentCount = 1, cb.pAttachments = &ba; - const VkDynamicState dyn[2] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; - VkPipelineDynamicStateCreateInfo ds{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; - ds.dynamicStateCount = 2, ds.pDynamicStates = dyn; - const VkFormat format = VK_FORMAT_R16G16_SFLOAT; - VkPipelineRenderingCreateInfo ri{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO}; - ri.colorAttachmentCount = 1, ri.pColorAttachmentFormats = &format; - VkGraphicsPipelineCreateInfo gi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; - gi.pNext = &ri; - gi.stageCount = 2, gi.pStages = stages; - gi.pVertexInputState = &vi, gi.pInputAssemblyState = &ia, gi.pViewportState = &vp, gi.pRasterizationState = &rs; - gi.pMultisampleState = &ms, gi.pColorBlendState = &cb, gi.pDynamicState = &ds, gi.layout = s.actor_layout; - const bool ok = vkCreateGraphicsPipelines(dev.device, VK_NULL_HANDLE, 1, &gi, nullptr, &s.actor) == VK_SUCCESS; - for (VkShaderModule x : m) vkDestroyShaderModule(dev.device, x, nullptr); - if (!ok) s.actor = VK_NULL_HANDLE, logf("vk: NVIDIA DLSS: the actor motion's pipeline was not made: camera motion alone"); - return ok; -} - -/// A range of guest memory as this recording binds it, by its device address (the pools and their mirrors have them) -uint64_t device_address(const BufferRef& r) { - if (!r) return 0; - VkBufferDeviceAddressInfo da{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO}; - da.buffer = r.buffer; - const VkDeviceAddress base = vkGetBufferDeviceAddress(dev.device, &da); - return base ? base + r.offset : 0; -} - -constexpr uint64_t kPoseBytes = 4 << 20; // (a pose buffer: 280 bones of 48 bytes are 13 KB a draw, 300 of them) - -/// The skinned draws into the scene (cam's depth) with a pose of the frame before (DLSS's last), drawn into the motion -/// image over the camera's motion. Each one's bones and origin are copied on the GPU, from where the game has them, into -/// this frame's pose buffer, for this frame and the next. before: the frame before's camera without its jitter. history: -/// DLSS has the frame before. Returns the draws drawn -int actor_motion(VkCommandBuffer cmd, const Camera& cam, const float* before, uint32_t rw, uint32_t rh, VkImageView dview, - bool history) { - State& s = st(); - struct Timed { - State& s; - std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); - ~Timed() { s.actor_ns += (std::chrono::steady_clock::now() - t0).count(); } - } timed{s}; - std::vector skins = std::move(s.skins); - s.skins.clear(); - std::unordered_map old = std::move(s.poses); - s.poses.clear(); - static const bool off = env_switch("RX_VK_DLSS_ACTORS") == 0; - if (off || skins.empty() || !make_actor_pipeline()) return 0; - for (auto& b : s.pose_buffers) - if (!b.buffer) { - if (!create_device_buffer(kPoseBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, - b.buffer, b.memory)) - return b = {}, 0; - b.address = device_address({b.buffer, 0, kPoseBytes, 0}); - } - const uint64_t now = texture_frame_no(); - const State::PoseBuffer& here = s.pose_buffers[s.pose_at]; - const State::PoseBuffer& there = s.pose_buffers[s.pose_at ^ 1]; - static const uint32_t tolerance = env_switch("RX_VK_DLSS_ACTOR_TOL") >= 0 ? (uint32_t)env_switch("RX_VK_DLSS_ACTOR_TOL") : 64; - std::vector draws; - std::vector counts; - uint32_t at = 0; - for (const auto& k : skins) { - if (k.k.depth != cam.depth) continue; - const uint32_t bytes = 16 + k.nbones * 48; - if (at + bytes > kPoseBytes) break; - // (this frame's pose, from where the game has it: its bones, then its origin at the object's 0x40) - const BufferRef bones = gpu_buffer_bind(k.k.bones, (uint64_t)k.nbones * 48, false); - const BufferRef origin = gpu_buffer_bind(k.k.object + 0x40, 12, false); - if (!bones || !origin) { - s.actor_skipped++; - continue; - } - const VkBufferCopy c0{origin.offset, at, 12}, c1{bones.offset, at + 16, (uint64_t)k.nbones * 48}; - vkCmdCopyBuffer(cmd, origin.buffer, here.buffer, 1, &c0); - vkCmdCopyBuffer(cmd, bones.buffer, here.buffer, 1, &c1); - s.poses[k.key] = {at, k.nbones, now}; - const uint32_t mine = at; - at += (bytes + 15) & ~15u; - const auto it = old.find(k.key); - if (!history || it == old.end() || it->second.frame != s.last_frame || it->second.nbones != k.nbones) { - s.actor_skipped++; - continue; - } - // (the vertices and indices where the game has them: its buffers as this recording binds them) - const uint64_t va = device_address(gpu_buffer_bind(k.k.vbuf, k.k.vsize, false)); - const uint64_t ia = device_address(gpu_buffer_bind(k.k.ibuf, (uint64_t)k.k.count << k.k.itype, false)); - if (!va || !ia || va % 4 || k.k.count <= 0 || k.k.itype < 0 || k.k.itype > 2) { - s.actor_skipped++; - continue; - } - ActorPush a{}; - a.vertices = va, a.count = (uint32_t)std::min(k.k.vsize / k.k.stride, UINT32_MAX); - dlss_actor::index_start(ia, k.k.itype, &a.indices, &a.first); - a.origins[0] = here.address + mine, a.origins[1] = there.address + it->second.at; - a.bones[0] = a.origins[0] + 16, a.bones[1] = a.origins[1] + 16; - a.nbones = k.nbones; - a.itype = (uint32_t)k.k.itype, a.base_vertex = k.k.base_vertex; - a.stride = k.k.stride / 4, a.pos = k.k.pos, a.sets = (uint32_t)k.k.sets; - for (int i = 0; i < 2; i++) a.joints[i] = k.k.joints[i], a.weights[i] = k.k.weights[i]; - a.formats = k.k.formats; - a.tolerance = tolerance; - draws.push_back(a); - counts.push_back((uint32_t)k.k.count); - } - s.pose_at ^= 1; - if (draws.empty()) return 0; - // (the cameras: this frame's as drawn, the frame before's without its jitter, the jitter, the render size) - State::Upload& u = s.uploads[s.upload_at]; - s.upload_at = (s.upload_at + 1) % (int)(sizeof s.uploads / sizeof s.uploads[0]); - if (!u.buffer && !create_buffer_cached(dlss_actor::kHeadWords * 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, u.buffer, u.memory, &u.map)) - return u = {}, 0; - float* head = (float*)u.map; - std::memcpy(head, cam.current, 64); - std::memcpy(head + 16, before, 64); - head[32] = cam.jitter[0], head[33] = cam.jitter[1], head[34] = (float)rw, head[35] = (float)rh; - flush_mapped(u.memory, dlss_actor::kHeadWords * 4); - full_barrier(cmd); // (the poses copied) - VkRenderingAttachmentInfo ca{VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO}; - ca.imageView = s.motion.view, ca.imageLayout = VK_IMAGE_LAYOUT_GENERAL; - ca.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, ca.storeOp = VK_ATTACHMENT_STORE_OP_STORE; - VkRenderingInfo ri{VK_STRUCTURE_TYPE_RENDERING_INFO}; - ri.renderArea = {{0, 0}, {rw, rh}}, ri.layerCount = 1, ri.colorAttachmentCount = 1, ri.pColorAttachments = &ca; - vkCmdBeginRendering(cmd, &ri); - vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, s.actor); - const VkViewport viewport{0, 0, (float)rw, (float)rh, 0.0f, 1.0f}; - const VkRect2D scissor{{0, 0}, {rw, rh}}; - vkCmdSetViewport(cmd, 0, 1, &viewport); - vkCmdSetScissor(cmd, 0, 1, &scissor); - const VkDescriptorBufferInfo bi{u.buffer, 0, dlss_actor::kHeadWords * 4}; - const VkDescriptorImageInfo ii{s.point, dview, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet w[2]{{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}, {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}}; - w[0].dstBinding = 0, w[0].descriptorCount = 1, w[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, w[0].pBufferInfo = &bi; - w[1].dstBinding = 1, w[1].descriptorCount = 1, w[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - w[1].pImageInfo = ⅈ - vkCmdPushDescriptorSetKHR(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, s.actor_layout, 0, 2, w); - for (size_t i = 0; i < draws.size(); i++) { // (vertex i of a draw is its i-th index) - vkCmdPushConstants(cmd, s.actor_layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof draws[i], &draws[i]); - vkCmdDraw(cmd, counts[i], 1, 0, 0); - } - vkCmdEndRendering(cmd); - s.actor_draws += draws.size(), s.actor_frames++; - return (int)draws.size(); -} - /// DLSS's preset: the DLSS model row's, RX_VK_DLSS_PRESET over it (upscaler_rows.h dlss_preset) unsigned wanted_preset() { static const char* const env = getenv("RX_VK_DLSS_PRESET"); return upscaler_rows::dlss_preset(opts.dlss_model, env && *env ? env : nullptr); } -/// DLSS's feature for this output size and preset, made when either changes (cmd: its creation's work) -bool make_feature(VkCommandBuffer cmd, uint32_t rw, uint32_t rh, uint32_t ow, uint32_t oh) { +/// DLSS's feature for this output size and preset, made when either changes (cmd: its creation's work): the engine's +/// prepare (vk_temporal.h) +int prepare(VkCommandBuffer cmd, uint32_t rw, uint32_t rh, uint32_t ow, uint32_t oh, uint32_t* in_w, uint32_t* in_h, + const char** fail) { State& s = st(); const unsigned preset = wanted_preset(); if (s.feature && s.feature_ow == ow && s.feature_oh == oh && rw <= s.feature_rw && rh <= s.feature_rh && rw >= s.min_rw && rh >= s.min_rh && s.feature_preset == preset) - return true; + return *in_w = s.feature_rw, *in_h = s.feature_rh, 1; // (why it is made: in the log with its time, so a stall in play can be matched with one) char why[160]; if (!s.feature && !s.features_made) snprintf(why, sizeof why, "the first"); @@ -745,7 +291,7 @@ bool make_feature(VkCommandBuffer cmd, uint32_t rw, uint32_t rh, uint32_t ow, ui s.feature_rw, s.feature_rh); if (s.feature) { NVSDK_NGX_Handle* old = s.feature; - later([old] { NVSDK_NGX_VULKAN_ReleaseFeature(old); }); + temporal_later([old] { NVSDK_NGX_VULKAN_ReleaseFeature(old); }); s.feature = nullptr; } // (the quality mode whose range of render sizes holds this one: the network's 3/4 is DLSS's MaxQuality range) @@ -771,7 +317,7 @@ bool make_feature(VkCommandBuffer cmd, uint32_t rw, uint32_t rh, uint32_t ow, ui break; } } - if (!found) return fail("no DLSS mode takes this render size"); + if (!found) return *fail = "no DLSS mode takes this render size", 0; // (the DLSS model row's preset for every mode, K by default: NVIDIA's transformer model. 0: NVIDIA's own per mode) static_assert(NVSDK_NGX_DLSS_Hint_Render_Preset_K == 11 && NVSDK_NGX_DLSS_Hint_Render_Preset_L == 12 && NVSDK_NGX_DLSS_Hint_Render_Preset_M == 13 && NVSDK_NGX_DLSS_Hint_Render_Preset_Default == 0); @@ -792,8 +338,7 @@ bool make_feature(VkCommandBuffer cmd, uint32_t rw, uint32_t rh, uint32_t ow, ui if (NVSDK_NGX_FAILED(r) || !s.feature) { logf("vk: NVIDIA DLSS: its feature was not made (%#x) for %ux%u -> %ux%u", (unsigned)r, max_w, max_h, ow, oh); s.feature = nullptr; - s.failed = true; - return false; + return *fail = "its feature was not made", -1; } s.feature_rw = max_w, s.feature_rh = max_h, s.feature_ow = ow, s.feature_oh = oh, s.min_rw = min_w, s.min_rh = min_h; s.feature_preset = preset; @@ -802,13 +347,53 @@ bool make_feature(VkCommandBuffer cmd, uint32_t rw, uint32_t rh, uint32_t ow, ui "preset %c%s", (unsigned long long)s.features_made, why, (unsigned long long)texture_frame_no(), made_ms, rw, rh, min_w, min_h, max_w, max_h, ow, oh, (int)mode, upscaler_rows::preset_letter(preset), preset ? "" : " (NVIDIA's default)"); - s.last_frame = 0; // (a new history) + *in_w = s.feature_rw, *in_h = s.feature_rh; + return 2; // (a new history) +} + +bool start() { return ngx_init(); } + +bool evaluate(VkCommandBuffer cmd, const TemporalFrame& f) { + State& s = st(); + NVSDK_NGX_Resource_VK color = resource(f.color, false), depth = resource(f.depth, false), mv = resource(f.motion, false), + output = resource(f.output, true); + NVSDK_NGX_VK_DLSS_Eval_Params ep{}; + ep.Feature.pInColor = &color, ep.Feature.pInOutput = &output; + ep.pInDepth = &depth, ep.pInMotionVectors = &mv; + ep.InJitterOffsetX = f.jitter[0], ep.InJitterOffsetY = f.jitter[1]; // (render pixels, x right and y down) + ep.InRenderSubrectDimensions = {f.render_w, f.render_h}; + ep.InReset = f.reset ? 1 : 0; + ep.InMVScaleX = ep.InMVScaleY = 1.0f; + const NVSDK_NGX_Result r = NGX_VULKAN_EVALUATE_DLSS_EXT(cmd, s.feature, s.params, &ep); + note_work(); // (as at its creation: the barrier after it, and the game's next one, are recorded) + if (NVSDK_NGX_FAILED(r)) { + logf("vk: NVIDIA DLSS: its evaluation failed (%#x)", (unsigned)r); + return false; + } return true; } +void release() { + State& s = st(); + if (!s.feature) return; + NVSDK_NGX_Handle* old = s.feature; + temporal_later([old] { NVSDK_NGX_VULKAN_ReleaseFeature(old); }); + s.feature = nullptr; + s.feature_rw = s.feature_rh = s.feature_ow = s.feature_oh = s.min_rw = s.min_rh = 0; +} + +uint64_t memory() { + State& s = st(); + unsigned long long ngx = 0; + return s.caps && NVSDK_NGX_SUCCEED(NGX_DLSS_GET_STATS(s.caps, &ngx)) ? (uint64_t)ngx : 0; +} + +const TemporalEngine kEngine{"NVIDIA DLSS", "DLSS", start, prepare, evaluate, release, memory}; + } // namespace bool dlss_built() { return true; } +const TemporalEngine* dlss_engine() { return &kEngine; } void dlss_instance_extensions(std::vector& want, const std::vector& all) { if (env_switch("RX_VK_DLSS") == 0) return; @@ -839,383 +424,16 @@ void dlss_device_extensions(std::vector& want, const std::vector= 0 || !dev.device) return; - State& s = st(); - std::lock_guard lk(s.mu); ngx_init(); } -void dlss_note_scene_draw(const Texture* color0, uint32_t width, uint32_t height, Texture* depth, uint64_t camera, - uint64_t camera_size) { - // (only a size the gameplay upscale's output can come from: 1 to 3 times smaller. A smaller target with a camera (the - // pause screen's model views, a reflection) is never the scene; one this size that is not either is left by the checks - // below: the format, the camera's own size and perspective, the largest in the frame, and the upscale's input) - if (!color0 || !depth || !camera || camera_size < 0x230 || !dlss_scene_size(width, height)) return; - if (color0->format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || !dlss_wanted()) return; - State& s = st(); - std::lock_guard lk(s.mu); - const uint64_t now = texture_frame_no(); - if (s.cam.valid && s.cam.frame == now && (uint64_t)width * height <= (uint64_t)s.cam.width * s.cam.height) - return; // (the largest one of the frame wins) - float c[0x230 / 4]; - if (!memory_read_as_submitted(camera, c, sizeof c)) return; - // (the target's size at 0x120; the projection at 0x70, its w row (0, 0, -1, 0) at 0xa0) - if (c[0x120 / 4] != (float)width || c[0x124 / 4] != (float)height || c[0xa0 / 4] != 0.0f || c[0xa4 / 4] != 0.0f || - c[0xa8 / 4] != -1.0f || c[0xac / 4] != 0.0f) - return; - Camera& k = s.cam; - std::memcpy(k.current, c + 0x30 / 4, sizeof k.current); - // (the offset sead's projection gave the frustum, in its third column: 2 * offset, which moves the picture by -that) - k.jitter[0] = -c[0x78 / 4], k.jitter[1] = -c[0x88 / 4]; - k.width = width, k.height = height, k.depth = depth, k.depth_gen = texture_destroy_generation(), k.frame = now; - k.valid = true; -} - -bool dlss_scene_size(uint32_t width, uint32_t height) { - const State& s = st(); - return dlss_checks::scene_size_fits(width, height, s.out_w.load(std::memory_order_relaxed), - s.out_h.load(std::memory_order_relaxed)); -} - -uint32_t dlss_jitter_size(int axis) { - // (RX_VK_DLSS_JITTER=0: none. 0 too once DLSS has not drawn for a while: the camera as the game has it) - static const bool off = env_switch("RX_VK_DLSS_JITTER") == 0; - State& s = st(); - const uint64_t last = s.last_frame.load(std::memory_order_relaxed); - if (off || !dlss_wanted() || s.failed.load(std::memory_order_relaxed) || !last || texture_frame_no() - last > 30) return 0; - if (axis == 2) return s.jitter_phases.load(std::memory_order_relaxed); - return axis ? s.jitter_h.load(std::memory_order_relaxed) : s.jitter_w.load(std::memory_order_relaxed); -} - -int dlss_status(char* b, size_t n) { - State& s = st(); - const uint64_t last = s.last_frame.load(std::memory_order_relaxed); - const int mode = dlss_mode(); - if (!mode || s.failed.load(std::memory_order_relaxed) || !last || texture_frame_no() - last > 30) return 0; - std::lock_guard lk(s.mu); - // (its own video memory: the images and buffers it made here, and what NGX says its feature holds, when it says) - uint64_t own = 0; - for (const Image* im : {&s.color, &s.depth, &s.motion, &s.output}) - if (im->image) own += allocation_size(im->memory); - for (const auto& pb : s.pose_buffers) - if (pb.buffer) own += allocation_size(pb.memory); - for (const auto& u : s.uploads) - if (u.buffer) own += allocation_size(u.memory); - unsigned long long ngx = 0; - const bool ngx_known = s.caps && NVSDK_NGX_SUCCEED(NGX_DLSS_GET_STATS(s.caps, &ngx)); - const uint64_t draws = s.actor_draws - s.status_draws, frames = s.actor_frames - s.status_frames; - char feature[48] = ""; - if (ngx_known) snprintf(feature, sizeof feature, ", its feature %llu MB", ngx >> 20); - char passed[64] = ""; - if (s.status_passed) snprintf(passed, sizeof passed, ", %llu left to the game's network", (unsigned long long)s.status_passed); - snprintf(b, n, "; %s: %llu frames drawn%s, %llu features made, jitter %+.3f %+.3f px%s, actor motion %llu draws in %llu " - "frames (%.3f ms CPU a frame), its images %llu MB%s", - mode == 2 ? "DLAA" : "DLSS", (unsigned long long)s.status_drawn, passed, (unsigned long long)s.features_made, - s.status_jitter[0], s.status_jitter[1], s.status_motion ? "" : " (no motion)", - (unsigned long long)draws, (unsigned long long)frames, frames ? s.actor_ns / 1e6 / (double)frames : 0.0, - (unsigned long long)(own >> 20), feature); - s.status_draws = s.actor_draws, s.status_frames = s.actor_frames, s.actor_ns = 0, s.status_drawn = s.status_passed = 0; - return mode; -} - -void dlss_idle() { - State& s = st(); - if (!s.holding.load(std::memory_order_relaxed)) return; - std::lock_guard lk(s.mu); - const uint64_t now = texture_frame_no(); - if (now - s.asked <= 120) return; // (DLSS in use, or off for under 120 frames (2 s): kept, a row stepped through comes back at once) - if (s.feature) { - NVSDK_NGX_Handle* old = s.feature; - later([old] { NVSDK_NGX_VULKAN_ReleaseFeature(old); }); - s.feature = nullptr; - s.feature_rw = s.feature_rh = s.feature_ow = s.feature_oh = s.min_rw = s.min_rh = 0; - logf("vk: NVIDIA DLSS: not used for 120 frames (the last at frame %llu, now %llu): its feature and images let go; " - "the next upscale with it makes them again", (unsigned long long)s.asked, (unsigned long long)now); - } - for (Image* im : {&s.color, &s.depth, &s.motion, &s.output}) drop_image(*im); - for (auto& pb : s.pose_buffers) // (the actor motion's poses and cameras) - if (pb.buffer) { - const State::PoseBuffer gone = pb; - later([gone] { destroy_buffer(gone.buffer, gone.memory); }); - pb = {}; - } - for (auto& u : s.uploads) - if (u.buffer) { - const State::Upload gone = u; - later([gone] { destroy_buffer(gone.buffer, gone.memory); }); - u = {}; - } - s.poses.clear(), s.skins.clear(), s.model_bones.clear(); - if (!s.depth_views.empty()) { - auto old = std::move(s.depth_views); - s.depth_views.clear(); - later([old] { - for (const auto& [image, view] : old) vkDestroyImageView(dev.device, view, nullptr); - }); - } - s.last_frame = 0; // (a new history when it comes back) - free_dead(false); - s.holding = !s.dead.empty(); -} - -namespace { -/// The bones a skinned draw's vertices use (the largest joint with a weight, plus one), read once a model; 0 when its -/// buffers cannot be read -uint32_t bones_used(const DlssSkin& k) { - if (k.count <= 0 || k.count > (1 << 20) || k.itype < 0 || k.itype > 2 || !k.stride) return 0; - std::vector idx((size_t)k.count << k.itype); - if (!memory_read_as_submitted(k.ibuf, idx.data(), idx.size())) return 0; - uint32_t lo = UINT32_MAX, hi = 0; - for (int i = 0; i < k.count; i++) { - uint32_t v = 0; - std::memcpy(&v, idx.data() + ((size_t)i << k.itype), (size_t)1 << k.itype); - lo = std::min(lo, v), hi = std::max(hi, v); - } - const int64_t first = (int64_t)k.base_vertex + lo, n = (int64_t)hi - lo + 1; - if (first < 0 || n > (1 << 20) || (uint64_t)(first + n) * k.stride > k.vsize) return 0; - std::vector v((size_t)n * k.stride); - if (!memory_read_as_submitted(k.vbuf + (uint64_t)first * k.stride, v.data(), v.size())) return 0; - uint32_t most = 0; - for (int64_t i = 0; i < n; i++) { - const uint8_t* q = v.data() + (size_t)i * k.stride; - for (int set = 0; set < k.sets; set++) - for (int j = 0; j < 4; j++) { - if (!q[k.weights[set] + j]) continue; - uint32_t joint = 0; - if (k.formats & (dlss_actor::kByteJoints << set)) joint = q[k.joints[set] + j]; - else std::memcpy(&joint, q + k.joints[set] + 2 * j, 2); - most = std::max(most, joint + 1); - } - } - return most; -} -} // namespace - -void dlss_note_skinned_draw(const DlssSkin& k) { - static const bool off = env_switch("RX_VK_DLSS_ACTORS") == 0; - if (off || !dlss_wanted() || !k.depth || !k.bones || k.bones_size < 48 || !k.object || k.object_size < 0x4c) return; - State& s = st(); - std::lock_guard lk(s.mu); - if (s.skins.size() >= 512) s.skins.clear(); // (no upscale for a while: what was kept is stale) - struct Timed { - State& s; - std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); - ~Timed() { s.actor_ns += (std::chrono::steady_clock::now() - t0).count(); } - } timed{s}; - State::Skin d; - d.k = k; - const uint64_t model = dlss_actor::key(k.vbuf, k.ibuf, k.count, k.base_vertex, k.program, 0); - // (only the bones its vertices use are taken: a palette holds up to 280, a model uses a few dozen) - auto mb = s.model_bones.find(model); - if (mb == s.model_bones.end()) { - if (s.model_bones.size() > 4096) s.model_bones.clear(); - mb = s.model_bones.emplace(model, bones_used(k)).first; - } - d.nbones = (uint32_t)std::min(mb->second, std::min(k.bones_size / 48, 280)); - if (!d.nbones) return; - int nth = 0; // (the same model drawn twice in a frame: the n-th draw of it is the n-th the frame before) - for (const auto& o : s.skins) nth += dlss_actor::same_model(o.key, model); - d.key = dlss_actor::key(k.vbuf, k.ibuf, k.count, k.base_vertex, k.program, nth); - s.skins.push_back(std::move(d)); -} - -bool dlss_upscale(VkCommandBuffer cmd, uint64_t key, bool known, Texture* in, VkImageView in_view, Texture* out, - VkImageView out_view, uint32_t out_level) { - if (!dlss_wanted()) return false; - State& s = st(); - std::lock_guard lk(s.mu); - if (!known) { // (another network of the upscale's shape: only the scene into the picture, the rest untouched and unsaid) - const Camera& c = s.cam; - // (the gameplay networks' own formats: they sample the scene's B10G11R11 colour and write the B10G11R11 picture. - // Other cooperative-matrix networks of the same shape (one RGBA8 into A2B10G10R10, RGB9E5 into B10G11R11) are not it) - if (!in || !out || in->format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || out->format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || - (out->width >> out_level) <= in->width) - return false; - note_output(out, out_level); - // (one in the gameplay networks' formats whose input is not this frame's scene: counted for the status line) - if (!c.valid || texture_frame_no() - c.frame > 1 || in->width != c.width || in->height != c.height) - return s.status_passed++, false; - static std::atomic told{0}; - if (told.exchange(key) != key) logf("vk: NVIDIA DLSS: network %016llx is the gameplay upscale (%ux%u -> %ux%u)", - (unsigned long long)key, in->width, in->height, out->width >> out_level, - out->height >> out_level); - } - free_dead(false); - if (known && out && out->format == VK_FORMAT_B10G11R11_UFLOAT_PACK32) note_output(out, out_level); - const Camera cam = s.cam; - s.cam.valid = false; // (each frame's own camera) - struct SkinsDone { // (the skinned draws, each frame's own too: actor_motion takes them, or they go here) - State& s; - ~SkinsDone() { s.skins.clear(); } - } skins_done{s}; - - if (s.failed) return fail("DLSS failed earlier"); - s.asked = texture_frame_no(), s.holding = true; // (not after a failure: what it holds is let go, dlss_idle) - if (!in || !in_view || !out || !out_view) return fail("no input or output view"); - if (!cam.valid || texture_frame_no() - cam.frame > 1) return fail("no scene camera this frame"); - if (cam.depth_gen != texture_destroy_generation()) return fail("the scene depth may be gone"); - const uint32_t rw = in->width, rh = in->height; - const uint32_t ow = std::max(1u, out->width >> out_level), oh = std::max(1u, out->height >> out_level); - if (rw != cam.width || rh != cam.height) return fail("the input is not the scene's size"); - if (cam.depth->width != rw || cam.depth->height != rh || !(cam.depth->usage & VK_IMAGE_USAGE_SAMPLED_BIT) || - cam.depth->samples != VK_SAMPLE_COUNT_1_BIT || !(cam.depth->aspect & VK_IMAGE_ASPECT_DEPTH_BIT)) - return fail("unexpected scene depth"); - if (out->format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || !dlss_checks::scene_size_fits(rw, rh, ow, oh)) - return fail("unexpected output"); - if (!ngx_init()) { - s.failed = true; - return fail("DLSS not available"); - } - if (!make_pipelines()) { - s.failed = true; - return fail("its shaders were not made"); - } - const VkImageView dview = depth_view(cam.depth); - if (!dview) return fail("no view of the scene depth"); - // (DLAA: DLSS at the render size, anti-aliasing alone; its picture then scaled to the output's by dlss_out.comp. With - // it the 3D is drawn at full scale, dlss_full_scale) - const bool dlaa = dlss_mode() == 2; - const uint32_t dw = dlaa ? rw : ow, dh = dlaa ? rh : oh; - // (from here commands are recorded, NGX's own too: whichever way this returns, the program's descriptor pushes are gone from - // the bind point and are pushed again) - struct Disturbed { - ~Disturbed() { descriptors_disturbed(); } - } disturbed; - if (!make_feature(cmd, rw, rh, dw, dh)) return false; - if (!make_image(cmd, s.color, VK_FORMAT_R16G16B16A16_SFLOAT, s.feature_rw, s.feature_rh) || - !make_image(cmd, s.depth, VK_FORMAT_R32_SFLOAT, s.feature_rw, s.feature_rh) || - !make_image(cmd, s.motion, VK_FORMAT_R16G16_SFLOAT, s.feature_rw, s.feature_rh, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) || - !make_image(cmd, s.output, VK_FORMAT_R16G16B16A16_SFLOAT, dw, dh)) - return fail("its images were not made"); - - const uint64_t now = texture_frame_no(); - const uint64_t last = s.last_frame; - const bool reset = !last || now - last > 2; - struct { - float reproject[16]; - float jitter[2]; - int32_t size[2]; - int32_t flags; - } pc{}; - static const bool show_motion = getenv("RX_VK_DLSS_DEBUG") && !strcmp(getenv("RX_VK_DLSS_DEBUG"), "motion"); - // (the motion without the jitter: from this frame's jittered clip space to the frame before's without its jitter. - // A jitter moves clip x by jitter * w: taken off the previous matrix's first two rows) - // (the frame before's camera as DLSS last drew it: the buffer's own slot for it, 0x180, holds zeros in this version) - float previous[16]; - std::memcpy(previous, s.last_vp, sizeof previous); - dlss_math::unjitter(previous, s.last_jitter); - const bool motion = !reset && dlss_math::reprojection(cam.current, previous, pc.reproject); - pc.jitter[0] = cam.jitter[0], pc.jitter[1] = cam.jitter[1]; - pc.size[0] = (int32_t)rw, pc.size[1] = (int32_t)rh, pc.flags = (motion ? 1 : 0) | (show_motion ? 2 : 0); - - full_barrier(cmd); // (the scene's colour and depth, written before) - { - const VkDescriptorImageInfo info[5] = {{s.point, in_view, VK_IMAGE_LAYOUT_GENERAL}, - {s.point, dview, VK_IMAGE_LAYOUT_GENERAL}, - {VK_NULL_HANDLE, s.color.view, VK_IMAGE_LAYOUT_GENERAL}, - {VK_NULL_HANDLE, s.depth.view, VK_IMAGE_LAYOUT_GENERAL}, - {VK_NULL_HANDLE, s.motion.view, VK_IMAGE_LAYOUT_GENERAL}}; - VkWriteDescriptorSet w[5]{}; - for (uint32_t i = 0; i < 5; i++) { - w[i] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; - w[i].dstBinding = i, w[i].descriptorCount = 1, w[i].pImageInfo = &info[i]; - w[i].descriptorType = i < 2 ? VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; - } - vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, s.prep); - vkCmdPushDescriptorSetKHR(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, s.prep_layout, 0, 5, w); - vkCmdPushConstants(cmd, s.prep_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof pc, &pc); - vkCmdDispatch(cmd, (rw + 7) / 8, (rh + 7) / 8, 1); - } - full_barrier(cmd); - // (what moves by itself: the skinned draws drawn again over the camera's motion, with their own) - const int actors = actor_motion(cmd, cam, previous, rw, rh, dview, motion); - if (actors) full_barrier(cmd); - if (show_motion && actors) { // (RX_VK_DLSS_DEBUG=motion: the motion as it is now, the actors' in it) - pc.flags = 4; - vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, s.prep); - const VkDescriptorImageInfo info[5] = {{s.point, in_view, VK_IMAGE_LAYOUT_GENERAL}, - {s.point, dview, VK_IMAGE_LAYOUT_GENERAL}, - {VK_NULL_HANDLE, s.color.view, VK_IMAGE_LAYOUT_GENERAL}, - {VK_NULL_HANDLE, s.depth.view, VK_IMAGE_LAYOUT_GENERAL}, - {VK_NULL_HANDLE, s.motion.view, VK_IMAGE_LAYOUT_GENERAL}}; - VkWriteDescriptorSet w[5]{}; - for (uint32_t i = 0; i < 5; i++) { - w[i] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; - w[i].dstBinding = i, w[i].descriptorCount = 1, w[i].pImageInfo = &info[i]; - w[i].descriptorType = i < 2 ? VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; - } - vkCmdPushDescriptorSetKHR(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, s.prep_layout, 0, 5, w); - vkCmdPushConstants(cmd, s.prep_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof pc, &pc); - vkCmdDispatch(cmd, (rw + 7) / 8, (rh + 7) / 8, 1); - full_barrier(cmd); - } - NVSDK_NGX_Resource_VK color = resource(s.color, false), depth = resource(s.depth, false), mv = resource(s.motion, false), - output = resource(s.output, true); - NVSDK_NGX_VK_DLSS_Eval_Params ep{}; - ep.Feature.pInColor = &color, ep.Feature.pInOutput = &output; - ep.pInDepth = &depth, ep.pInMotionVectors = &mv; - // (the jitter in render pixels, x right and y down) - float jitter_px[2]; - dlss_math::jitter_to_pixels(cam.jitter, rw, rh, jitter_px); - ep.InJitterOffsetX = jitter_px[0], ep.InJitterOffsetY = jitter_px[1]; - ep.InRenderSubrectDimensions = {rw, rh}; - ep.InReset = reset ? 1 : 0; - ep.InMVScaleX = ep.InMVScaleY = 1.0f; - const NVSDK_NGX_Result r = NGX_VULKAN_EVALUATE_DLSS_EXT(cmd, s.feature, s.params, &ep); - note_work(); // (as at its creation: the barrier after it, and the game's next one, are recorded) - if (NVSDK_NGX_FAILED(r)) { - logf("vk: NVIDIA DLSS: its evaluation failed (%#x): the game's upscale from now on", (unsigned)r); - s.failed = true; - return false; // (the network writes its output over whatever the evaluation did) - } - full_barrier(cmd); - { - const int32_t opc[2] = {(int32_t)ow, (int32_t)oh}; - const VkDescriptorImageInfo info[2] = {{dlaa ? s.linear : s.point, s.output.view, VK_IMAGE_LAYOUT_GENERAL}, - {VK_NULL_HANDLE, out_view, VK_IMAGE_LAYOUT_GENERAL}}; - VkWriteDescriptorSet w[2]{{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}, {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}}; - w[0].dstBinding = 0, w[0].descriptorCount = 1, w[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - w[0].pImageInfo = &info[0]; - w[1].dstBinding = 1, w[1].descriptorCount = 1, w[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; - w[1].pImageInfo = &info[1]; - vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, s.out); - vkCmdPushDescriptorSetKHR(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, s.out_layout, 0, 2, w); - vkCmdPushConstants(cmd, s.out_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof opc, opc); - vkCmdDispatch(cmd, (ow + 7) / 8, (oh + 7) / 8, 1); - } - s.last_frame = now; - s.last_jitter[0] = cam.jitter[0], s.last_jitter[1] = cam.jitter[1]; - // (for the status line, dlss_status: the jitter DLSS was given, in render pixels) - s.status_jitter[0] = ep.InJitterOffsetX, s.status_jitter[1] = ep.InJitterOffsetY, s.status_motion = motion; - s.status_drawn++; - - std::memcpy(s.last_vp, cam.current, sizeof s.last_vp); - s.jitter_w = rw, s.jitter_h = rh, s.jitter_phases = (uint32_t)dlss_jitter_phases(rw, rh, dw, dh); - if (s.reason) logf("vk: NVIDIA DLSS draws the upscale again (frame %llu)", (unsigned long long)now); - s.reason = nullptr; - // (RX_VK_DLSS_DEBUG=alternate, a test's reference: every other frame the game's own upscale over DLSS's, which still - // draws and keeps its history. A frame of DLSS then has the game's picture of the frames either side to be measured against) - static const bool alternate = getenv("RX_VK_DLSS_DEBUG") && !strcmp(getenv("RX_VK_DLSS_DEBUG"), "alternate"); - if (alternate && (now & 1)) return false; - if (!s.told_first) { - s.told_first = true; - logf("vk: NVIDIA DLSS draws OoT's upscale: %ux%u -> %ux%u%s", rw, rh, ow, oh, show_motion ? " (its motion shown)" : ""); - } - return true; -} - #else // (no NGX SDK in this build: DLSS is not there) bool dlss_built() { return false; } +const TemporalEngine* dlss_engine() { return nullptr; } void dlss_probe() {} void dlss_instance_extensions(std::vector&, const std::vector&) {} void dlss_device_extensions(std::vector&, const std::vector&) {} -void dlss_note_scene_draw(const Texture*, uint32_t, uint32_t, Texture*, uint64_t, uint64_t) {} -bool dlss_scene_size(uint32_t, uint32_t) { return false; } -bool dlss_upscale(VkCommandBuffer, uint64_t, bool, Texture*, VkImageView, Texture*, VkImageView, uint32_t) { return false; } -void dlss_idle() {} -int dlss_status(char*, size_t) { return 0; } -uint32_t dlss_jitter_size(int) { return 0; } -void dlss_note_skinned_draw(const DlssSkin&) {} #endif diff --git a/gpu/vk/vk_dlss.h b/gpu/vk/vk_dlss.h index 6d6eee903..5038de5f4 100644 --- a/gpu/vk/vk_dlss.h +++ b/gpu/vk/vk_dlss.h @@ -1,5 +1,6 @@ -// NVIDIA DLSS Super Resolution in place of OoT's gameplay upscale network (vk_dlss.cpp). Built in only with NVIDIA's NGX -// SDK (HOOKSHOT_NGX, scripts/ngx_sdk.py: Linux and Windows); without it these say DLSS is not there and the game's network runs. +// NVIDIA DLSS Super Resolution, one of the temporal upscaler's engines (vk_temporal.h; vk_dlss.cpp). Built in only with +// NVIDIA's NGX SDK (HOOKSHOT_NGX, scripts/ngx_sdk.py: Linux and Windows); without it these say DLSS is not there and the +// game's network runs. #pragma once #include @@ -8,7 +9,7 @@ namespace mkwvk { -struct Texture; +struct TemporalEngine; /// Whether this build has DLSS in it (HOOKSHOT_NGX) bool dlss_built(); @@ -19,61 +20,11 @@ DlssWhy dlss_why(); /// Asks NGX once whether DLSS runs on this device, so dlss_why knows before the first upscale (the menu, when it opens). /// Nothing off an NVIDIA device with NGX's extensions, or in a build without DLSS void dlss_probe(); -/// What is asked for: 0 off, 1 DLSS (in place of the game's gameplay upscale, at any render share), 2 DLAA (anti-aliasing at the render size, the 3D at full scale): -/// the Insert menu's Upscaler row (opts.dlss), or RX_VK_DLSS=0 / 1 / 2 over it. 0 when dlss_why() says it cannot run -int dlss_mode(); -bool dlss_wanted(); // (dlss_mode() != 0) -/// DLAA: the 3D drawn at full scale, whatever the Render resolution row says (rx_option 2 and 3) -bool dlss_full_scale(); /// The instance's extensions DLSS needs, added to `want` when the loader has them (all: what it has) void dlss_instance_extensions(std::vector& want, const std::vector& all); /// The device's extensions DLSS needs, added to `want` when the device has them void dlss_device_extensions(std::vector& want, const std::vector& all); -/// A draw into a scene-sized target (colour target 0 and the depth target), with the vertex stage's uniform buffer 3 at -/// `camera` (its size): the camera of the frame, when that buffer holds one -void dlss_note_scene_draw(const Texture* color0, uint32_t width, uint32_t height, Texture* depth, uint64_t camera, - uint64_t camera_size); -/// Whether a target of width x height can be the scene DLSS draws from: the gameplay upscale's output (as last seen) is 1 to 3 -/// times its size (DLAA to Ultra Performance; dlss_checks.h). False before the first upscale, and in a build without DLSS -bool dlss_scene_size(uint32_t width, uint32_t height); -/// A skinned draw into a scene-sized target (vk_draw.cpp finds them by their vertex layout): what DLSS's motion needs to draw -/// it again with this frame's bones and the frame before's (actor motion: Link, the others, what moves by itself) -struct DlssSkin { - const Texture* depth = nullptr; // (its depth target: only the scene's are taken) - uint64_t vbuf = 0, vsize = 0; // (vertex stream 0) - uint32_t stride = 0; - uint32_t pos = 0; // (byte offsets in a vertex: the position, RGB32F or RGBA16F) - uint32_t joints[2]{}, weights[2]{}; // (RGBA16UI or RGBA8UI joints and RGBA8 weights, four a set; sets: 1 or 2) - int sets = 0; - uint32_t formats = 0; // (dlss_actor::Push::formats) - uint64_t ibuf = 0; - int itype = 0, count = 0, base_vertex = 0; - uint64_t bones = 0, bones_size = 0; // (the palette: 3 x 4 row-major matrices, model space to the camera's world) - uint64_t object = 0, object_size = 0; // (the object's buffer: its origin at 0x40) - uint64_t program = 0; -}; -void dlss_note_skinned_draw(const DlssSkin& k); - -/// A gameplay upscale's dispatch (program `key`; vk_draw.cpp's dlss_stand_in finds them: the 4/3 network fd029a317a9a4b94 at -/// the game's usual 3/4, the same-size pass 9dc98871eb7fc101 at full size, and one network of its own for each other share): its input as it samples it (in, in_view) and its output's storage -/// view (out at level 0). known: one of the gameplay networks by its key (the 4/3 one, the full-size pass); else a network of -/// that shape (the fixed shares' own), taken only when its input is this frame's scene and its output the picture, and left -/// to run without a word otherwise. true: DLSS wrote the output (the dispatch is left out); false: the network runs, the -/// reason logged -bool dlss_upscale(VkCommandBuffer cmd, uint64_t key, bool known, Texture* in, VkImageView in_view, Texture* out, - VkImageView out_view, uint32_t out_level); - -/// Once a frame (the game's present): after 120 frames without DLSS (off, or the upscale not taken) its feature, images and -/// deferred frees are let go, so its video memory comes back. Nothing to do costs one atomic load -void dlss_idle(); - -/// For the renderer's status line (vk_replay.cpp status_tick): while DLSS or DLAA draws, its details since the last call -/// into b ("; DLSS: jitter ..., actor motion ..., its images ... MB") and 1 (DLSS) or 2 (DLAA); else 0 and b left alone -int dlss_status(char* b, size_t n); - -/// The scene's width (axis 0) or height (1) for the host's camera jitter (nx2/oot_jitter.py, HOS_OPT_DLSS_JITTER_W and _H): 0 when -/// there is to be none (DLSS not drawing, or RX_VK_DLSS_JITTER=0). Axis 2: the jitter sequence's length for DLSS's scale -/// (dlss_jitter_phases, HOS_OPT_DLSS_JITTER_PHASES) -uint32_t dlss_jitter_size(int axis); +/// DLSS as the temporal upscaler's engine (DLSS and DLAA on the Upscaler row); nullptr in a build without it +const TemporalEngine* dlss_engine(); } // namespace mkwvk diff --git a/gpu/vk/vk_draw.cpp b/gpu/vk/vk_draw.cpp index c981dacf9..6be2a0c0b 100644 --- a/gpu/vk/vk_draw.cpp +++ b/gpu/vk/vk_draw.cpp @@ -34,7 +34,7 @@ #include "vk_paths.h" #include "vk_draw.h" #include "dlss_actor.h" -#include "vk_dlss.h" +#include "vk_temporal.h" #include "vk_dump.h" #include "vk_gprof.h" #include "vk_jobs.h" @@ -5024,11 +5024,11 @@ void bind_index(VkCommandBuffer cmd, VkBuffer buffer, VkDeviceSize offset, VkInd last_ib = {b, (uint64_t)offset, (uint32_t)type, true}; } -/// (hookshot, a diagnostic for NVIDIA DLSS's motion: vk_dlss.cpp) RX_VK_DLSS_TRACE=: every that many frames, one frame's -/// draws into targets of a size DLSS can take as the scene (dlss_scene_size), logged: the program, its targets' formats, the geometry and the uniform buffers +/// (hookshot, a diagnostic for the temporal upscaler's motion: vk_temporal.cpp) RX_VK_DLSS_TRACE=: every that many frames, one frame's +/// draws into targets of a size the upscaler can take as the scene (temporal_scene_size), logged: the program, its targets' formats, the geometry and the uniform buffers static void dlss_trace_draw(const NvnState& s, const Program& p, const DrawArgs& d, const Targets& at) { static const int every = getenv("RX_VK_DLSS_TRACE") ? atoi(getenv("RX_VK_DLSS_TRACE")) : 0; - if (every <= 0 || !dlss_scene_size(at.width, at.height)) return; + if (every <= 0 || !temporal_scene_size(at.width, at.height)) return; const uint64_t f = texture_frame_no(); if (f % (uint64_t)every != (uint64_t)every / 2) return; char line[3072]; @@ -5059,20 +5059,21 @@ static void dlss_trace_draw(const NvnState& s, const Program& p, const DrawArgs& logf("%s", line); } -/// (hookshot) A skinned draw into the scene, for NVIDIA DLSS's actor motion (vk_dlss.cpp). OoT's characters and the things that +/// (hookshot) A skinned draw into the scene, for the temporal upscaler's actor motion (vk_temporal.cpp: DLSS's and FSR's). OoT's characters and the things that /// move by themselves are indexed draws of one vertex stream: the position at attribute 0 (RGB32F or RGBA16F), the weights at 4 /// (and 5, RGBA8), the joints at 6 (and 7, RGBA16UI or RGBA8UI), the bone palette in the vertex stage's uniform buffer 6 (NVN's /// binding 3: 3 x 4 matrices, a whole number of them) and the object's origin in its buffer 4 (binding 1, at 0x40) -static void dlss_skin_check(const NvnState& s, const Program& p, const DrawArgs& d, const Targets& at) { +static void skin_check(const NvnState& s, const Program& p, const DrawArgs& d, const Targets& at) { if (d.mesh || !d.indexed || d.indirect || d.instances != 1 || !at.n || !at.color[0] || !at.depth || - !dlss_scene_size(at.width, at.height) || at.color_format[0] != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || s.nvattr < 7 || !dlss_wanted()) + !temporal_scene_size(at.width, at.height) || at.color_format[0] != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || s.nvattr < 7 || + !temporal_wanted()) return; const auto attr = [&](int i) { return *reinterpret_cast(&s.vattr[i]); }; const nvn_va_st pos = attr(0); if ((pos.fmt != NVN_FORMAT_RGB32F && pos.fmt != NVN_FORMAT_RGBA16F) || pos.stream != 0) return; const nvn_p_bind_buf& bones = s.ubo[0][3]; if (!bones.addr || bones.size < 48 || bones.size % 48) return; - DlssSkin k; + SkinnedDraw k; k.formats = pos.fmt == NVN_FORMAT_RGBA16F ? dlss_actor::kHalfPosition : 0; for (int set = 0; set < 2 && 6 + set < s.nvattr; set++) { const nvn_va_st w = attr(4 + set), j = attr(6 + set); @@ -5088,7 +5089,7 @@ static void dlss_skin_check(const NvnState& s, const Program& p, const DrawArgs& k.ibuf = d.ibuf, k.itype = d.itype, k.count = d.count, k.base_vertex = d.base_vertex; k.bones = bones.addr, k.bones_size = bones.size, k.object = s.ubo[0][1].addr, k.object_size = s.ubo[0][1].size; k.program = p.key; - dlss_note_skinned_draw(k); + temporal_note_skinned_draw(k); } void draw(VkCommandBuffer cmd, NvnState& s, const DrawArgs& d) { @@ -5104,9 +5105,10 @@ void draw(VkCommandBuffer cmd, NvnState& s, const DrawArgs& d) { } const Targets at = bare_targets(s, targets(s)); if (!at.width || !at.height) SKIP_HERE(); - // (hookshot) the scene's camera and depth for NVIDIA DLSS (vk_dlss.cpp): the vertex stage's uniform buffer 3 (NVN's binding 0) + // (hookshot) the scene's camera and depth for the temporal upscaler (vk_temporal.cpp): the vertex stage's uniform buffer 3 + // (NVN's binding 0) if (at.n && at.color[0] && at.depth) - dlss_note_scene_draw(at.color[0], at.width, at.height, at.depth, s.ubo[0][0].addr, s.ubo[0][0].size); + temporal_note_scene_draw(at.color[0], at.width, at.height, at.depth, s.ubo[0][0].addr, s.ubo[0][0].size); uint8_t types[8]; for (int i = 0; i < 8; i++) { const VkFormat f = i < at.n ? at.color_format[i] : VK_FORMAT_UNDEFINED; @@ -5242,7 +5244,7 @@ void draw(VkCommandBuffer cmd, NvnState& s, const DrawArgs& d) { if (!pl) SKIP_HERE(); cur_program = &p; dlss_trace_draw(s, p, d, at); - dlss_skin_check(s, p, d, at); + skin_check(s, p, d, at); if (!record_progs().empty() && std::find(record_progs().begin(), record_progs().end(), p.key) != record_progs().end()) record_draw(s, p, d, at); // (RX_VK_DUMP_EACH=,...: in a dump, the pass ends after each draw of those programs, so every one of them is @@ -6092,14 +6094,14 @@ static bool typed_upscale(VkCommandBuffer cmd, VkImageView input_view, VkImageVi if (temporal) probe_image(cmd, out, level, 0, key, 't'); return true; } -/// (hookshot) NVIDIA DLSS in place of the gameplay upscale network (vk_dlss.cpp), when it is on and its inputs are as expected: -/// the network's input as it samples it, its output's storage view -bool dlss_stand_in(VkCommandBuffer cmd, const NvnState& s, const Program& p) { +/// (hookshot) A temporal upscaler (NVIDIA DLSS, AMD FSR 4: vk_temporal.cpp) in place of the gameplay upscale network, when +/// one is on and its inputs are as expected: the network's input as it samples it, its output's storage view +bool temporal_stand_in(VkCommandBuffer cmd, const NvnState& s, const Program& p) { // (the 4/3 network and the full-size pass by their keys; the game's other gameplay upscales, one network for each fixed // share of the Render resolution row (67%: 6676619641320ba7, 58% f10782f9e58b6a7c, 50% 72ba090c70fe2614, 33% - // 0775215f8da720e6 at 2048x1152), by their shape: a network sampling one texture into one image. dlss_upscale takes those + // 0775215f8da720e6 at 2048x1152), by their shape: a network sampling one texture into one image. temporal_upscale takes those // only when the texture is this frame's scene, in the gameplay networks' input format, and the image the picture) - if (!dlss_wanted() || p.stages.empty()) return false; + if (!temporal_wanted() || p.stages.empty()) return false; const Stage& st = p.stages[0]; if (!gameplay_network(p.key) && !(st.info.uses_cooperative_matrix && st.info.texture_descriptors.size() == 1 && st.info.image_descriptors.size() == 1)) @@ -6116,7 +6118,7 @@ bool dlss_stand_in(VkCommandBuffer cmd, const NvnState& s, const Program& p) { uint32_t level = 0; const uint32_t out_handle = handle_of(s, st, od, 0); if (out_handle) texture_view_of(out_handle, VK_IMAGE_VIEW_TYPE_2D, ViewUse::Storage, image_format(od.format), &out, &level); - const bool done = dlss_upscale(cmd, p.key, gameplay_network(p.key), in, dw().imgs[0].imageView, out, dw().imgs[ntex].imageView, level); + const bool done = temporal_upscale(cmd, p.key, gameplay_network(p.key), in, dw().imgs[0].imageView, out, dw().imgs[ntex].imageView, level); // (its actor motion may have drawn with a pipeline, viewport and descriptors of its own: the next draw sets all of its again) last_dyn_valid = false, last_desc_valid = false, last_vb_valid = false, last_pipeline = VK_NULL_HANDLE; vertex_set.on = false, last_ib.on = false; @@ -6756,7 +6758,7 @@ void dispatch(VkCommandBuffer cmd, NvnState& s, uint32_t x, uint32_t y, uint32_t logf("%s", line); } } - if (dlss_stand_in(cmd, s, p)) return void(st_dispatches++); + if (temporal_stand_in(cmd, s, p)) return void(st_dispatches++); if (taau_stand_in(cmd, s, p)) return void(st_dispatches++); if (!p.compute_pipeline) SKIP_HERE(); dump_ssbos(cmd, p, "before"); diff --git a/gpu/vk/vk_fsr4.cpp b/gpu/vk/vk_fsr4.cpp new file mode 100644 index 000000000..5ecd83bb6 --- /dev/null +++ b/gpu/vk/vk_fsr4.cpp @@ -0,0 +1,462 @@ +// (hookshot) AMD FSR 4, one of the temporal upscaler's engines (vk_fsr4.h). The frame chain it draws with is +// vk_temporal.cpp's, the same as NVIDIA DLSS's: the colour as the game's network samples it, the depth, the motion (the +// camera's and the skinned draws' own) and the camera jitter. Here are AMD's library, the upscale context and its dispatch. +// The host side follows the API guide of FidelityFX SDK 2.4 (Kits/FidelityFX/docs/getting-started/ffx-api.md): a Vulkan +// backend description chained to the upscale context description, inputs read in SHADER_READ_ONLY_OPTIMAL, the output +// written in GENERAL. First written for Lichii's OoT branch (madebyjordan, vk_fsr4_upscaler.cpp), GPL-3.0-or-later; its Linux +// path (a Vulkan FSR 4 library through dlopen) by N3oTraX. +// +// AMD's library is looked for in RX_VK_FSR_DIR= when set, the executable's folder (bin\ beside the launcher), then +// the game folder: amd_fidelityfx_loader_vk.dll, with amd_fidelityfx_upscaler_vk.dll beside it (Linux: a Vulkan FSR 4 +// library, amd_fidelityfx_upscaler_vk.so). Of the upscalers it lists for this GPU the newest FSR 4 is taken, never an older +// one in its place. RX_VK_FSR=0: FSR out for this run. +#include "vk_fsr4.h" + +#include +#include + +#include "vk_internal.h" +#include "vk_temporal.h" + +#ifdef HOOKSHOT_FSR +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#endif + +#include "vk_paths.h" +#include "vk_texture.h" + +#include "api/include/ffx_api.h" +#include "api/include/ffx_api_types.h" +#include "api/include/vk/ffx_api_vk.h" +#include "upscalers/include/ffx_upscale.h" +#endif +#include "vk_work.h" // (after the others: the barriers here count as work for full_barrier) + +namespace mkwvk { + +namespace { +int env_switch(const char* name) { // (-1: unset) + const char* e = getenv(name); + return e && *e ? atoi(e) : -1; +} +// (what is known of FSR on this device: -1 not yet, else an FsrWhy. Set when AMD's library has been asked) +std::atomic g_why{-1}; +} // namespace + +FsrWhy fsr4_why() { + if (!fsr4_built()) return FSR_NOT_BUILT; + static const bool env_off = env_switch("RX_VK_FSR") == 0; + if (env_off) return FSR_ENV_OFF; + const int w = g_why.load(std::memory_order_relaxed); + return w < 0 ? FSR_OK : (FsrWhy)w; +} + +#ifdef HOOKSHOT_FSR + +namespace { + +#ifdef _WIN32 +constexpr const char* kLoader = "amd_fidelityfx_loader_vk.dll"; +#else +// (linux) no AMD loader exists there: the upscaler library itself, which exports the ffx entry points (a Vulkan FSR 4 build) +constexpr const char* kLoader = "amd_fidelityfx_upscaler_vk.so"; +#endif + +/// AMD's library, loaded once (the menu's thread may ask first: fsr4_probe), and the upscaler it has for this device +struct Library { + std::mutex mu; + bool tried = false, ok = false; + HMODULE lib = nullptr; + PfnFfxCreateContext create = nullptr; + PfnFfxDestroyContext destroy = nullptr; + PfnFfxQuery query = nullptr; + PfnFfxDispatch dispatch = nullptr; + uint64_t version_id = 0; + VkDeviceContext device_context{}; +}; +Library& lib() { + static Library l; + return l; +} + +/// An upscale context and the descriptions it was made from: it keeps pointers into them until it is destroyed, which +/// happens a few frames after it is let go (temporal_later), so each context has its own +struct Context { + ffxCreateBackendVKDesc backend{}; + ffxOverrideVersion version{}; +#ifndef _WIN32 + ffxCreateBackendVKDesc_DEPRECATED legacy_backend{}; // (linux) the backend description a Vulkan FSR 4 build like fsr4-vk reads +#endif + ffxCreateContextDescUpscale upscale{}; + ffxContext context = nullptr; + uint32_t width = 0, height = 0; // (its output, which is also the most the render size can be) + uint64_t memory = 0; // (what AMD's library says it holds) + ~Context() { + if (context) lib().destroy(&context, nullptr); + } +}; + +/// The engine's state: used with the temporal upscaler's lock held +struct State { + std::shared_ptr context; + std::chrono::steady_clock::time_point last{}; + uint64_t evals = 0; + int told_resets = 0; +}; +State& st() { + static State s; + return s; +} + +void on_message(uint32_t type, const wchar_t* message) { + char text[512] = {}; + if (message) WideCharToMultiByte(CP_UTF8, 0, message, -1, text, (int)sizeof text - 1, nullptr, nullptr); + logf("vk: AMD FSR 4: AMD's %s: %s", type == FFX_API_MESSAGE_TYPE_ERROR ? "error" : "warning", text); +} + +/// The folders the library is looked for in: RX_VK_FSR_DIR, the executable's (bin\ beside the launcher), the game folder +std::vector library_dirs() { + std::vector d; + if (const char* e = getenv("RX_VK_FSR_DIR"); e && *e) d.emplace_back(e); + char exe[MAX_PATH]; + DWORD n = GetModuleFileNameA(nullptr, exe, MAX_PATH); + if (n && n < MAX_PATH) { + std::string s(exe, n); + d.push_back(s.substr(0, s.find_last_of("\\/"))); + } + n = game_module_path(exe, MAX_PATH); + if (n && n < MAX_PATH) { + std::string s(exe, n); + s = s.substr(0, s.find_last_of("\\/")); + if (d.empty() || d.back() != s) d.push_back(s); + } + return d; +} + +void* symbol(const char* name) { +#ifdef _WIN32 + return (void*)GetProcAddress(lib().lib, name); +#else + return dlsym(lib().lib, name); +#endif +} + +/// AMD's library on this device, once: false when FSR 4 cannot run here (g_why says why, the log too) +bool load() { + Library& l = lib(); + std::lock_guard lk(l.mu); + if (l.tried) return l.ok; + l.tried = true; + const auto no = [](FsrWhy why) { + g_why = why; + return false; + }; + std::string where; + for (const std::string& dir : library_dirs()) { + where += (where.empty() ? "" : ", ") + dir; +#ifdef _WIN32 + const std::string path = dir + "\\" + kLoader; + if (GetFileAttributesA(path.c_str()) == INVALID_FILE_ATTRIBUTES) continue; + // (the upscaler DLL the loader picks lies beside it: the loader's folder first in the search) + l.lib = LoadLibraryExA(path.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); +#else + const std::string path = dir + "/" + kLoader; + if (access(path.c_str(), R_OK) != 0) continue; + l.lib = (HMODULE)dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (!l.lib) logf("vk: AMD FSR 4: %s not loaded: %s", path.c_str(), dlerror()); +#endif + if (l.lib) { + logf("vk: AMD FSR 4: %s", path.c_str()); + break; + } + } + if (!l.lib) { + logf("vk: AMD FSR 4: no %s in %s (the readme says where AMD's files come from)", kLoader, where.c_str()); + return no(FSR_NO_LIBRARY); + } + l.create = (PfnFfxCreateContext)symbol("ffxCreateContext"); + l.destroy = (PfnFfxDestroyContext)symbol("ffxDestroyContext"); + l.query = (PfnFfxQuery)symbol("ffxQuery"); + l.dispatch = (PfnFfxDispatch)symbol("ffxDispatch"); + if (!l.create || !l.destroy || !l.query || !l.dispatch) { + logf("vk: AMD FSR 4: %s lacks the FSR API's entry points", kLoader); + return no(FSR_NO_LIBRARY); + } + l.device_context = VkDeviceContext{dev.device, dev.physical, vkGetDeviceProcAddr}; + // (the library lists every upscaler this device can run: the newest 4.x is taken explicitly, so an FSR 3.1 provider never + // stands in silently) + ffxQueryDescGetVersions versions{}; + versions.header.type = FFX_API_QUERY_DESC_TYPE_GET_VERSIONS; + versions.createDescType = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE; + versions.device = &l.device_context; + uint64_t count = 0; + versions.outputCount = &count; + if (l.query(nullptr, &versions.header) != FFX_API_RETURN_OK || !count) { + logf("vk: AMD FSR 4: AMD's library lists no upscaler for this GPU"); + return no(FSR_NO_MODEL); + } + std::vector ids(count); + std::vector names(count); + versions.versionIds = ids.data(); + versions.versionNames = names.data(); + if (l.query(nullptr, &versions.header) != FFX_API_RETURN_OK) { + logf("vk: AMD FSR 4: the upscalers could not be listed"); + return no(FSR_NO_MODEL); + } + std::string listed; + unsigned best[3] = {0, 0, 0}; + for (uint64_t i = 0; i < count; i++) { + const char* name = names[i] ? names[i] : ""; + listed += (listed.empty() ? "" : ", ") + std::string(name); + unsigned v[3] = {0, 0, 0}; + sscanf(name, "%u.%u.%u", &v[0], &v[1], &v[2]); + // (the first of equal versions: a Vulkan FSR 4 build lists its Auto, which picks the GPU's model, before INT8 and FP8) + if (v[0] == 4 && (!l.version_id || v[1] > best[1] || (v[1] == best[1] && v[2] > best[2]))) { + std::memcpy(best, v, sizeof best); + l.version_id = ids[i]; + } + } + if (!l.version_id) { + logf("vk: AMD FSR 4: no FSR 4 for this GPU (AMD's library has: %s)", listed.c_str()); + return no(FSR_NO_MODEL); + } + logf("vk: AMD FSR 4: upscalers %s; using %u.%u.%u", listed.c_str(), best[0], best[1], best[2]); + l.ok = true; + g_why = FSR_OK; + return true; +} + +/// A context for outputs of w x h, any render size up to that (the game's dynamic resolution and the Render resolution row +/// change it every so often: one context takes them all) +std::shared_ptr make_context(uint32_t w, uint32_t h) { + Library& l = lib(); + auto c = std::make_shared(); + c->backend.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK; + c->backend.deviceContext = l.device_context; + c->version.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION; + c->version.versionId = l.version_id; + c->backend.header.pNext = &c->version.header; +#ifndef _WIN32 + // (linux) fsr4-vk looks for the SDK's older backend description, not ffxCreateBackendVKDesc: without it, it finds no device + // and creation returns FFX_API_RETURN_ERROR_PARAMETER. Both stay chained + c->legacy_backend.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK_DEPRECATED; + c->legacy_backend.vkDevice = l.device_context.vkDevice; + c->legacy_backend.vkPhysicalDevice = l.device_context.vkPhysicalDevice; + c->legacy_backend.vkDeviceProcAddr = l.device_context.vkDeviceProcAddr; + c->version.header.pNext = &c->legacy_backend.header; +#endif + c->upscale.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE; + c->upscale.header.pNext = &c->backend.header; + // (the chain's colour is tonemapped and square-root encoded already: LDR, perceptual, no auto exposure; depth not + // inverted. The render size changes under the one context) + c->upscale.flags = FFX_UPSCALE_ENABLE_NON_LINEAR_COLORSPACE | FFX_UPSCALE_ENABLE_DYNAMIC_RESOLUTION; + c->upscale.maxRenderSize = {w, h}; + c->upscale.maxUpscaleSize = {w, h}; + c->upscale.fpMessage = &on_message; + const auto t0 = std::chrono::steady_clock::now(); + const ffxReturnCode_t r = l.create(&c->context, &c->upscale.header, nullptr); + const double made_ms = std::chrono::duration(std::chrono::steady_clock::now() - t0).count(); + if (r != FFX_API_RETURN_OK || !c->context) { + logf("vk: AMD FSR 4: its upscaler was not made (%d) for %ux%u", (int)r, w, h); + c->context = nullptr; + return nullptr; + } + c->width = w, c->height = h; + FfxApiEffectMemoryUsage usage{}; + ffxQueryDescUpscaleGetGPUMemoryUsage mem{}; + mem.header.type = FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE; + mem.gpuMemoryUsageUpscaler = &usage; + if (l.query(&c->context, &mem.header) == FFX_API_RETURN_OK) c->memory = usage.totalUsageInBytes; + ffxQueryGetProviderVersion provider{}; + provider.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION; + const bool known = l.query(&c->context, &provider.header) == FFX_API_RETURN_OK && provider.versionName; + logf("vk: AMD FSR 4: FSR %s made for outputs of %ux%u (frame %llu, %.1f ms of CPU; its own %llu MB)", + known ? provider.versionName : "?", w, h, (unsigned long long)texture_frame_no(), made_ms, + (unsigned long long)(c->memory >> 20)); + return c; +} + +FfxApiResource resource(const TemporalImage& im, VkImageUsageFlags usage, uint32_t state) { + VkImageCreateInfo ci{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + ci.imageType = VK_IMAGE_TYPE_2D, ci.format = im.format, ci.extent = {im.width, im.height, 1}; + ci.mipLevels = 1, ci.arrayLayers = 1, ci.samples = VK_SAMPLE_COUNT_1_BIT, ci.tiling = VK_IMAGE_TILING_OPTIMAL; + ci.usage = usage, ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + return ffxApiGetResourceVK((void*)im.image, ffxApiGetImageResourceDescriptionVK(im.image, ci, 0), state); +} + +VkImageMemoryBarrier2 barrier(VkImage image, VkAccessFlags2 src, VkAccessFlags2 dst, VkImageLayout from, VkImageLayout to) { + VkImageMemoryBarrier2 b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2}; + b.srcStageMask = b.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + b.srcAccessMask = src, b.dstAccessMask = dst, b.oldLayout = from, b.newLayout = to; + b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b.image = image, b.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + return b; +} +void barriers(VkCommandBuffer cmd, const VkImageMemoryBarrier2* b, uint32_t n) { + VkDependencyInfo di{VK_STRUCTURE_TYPE_DEPENDENCY_INFO}; + di.imageMemoryBarrierCount = n, di.pImageMemoryBarriers = b; + vkCmdPipelineBarrier2(cmd, &di); +} + +#ifndef _WIN32 +/// (linux) a Vulkan FSR 4 build takes no frame without an exposure, and the chain's colour is tonemapped already: a 1x1 R32F of 1 +bool unit_exposure(VkCommandBuffer cmd, TemporalImage& out) { + static VkImage image = VK_NULL_HANDLE; + static VmaAllocation allocation{}; + const bool made = image != VK_NULL_HANDLE; + if (!made) { + VkImageCreateInfo ci{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + ci.imageType = VK_IMAGE_TYPE_2D, ci.format = VK_FORMAT_R32_SFLOAT, ci.extent = {1, 1, 1}, ci.mipLevels = 1, ci.arrayLayers = 1; + ci.samples = VK_SAMPLE_COUNT_1_BIT, ci.tiling = VK_IMAGE_TILING_OPTIMAL; + ci.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if (!create_image(ci, image, allocation)) return image = VK_NULL_HANDLE, false; + } + out = TemporalImage{image, VK_NULL_HANDLE, VK_FORMAT_R32_SFLOAT, 1, 1}; + if (!made) { + const VkImageMemoryBarrier2 to_clear = barrier(image, 0, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + barriers(cmd, &to_clear, 1); + const VkClearColorValue one{{1.0f, 0.0f, 0.0f, 0.0f}}; + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + vkCmdClearColorImage(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &one, 1, &range); + const VkImageMemoryBarrier2 to_read = barrier(image, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + barriers(cmd, &to_read, 1); + } + return true; +} +#endif + +// ---- the engine (vk_temporal.h TemporalEngine) ---- + +bool start() { return load(); } + +void release() { + State& s = st(); + if (!s.context) return; + std::shared_ptr old = std::move(s.context); + temporal_later([old] {}); // (destroyed with the last copy, a few frames on: recorded dispatches may still use it) + s.context.reset(); +} + +int prepare(VkCommandBuffer, uint32_t, uint32_t, uint32_t ow, uint32_t oh, uint32_t* in_w, uint32_t* in_h, const char** why) { + State& s = st(); + int made = 1; + if (!s.context || s.context->width != ow || s.context->height != oh) { + release(); + s.context = make_context(ow, oh); + if (!s.context) return *why = "its upscaler was not made", -1; + made = 2; + } + // (the inputs at the context's largest render size: the render size moves under it without new images) + *in_w = ow, *in_h = oh; + return made; +} + +bool evaluate(VkCommandBuffer cmd, const TemporalFrame& f) { + State& s = st(); + Library& l = lib(); +#ifndef _WIN32 + TemporalImage exposure{}; + if (!unit_exposure(cmd, exposure)) return false; +#endif + const auto now = std::chrono::steady_clock::now(); + const float frame_ms = s.evals ? std::chrono::duration(now - s.last).count() : 1000.0f / 30.0f; + s.last = now; + + constexpr VkImageLayout G = VK_IMAGE_LAYOUT_GENERAL, R = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + constexpr VkAccessFlags2 any = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; + const VkImageMemoryBarrier2 in[4] = {barrier(f.color.image, VK_ACCESS_2_MEMORY_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT, G, R), + barrier(f.depth.image, VK_ACCESS_2_MEMORY_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT, G, R), + barrier(f.motion.image, VK_ACCESS_2_MEMORY_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT, G, R), + barrier(f.output.image, any, any, G, G)}; + barriers(cmd, in, 4); + + ffxDispatchDescUpscale d{}; + d.header.type = FFX_API_DISPATCH_DESC_TYPE_UPSCALE; + d.commandList = cmd; + d.color = resource(f.color, VK_IMAGE_USAGE_SAMPLED_BIT, FFX_API_RESOURCE_STATE_COMPUTE_READ); + d.depth = resource(f.depth, VK_IMAGE_USAGE_SAMPLED_BIT, FFX_API_RESOURCE_STATE_COMPUTE_READ); + d.motionVectors = resource(f.motion, VK_IMAGE_USAGE_SAMPLED_BIT, FFX_API_RESOURCE_STATE_COMPUTE_READ); + d.output = resource(f.output, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); +#ifndef _WIN32 + d.exposure = resource(exposure, VK_IMAGE_USAGE_SAMPLED_BIT, FFX_API_RESOURCE_STATE_COMPUTE_READ); +#endif + d.jitterOffset = {f.jitter[0], f.jitter[1]}; // (render pixels, as the projection was offset: the same as DLSS's) + d.motionVectorScale = {1.0f, 1.0f}; // (render pixels already, towards the frame before) + d.renderSize = {f.render_w, f.render_h}; + d.upscaleSize = {f.output.width, f.output.height}; + d.enableSharpening = false; // (the Sharpness row sharpens at the window, after the HUD) + d.sharpness = 0.0f; + d.frameTimeDelta = frame_ms; + d.preExposure = 1.0f; + d.reset = f.reset; + d.cameraNear = f.near_z, d.cameraFar = f.far_z, d.cameraFovAngleVertical = f.fov_y; + d.viewSpaceToMetersFactor = 1.0f; + d.flags = FFX_UPSCALE_FLAG_NON_LINEAR_COLOR_SRGB; + const ffxReturnCode_t r = l.dispatch(&s.context->context, &d.header); + note_work(); // (AMD's dispatches: full_barrier must not take the next barrier for a repeat of the one before them) + + const VkImageMemoryBarrier2 out[4] = {barrier(f.color.image, VK_ACCESS_2_SHADER_READ_BIT, any, R, G), + barrier(f.depth.image, VK_ACCESS_2_SHADER_READ_BIT, any, R, G), + barrier(f.motion.image, VK_ACCESS_2_SHADER_READ_BIT, any, R, G), + barrier(f.output.image, any, any, G, G)}; + barriers(cmd, out, 4); + if (r != FFX_API_RETURN_OK) { + logf("vk: AMD FSR 4: its dispatch failed (%d)", (int)r); + return false; + } + // (the first evaluation, and the first resets: a camera cut each) + if (s.evals++ == 0 || (f.reset && s.told_resets < 40)) + s.told_resets++, logf("vk: AMD FSR 4: %ux%u -> %ux%u, jitter %+.3f %+.3f px%s", f.render_w, f.render_h, f.output.width, + f.output.height, f.jitter[0], f.jitter[1], f.reset ? " (history reset)" : ""); + return true; +} + +uint64_t memory() { + State& s = st(); + return s.context ? s.context->memory : 0; +} + +const TemporalEngine kEngine{"AMD FSR 4", "FSR", start, prepare, evaluate, release, memory}; + +} // namespace + +bool fsr4_built() { return true; } +const TemporalEngine* fsr4_engine() { return &kEngine; } + +void fsr4_probe() { + if (fsr4_why() != FSR_OK || g_why.load(std::memory_order_relaxed) >= 0 || !dev.device) return; + load(); +} + +bool fsr4_wants_descriptor_buffer() { +#ifdef _WIN32 + return false; +#else + if (fsr4_why() == FSR_ENV_OFF) return false; + for (const std::string& dir : library_dirs()) + if (access((dir + "/" + kLoader).c_str(), R_OK) == 0) return true; + return false; +#endif +} + +#else // (no HOOKSHOT_FSR in this build: FSR is not there) + +bool fsr4_built() { return false; } +const TemporalEngine* fsr4_engine() { return nullptr; } +void fsr4_probe() {} +bool fsr4_wants_descriptor_buffer() { return false; } + +#endif + +} // namespace mkwvk diff --git a/gpu/vk/vk_fsr4.h b/gpu/vk/vk_fsr4.h new file mode 100644 index 000000000..6e10a95c7 --- /dev/null +++ b/gpu/vk/vk_fsr4.h @@ -0,0 +1,25 @@ +// AMD FSR 4, one of the temporal upscaler's engines (vk_temporal.h; vk_fsr4.cpp), through the AMD FSR API. Only the API's MIT +// headers are built in (third_party/fidelityfx), and only in a build with HOOKSHOT_FSR (make_game.py --fsr, which also puts +// the camera jitter into the game: nx2/oot_jitter.py). AMD's signed amd_fidelityfx_loader_vk.dll and +// amd_fidelityfx_upscaler_vk.dll are loaded from the user's PC at run time; none ship with Hookshot. Linux: a Vulkan FSR 4 +// library (such as fsr4-vk's amd_fidelityfx_upscaler_vk.so) in their place. +#pragma once + +namespace mkwvk { + +struct TemporalEngine; + +/// Whether this build has FSR in it (HOOKSHOT_FSR) +bool fsr4_built(); +/// Why FSR 4 cannot be chosen here (the Upscaler row says it), FSR_OK when it can or is not known yet +enum FsrWhy { FSR_OK, FSR_NOT_BUILT, FSR_ENV_OFF, FSR_NO_LIBRARY, FSR_NO_MODEL, FSR_WHY_COUNT }; +FsrWhy fsr4_why(); +/// Loads AMD's library once and asks it for an FSR 4 upscaler for this device, so fsr4_why knows before the first upscale +/// (the menu, when it opens). Nothing in a build without FSR, or before the device is made +void fsr4_probe(); +/// (linux) a Vulkan FSR 4 library is there and records with descriptor buffers: the device has to enable them +bool fsr4_wants_descriptor_buffer(); +/// FSR 4 as the temporal upscaler's engine (FSR 4 and its native AA on the Upscaler row); nullptr in a build without it +const TemporalEngine* fsr4_engine(); + +} // namespace mkwvk diff --git a/gpu/vk/vk_main.cpp b/gpu/vk/vk_main.cpp index afb68c526..849b45cbe 100644 --- a/gpu/vk/vk_main.cpp +++ b/gpu/vk/vk_main.cpp @@ -21,6 +21,7 @@ #include "gpu_choice.h" #include "vk_menu.h" #include "vk_dlss.h" +#include "vk_fsr4.h" #include "vk_reflex.h" #include "vk_progress.h" @@ -250,6 +251,8 @@ bool create_device() { optional(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, dev.device_fault); optional(VK_KHR_COOPERATIVE_MATRIX_EXTENSION_NAME, dev.coop_matrix); dlss_device_extensions(want, exts); // (hookshot: NVIDIA DLSS's, vk_dlss.cpp) + bool fsr_descriptor_buffer = false; // (hookshot, linux: only with a Vulkan FSR 4 library beside the game, vk_fsr4.cpp) + if (fsr4_wants_descriptor_buffer()) optional(VK_EXT_DESCRIPTOR_BUFFER_EXTENSION_NAME, fsr_descriptor_buffer); reflex_device_extensions(want, exts); // (hookshot: NVIDIA Reflex's, vk_reflex.cpp) // the queue: one family for graphics, compute and transfer @@ -330,8 +333,11 @@ bool create_device() { if (dev.view_2d_of_3d) *tail = &v2d3d, tail = &v2d3d.pNext; VkPhysicalDeviceCooperativeMatrixFeaturesKHR coop{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COOPERATIVE_MATRIX_FEATURES_KHR}; if (dev.coop_matrix) *tail = &coop, tail = &coop.pNext; + VkPhysicalDeviceDescriptorBufferFeaturesEXT descriptor_buffer{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_BUFFER_FEATURES_EXT}; + if (fsr_descriptor_buffer) *tail = &descriptor_buffer, tail = &descriptor_buffer.pNext; reflex_device_features(tail); // (present ids, when NVIDIA Reflex's extensions are there) vkGetPhysicalDeviceFeatures2(dev.physical, &f); + descriptor_buffer.descriptorBufferCaptureReplay = VK_FALSE; dev.view_2d_of_3d = dev.view_2d_of_3d && v2d3d.sampler2DViewOf3D; coop.cooperativeMatrixRobustBufferAccess = VK_FALSE; dev.coop_matrix = dev.coop_matrix && coop.cooperativeMatrix && diff --git a/gpu/vk/vk_menu.cpp b/gpu/vk/vk_menu.cpp index ce18bec6b..3e8e6edfb 100644 --- a/gpu/vk/vk_menu.cpp +++ b/gpu/vk/vk_menu.cpp @@ -31,6 +31,8 @@ #include "vk_startup_paint.h" #include "vk_work.h" // (after the others: counts the commands that do GPU work, for full_barrier) #include "vk_dlss.h" +#include "vk_fsr4.h" +#include "vk_temporal.h" #include "vk_reflex.h" #include "upscaler_rows.h" #include "menu_restart.h" @@ -121,24 +123,30 @@ const char* hn_reflex(int v, int variant) { if (variant < 0) return reflex_available() ? t[std::clamp(v, 0, 2)] : none; return variant == 0 ? t[std::clamp(v, 0, 2)] : variant == 1 ? none : nullptr; } -// The Upscaler (opts.dlss): what scales the 3D up to the output. DLSS and DLAA only where dlss_why() lets them run; the menu -// shows (and steps from) what runs, dlss_mode(), and keeps the saved choice for a PC where it can +// The Upscaler (opts.upscaler): what scales the 3D up to the output. DLSS and DLAA only where dlss_why() lets them run, FSR 4 +// and its native AA only where fsr4_why() does; the menu shows (and steps from) what runs, temporal_mode(), and keeps the +// saved choice for a PC where it can namespace ur = upscaler_rows; -int upscaler() { return dlss_mode(); } +int upscaler() { return temporal_mode(); } +bool dlss_ok() { return dlss_why() == DLSS_OK; } +bool fsr_ok() { return fsr4_why() == FSR_OK; } const char* tx_upscaler(int) { - static const char* t[] = {"Game's own", "NVIDIA DLSS", "NVIDIA DLAA"}; - return t[std::clamp(upscaler(), 0, 2)]; + static const char* t[ur::kUpscalers] = {"Game's own", "NVIDIA DLSS", "NVIDIA DLAA", "AMD FSR 4", "AMD FSR 4 Native AA"}; + return t[std::clamp(upscaler(), 0, ur::kUpscalers - 1)]; } -/// The Upscaler: the one shown (what runs, upscaler()), and with the game's own, why DLSS and DLAA are not offered +/// The Upscaler: the one shown (what runs, upscaler()), and with the game's own, why DLSS and FSR are not offered. Variants +/// (every one): each value, then the game's own with each of DLSS's reasons, then with each of FSR's const char* hn_upscaler(int v, int variant) { - static const char* const t[] = { + static const char* const t[ur::kUpscalers] = { "The game's own network.", "NVIDIA's, from a smaller 3D. Softer than the game's own: set Sharpness to around 80%. Things that move by " "themselves can trail for now.", "NVIDIA's, the 3D at full size with its edges smoothed; the slowest. Softer than the game's own: set Sharpness to " "around 80%. Things that move by themselves can trail for now.", + "AMD's, from a smaller 3D. Things that move by themselves can trail for now.", + "AMD's, the 3D at full size with its edges smoothed; the slowest.", }; - static const char* const why[DLSS_WHY_COUNT] = { + static const char* const dlss_no[DLSS_WHY_COUNT] = { nullptr, "DLSS and DLAA: not in this build (built without NVIDIA's DLSS SDK, see the readme).", "DLSS and DLAA: off for this run (RX_VK_DLSS=0).", @@ -147,17 +155,32 @@ const char* hn_upscaler(int v, int variant) { "DLSS and DLAA: DLSS's runtime was not found (the game folder's ngx folder, see the readme).", "DLSS and DLAA: NVIDIA's NGX on Linux loads DLSS's runtime only from a folder named in ASCII (see the readme).", }; - // (now: the value shown and what runs here; every one: each value, and the game's own with each reason) - int w = 0; - if (variant < 0) v = std::clamp(upscaler(), 0, 2), w = (int)dlss_why(); - else if (v == ur::GAME) w = variant; - else if (variant > 0) return nullptr; - v = std::clamp(v, 0, 2); - if (w >= DLSS_WHY_COUNT) return nullptr; - if (v != ur::GAME || !why[w]) return t[v]; - static char b[DLSS_WHY_COUNT][256]; - snprintf(b[w], sizeof b[w], "%s %s", t[0], why[w]); - return b[w]; + static const char* const fsr_no[FSR_WHY_COUNT] = { + nullptr, + "AMD FSR 4: not in this build (see the readme).", + "AMD FSR 4: off for this run (RX_VK_FSR=0).", + "AMD FSR 4: AMD's files were not found beside the game (see the readme).", + "AMD FSR 4: AMD's files have no FSR 4 for this GPU.", + }; + // (now: the value shown and what runs here, with both engines' reasons; every one: as said above) + int d = 0, f = 0; + if (variant < 0) { + v = std::clamp(upscaler(), 0, ur::kUpscalers - 1), d = (int)dlss_why(), f = (int)fsr4_why(); + } else if (v == ur::GAME) { + if (variant < DLSS_WHY_COUNT) d = variant; + else if (variant < DLSS_WHY_COUNT + FSR_WHY_COUNT - 1) f = variant - DLSS_WHY_COUNT + 1; + else return nullptr; + } else if (variant > 0) { + return nullptr; + } + v = std::clamp(v, 0, ur::kUpscalers - 1); + // (the reasons are said only where the engine is in the build: one without FSR does not tell an NVIDIA user why) + const char* dn = dlss_built() || variant >= 0 ? dlss_no[d] : nullptr; + const char* fn = fsr4_built() || variant >= 0 ? fsr_no[f] : nullptr; + if (v != ur::GAME || (!dn && !fn)) return t[v]; + static char b[DLSS_WHY_COUNT][FSR_WHY_COUNT][320]; + snprintf(b[d][f], sizeof b[d][f], "%s%s%s%s%s", t[0], dn ? " " : "", dn ? dn : "", fn ? " " : "", fn ? fn : ""); + return b[d][f]; } /// The DLSS model row (opts.dlss_model): NVIDIA's network by its preset, or RX_VK_DLSS_PRESET's for this run const char* dlss_preset_env() { @@ -228,8 +251,9 @@ const char* tx_resolution(int v) { static char b[48]; const int up = upscaler(); if (up == ur::DLAA) return "Native (DLAA)"; + if (up == ur::FSR_AA) return "Native (FSR)"; if (!ur::percent(v)) return "Auto (dynamic)"; - if (const char* mode = up == ur::DLSS ? ur::dlss_mode_name(v) : nullptr) { + if (const char* mode = ur::scaled(up) ? ur::mode_name(v) : nullptr) { snprintf(b, sizeof b, "%s (%d%%)", mode, ur::percent(v)); return b; } @@ -240,12 +264,25 @@ const char* tx_resolution(int v) { } /// The Render resolution: the value chosen, for the Upscaler shown (every one: each value with each Upscaler) const char* hn_resolution(int v, int variant) { - const int up = variant < 0 ? std::clamp(upscaler(), 0, 2) : variant; - if (up > 2) return nullptr; + const int up = variant < 0 ? std::clamp(upscaler(), 0, ur::kUpscalers - 1) : variant; + if (up >= ur::kUpscalers) return nullptr; if (up == ur::DLAA) return "DLAA draws the 3D at full size: nothing to set here. Choose DLSS or the game's own upscaler to draw it " "smaller."; + if (up == ur::FSR_AA) + return "FSR's native AA draws the 3D at full size: nothing to set here. Choose FSR 4 or the game's own upscaler " + "to draw it smaller."; const int p = ur::percent(v); + if (up == ur::FSR) { + switch (p) { + case 0: return "The game lowers it when the GPU is busy, and FSR follows."; + case 67: return "FSR's sharpest mode."; + case 58: return "Between Quality and Performance."; + case 50: return "Faster, and softer."; + case 33: return "FSR's fastest mode, and its softest."; + default: return nullptr; // (not one of FSR's steps: the row moves to one) + } + } if (up == ur::DLSS) { switch (p) { case 0: return "The game lowers it when the GPU is busy, and DLSS follows. Set Sharpness to around 80%."; @@ -262,7 +299,7 @@ const char* hn_resolution(int v, int variant) { } /// A row with nothing else to choose here: shown without its arrows, Left and Right leave it (and the saved value) alone bool row_fixed(const int* value) { - return (value == &opts.reflex && !reflex_available()) || (value == &opts.dlss && ur::upscaler_fixed(dlss_why() == DLSS_OK)) || + return (value == &opts.reflex && !reflex_available()) || (value == &opts.upscaler && ur::upscaler_fixed(dlss_ok(), fsr_ok())) || (value == &opts.resolution && ur::resolution_fixed(upscaler())) || (value == &opts.upscale && !filter_needed()) || (value == &opts.dlss_model && ur::model_fixed(upscaler(), dlss_preset_env() != nullptr)); } @@ -472,7 +509,7 @@ Row rows[] = { {TAB_GRAPHICS, "Output resolution", &opts.output_res, 0, kLastOutput, 1, tx_output_res, "The game's whole frame, HUD included, before it is scaled to the window. Docked only. Applies after a restart.", hn_output_res, true}, - {TAB_GRAPHICS, "Upscaler", &opts.dlss, 0, 2, 1, tx_upscaler, "What scales the 3D up to the Output resolution.", + {TAB_GRAPHICS, "Upscaler", &opts.upscaler, 0, ur::kUpscalers - 1, 1, tx_upscaler, "What scales the 3D up to the Output resolution.", hn_upscaler}, {TAB_GRAPHICS, "DLSS model", &opts.dlss_model, 0, ur::kModels - 1, 1, tx_dlss_model, "Which of NVIDIA's DLSS networks draws the picture.", hn_dlss_model, false, true}, @@ -694,9 +731,10 @@ std::string restart_line(HDC dc, int width) { return line; } -/// A row on its tab: the Cheats one only with cheats on, the Upscaler only in a build with DLSS +/// A row on its tab: the Cheats one only with cheats on, the Upscaler only in a build with DLSS or FSR bool row_shown(int i) { - return (i != kCheatsRow || cheats_enabled) && (rows[i].value != &opts.dlss || ur::upscaler_shown(dlss_built())) && + return (i != kCheatsRow || cheats_enabled) && + (rows[i].value != &opts.upscaler || ur::upscaler_shown(dlss_built() || fsr4_built())) && (rows[i].value != &opts.dlss_model || ur::model_shown(dlss_built())); } int tab_row_count(int t) { @@ -756,7 +794,7 @@ const char* ini_key(const Row& r) { // (rows renamed since their values were first saved keep their keys: an options file from before still loads) if (r.value == &opts.resolution) return "resolution"; if (r.value == &opts.upscale) return "upscaling"; - if (r.value == &opts.dlss) return "dlss"; // (as opts.dlss and RX_VK_DLSS) + if (r.value == &opts.upscaler) return "dlss"; // (its name from before FSR, as RX_VK_DLSS) static char k[32]; size_t n = 0; for (const char* p = r.name; *p && n + 1 < sizeof k; p++) k[n++] = *p == ' ' ? '_' : (char)tolower(*p); @@ -879,7 +917,7 @@ void set_row(Row& r, int v, int dir, bool keep = true) { // (nothing else to choose: no change, no save. A saved choice that cannot run here is kept) if (row_fixed(r.value)) return; // (the scaling rows: only what can run here; the Render resolution row through the Upscaler's own steps) - if (r.value == &opts.dlss) v = ur::step_upscaler(upscaler(), dir, true); + if (r.value == &opts.upscaler) v = ur::step_upscaler(upscaler(), dir, dlss_ok(), fsr_ok()); if (r.value == &opts.resolution) v = ur::step(upscaler(), *r.value, dir); if (r.value == &opts.output_res) { // (smallest to largest: kOutputOrder) v = step_output_choice(*r.value, dir); @@ -897,7 +935,7 @@ void set_row(Row& r, int v, int dir, bool keep = true) { } else { *r.value = v > r.hi ? r.lo : v < r.lo ? r.hi : v; // (another Upscaler: the Render resolution moves to the nearest of its steps, DLSS's modes or the game's) - if (r.value == &opts.dlss) opts.resolution = ur::snap(upscaler(), opts.resolution); + if (r.value == &opts.upscaler) opts.resolution = ur::snap(upscaler(), opts.resolution); if (!r.preset_exempt) { // (the controls, the window and the counter are not the preset's) opts.preset = 0; // Custom sync_preset_budgets(0); @@ -1918,7 +1956,7 @@ void menu_tick() { // (the layout row shows the controls as they are: host/nn_hid.c keeps them, the ini's pad_ lines, and a saved // layout is not applied over them at start) if (open_now.load()) opts.pad_layout = detect_pad_layout(); - if (open_now.load()) dlss_probe(); // (whether the Upscaler row can offer DLSS here: NGX asked once) + if (open_now.load()) dlss_probe(), fsr4_probe(); // (whether the Upscaler row can offer DLSS and FSR here: asked once) if (open_now.load()) refresh_mics(); // (the microphones as they are now) } if (open_now.load() && page == 5) { // (hookshot) the restart question: Later (the default) or Restart now diff --git a/gpu/vk/vk_menu.h b/gpu/vk/vk_menu.h index f3cba6805..ce5533659 100644 --- a/gpu/vk/vk_menu.h +++ b/gpu/vk/vk_menu.h @@ -14,8 +14,9 @@ struct Options { int output_res = 0; // the game's whole frame, docked: 0 its 2048x1152, 1 2560x1440, 2 3840x2160, 3 1920x1080; read once, at the start (host/nn_applet.c) int upscale = 0; // to the window (Window scaling filter, saved as upscaling): 0 off (the plain copy), 1 Catmull-Rom, 2 Lanczos int reflex = 0; // NVIDIA Reflex: 0 off, 1 on, 2 on + boost (vk_reflex.cpp; NVIDIA GPUs with VK_NV_low_latency2) - int dlss = 0; // the Upscaler: 0 the game's own network, 1 NVIDIA DLSS, 2 NVIDIA DLAA (vk_dlss.cpp; a build with NVIDIA's - // SDK only). Saved as dlss + int upscaler = 0; // the Upscaler: 0 the game's own network, 1 NVIDIA DLSS, 2 NVIDIA DLAA (a build with NVIDIA's SDK only), + // 3 AMD FSR 4, 4 its native AA (a build with FSR only). upscaler_rows.h; vk_temporal.cpp. Saved as dlss + // (its name from before FSR: an options file from then still loads) int dlss_model = 0; // DLSS's network, by its preset: 0 K (transformer), 1 M, 2 L (second-generation transformer), 3 NVIDIA's // default (upscaler_rows.h model_preset; vk_dlss.cpp). RX_VK_DLSS_PRESET over it int sharpness = 0; // 0 to 100 diff --git a/gpu/vk/vk_replay.cpp b/gpu/vk/vk_replay.cpp index 89edac9ea..60aa2ca52 100644 --- a/gpu/vk/vk_replay.cpp +++ b/gpu/vk/vk_replay.cpp @@ -26,7 +26,7 @@ #include #include -#include "vk_dlss.h" +#include "vk_temporal.h" #include "status_line.h" #include "vk_reflex.h" #include "vk_draw.h" @@ -2619,7 +2619,7 @@ void status_tick() { } char dlss[320] = ""; const bool line = frames % sl::kEvery == 0; - const int mode = line ? dlss_status(dlss, sizeof dlss) : dlss_mode(); // (dlss_status: since the last line, so only for one) + const int mode = line ? temporal_status(dlss, sizeof dlss) : temporal_mode(); // (temporal_status: since the last line, so only for one) static bool over = false; if (known && sl::crossed(over, used, allowed)) { char w[200]; @@ -2637,7 +2637,7 @@ uint64_t present_value = 0; // (the present job's value, handed out as it came void do_present(const nvn_tex* t, const int crop[4]) { // (hookshot) NVIDIA Reflex (vk_reflex.cpp): this frame's simulation ended, its submission starts; on the way out (after the // present) the driver's sleep until the next frame should begin - dlss_idle(); // (NVIDIA DLSS: its video memory let go once it has not been used for a while) + temporal_idle(); // (DLSS, FSR: their video memory let go once not used for a while) status_tick(); // (the status line, and the video-memory warning) reflex_present_begins(); struct ReflexSleep { ~ReflexSleep() { reflex_sleep(); } } reflex_sleep_after; @@ -3798,10 +3798,10 @@ void fill_replay_table(MkwVkRenderer& t) { if (id == HOS_OPT_CHEAT_FLIGHT) return menu_player_flight(); if (id == HOS_OPT_CHEAT_ACTION) return menu_take_cheat_action(); if (id == HOS_OPT_CHEAT_CAMERA) return menu_free_camera(); - return id == 0 ? opts.realtime : id == 1 ? menu_is_open() : id == 2 ? resolution_auto() && !dlss_full_scale() : id == 3 ? (dlss_full_scale() ? 10000 : resolution_scale()) : id == 4 ? opts.pad_background : id == 5 ? opts.text_speed : id == 6 ? opts.mouse_native : + return id == 0 ? opts.realtime : id == 1 ? menu_is_open() : id == 2 ? resolution_auto() && !temporal_full_scale() : id == 3 ? (temporal_full_scale() ? 10000 : resolution_scale()) : id == 4 ? opts.pad_background : id == 5 ? opts.text_speed : id == 6 ? opts.mouse_native : id == 7 ? opts.mouse_sens : id == 8 ? opts.mouse_invert : id == 9 ? opts.rumble : id == 16 ? opts.invert_cam_x : id == 17 ? opts.invert_cam_y : id == HOS_OPT_GYRO ? opts.gyro : id == HOS_OPT_GYRO_SENS_X ? opts.gyro_sens_x : id == HOS_OPT_GYRO_SENS_Y ? opts.gyro_sens_y : id == HOS_OPT_GYRO_INV_X ? opts.gyro_inv_x : id == HOS_OPT_GYRO_INV_Y ? opts.gyro_inv_y : - id == HOS_OPT_DLSS_JITTER_W || id == HOS_OPT_DLSS_JITTER_H ? (int)dlss_jitter_size(id - HOS_OPT_DLSS_JITTER_W) : - id == HOS_OPT_DLSS_JITTER_PHASES ? (int)dlss_jitter_size(2) : 0; + id == HOS_OPT_DLSS_JITTER_W || id == HOS_OPT_DLSS_JITTER_H ? (int)temporal_jitter_size(id - HOS_OPT_DLSS_JITTER_W) : + id == HOS_OPT_DLSS_JITTER_PHASES ? (int)temporal_jitter_size(2) : 0; }; t.option_set = menu_option_set; t.nvn_register_sampler = register_sampler_queued; diff --git a/gpu/vk/vk_temporal.cpp b/gpu/vk/vk_temporal.cpp new file mode 100644 index 000000000..22ab4b779 --- /dev/null +++ b/gpu/vk/vk_temporal.cpp @@ -0,0 +1,926 @@ +// (hookshot) A temporal upscaler in place of OoT's gameplay upscale network: the frame chain (vk_temporal.h), shared by the +// engines that draw with it, NVIDIA DLSS (vk_dlss.cpp) and AMD FSR 4 (vk_fsr4.cpp). +// The chain is hckrman101's, from their NVIDIA DLSS (#841): the camera, the inputs pass, the actor motion and the jitter's +// state, moved here from vk_dlss.cpp unchanged in what they do, so that DLSS and FSR share them. +// +// OoT has no temporal upscaler of its own: its scene is drawn at a lower size and a network of its own (the program +// fd029a317a9a4b94 at 3/4, another for each other share; one dispatch, cooperative matrices) scales it up, from the +// square root of the scene's tonemapped colour, into the frame's B10G11R11 picture (before the HUD). Here that dispatch is replaced: +// - the scene's camera is taken from the draws into the scene's colour target: their vertex stage's uniform buffer 3 +// holds the target's size at 0x120, this frame's view-projection at 0x30 (row major, clip = M * position; the projection's w row ends -1, 0 at 0xa8). The largest such target in a frame is +// the scene (reflections and half-size passes have the same layout). The scene's depth is that draw's depth target. +// - shaders/dlss_prep.comp makes the engine's inputs at the render size: the colour as the network samples it (LDR), the +// depth, and motion vectors from the depth reprojected through the two cameras. The game has no motion vectors of its +// own (no velocity target: the scene pass draws into one B10G11R11 target, and its upscale network samples the colour alone), +// so that is the camera's motion, and what moves by itself would ghost. The scene camera's sub-pixel jitter +// (nx2/oot_jitter.py, while an engine draws) is read back from its projection, given to the engine and taken off the motion. +// - actor motion (actor_motion, shaders/dlss_actor.vert): the skinned draws into the scene (vk_draw.cpp's skin_check: +// Link, the other characters, the animals, what the game animates with bones) are drawn again over that motion, each +// vertex skinned with this frame's bones and the frame before's, so their pixels get their own motion. Their poses are +// copied on the GPU from where the game has them into two buffers that take turns; a pixel is the draw's where its depth is +// the scene's (within RX_VK_DLSS_ACTOR_TOL steps, default 64). What moves without bones (particles, water, foliage moved in +// its vertex shader, mesh-shader draws) keeps the camera's motion. RX_VK_DLSS_ACTORS=0: camera motion alone. +// - the engine scales them to the output's size; shaders/dlss_out.comp squares that back to linear colour into the +// network's output. +// Anything unexpected (no camera this frame, other sizes, the engine not there or failing) leaves the network to run, the +// reason in the log when it changes. +// +// The Insert menu's Upscaler row (opts.upscaler, upscaler_rows.h) chooses the engine; RX_VK_DLSS=1 / 2 (DLSS, DLAA) or +// RX_VK_FSR=1 / 2 (FSR 4, its native anti-aliasing) over it, and =0 takes that engine out for the run. +// RX_VK_DLSS_DEBUG=motion: the motion vectors shown instead of the picture (|x|, |y| over 16 px as red and green, the actors' +// in it); =alternate: every other frame the game's own upscale (a test's reference beside the engine's). (The names are +// DLSS's from before FSR came: they apply to either engine.) +#include "vk_temporal.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dlss_actor.h" +#include "dlss_checks.h" +#include "dlss_math.h" +#include "upscaler_rows.h" +#include "vk_dlss.h" +#include "vk_draw.h" +#include "vk_fsr4.h" +#include "vk_menu.h" +#include "vk_texture.h" + +#include "shaders/dlss_actor_frag_spv.h" +#include "shaders/dlss_actor_vert_spv.h" +#include "shaders/dlss_out_comp_spv.h" +#include "shaders/dlss_prep_comp_spv.h" +#include "vk_work.h" // (after the others: counts the commands that do GPU work, for full_barrier) + +namespace mkwvk { + +namespace ur = upscaler_rows; + +namespace { +int env_switch(const char* name) { // (-1: unset) + const char* e = getenv(name); + return e && *e ? atoi(e) : -1; +} +} // namespace + +int temporal_mode() { + static const int env_dlss = env_switch("RX_VK_DLSS"), env_fsr = env_switch("RX_VK_FSR"); + int v = opts.upscaler; + if (env_dlss > 0) v = env_dlss == 2 ? ur::DLAA : ur::DLSS; + else if (env_fsr > 0) v = env_fsr == 2 ? ur::FSR_AA : ur::FSR; + if (v < 0 || v >= ur::kUpscalers) return ur::GAME; + if (ur::is_dlss(v) && dlss_why() != DLSS_OK) return ur::GAME; + if (ur::is_fsr(v) && fsr4_why() != FSR_OK) return ur::GAME; + return v; +} +bool temporal_wanted() { return temporal_mode() != ur::GAME; } +bool temporal_full_scale() { return ur::full_scale(temporal_mode()); } + +namespace { + +struct Image { + VkImage image = VK_NULL_HANDLE; + VmaAllocation memory = nullptr; + VkImageView view = VK_NULL_HANDLE; + VkFormat format = VK_FORMAT_UNDEFINED; + uint32_t width = 0, height = 0; + TemporalImage shown() const { return {image, view, format, width, height}; } +}; + +struct Camera { + float current[16]{}; // (its view-projection) + float jitter[2]{}; // (the projection's offset of the picture's centre, in clip space: nx2/oot_jitter.py) + float near_z = 0, far_z = 0, fov_y = 0; // (its projection's, 0 when it is not a plain perspective one) + uint32_t width = 0, height = 0; + Texture* depth = nullptr; + uint64_t depth_gen = 0; // (texture_destroy_generation when taken: a depth texture destroyed since is not used) + uint64_t frame = 0; // (texture_frame_no when taken) + bool valid = false; +}; + +/// An engine as the chain keeps it: started once, failed for good once +struct Engine { + const TemporalEngine* e = nullptr; + bool started = false, ok = false; + std::atomic failed{false}; // (atomic: temporal_jitter_size reads it on the game's thread, without the lock) +}; + +struct State { + std::mutex mu; + Engine engines[2]; // (DLSS, FSR: engine_of) + const TemporalEngine* drawing = nullptr; // (the engine that drew last: another one starts a new history) + Image color, depth, motion, output; + VkDescriptorSetLayout prep_set = VK_NULL_HANDLE, out_set = VK_NULL_HANDLE; + VkPipelineLayout prep_layout = VK_NULL_HANDLE, out_layout = VK_NULL_HANDLE; + VkPipeline prep = VK_NULL_HANDLE, out = VK_NULL_HANDLE; + VkSampler point = VK_NULL_HANDLE, linear = VK_NULL_HANDLE; + Camera cam; // (this frame's, since the last upscale) + // (the gameplay upscale's output, as last seen: the scene sizes the upscaler can take follow from it, + // temporal_scene_size. Read on the game's draws without the lock) + std::atomic out_w{0}, out_h{0}; + std::atomic last_frame{0}; // (the frame an engine last drew: read on the game's thread too) + float last_jitter[2]{}; // (its camera's jitter: the frame before's, for the next one) + float last_vp[16]{}; // (its camera's view-projection: the frame before's) + std::atomic jitter_w{0}, jitter_h{0}; // (the scene's size while an engine draws: the host's jitter's scale) + std::atomic jitter_phases{8}; // (its sequence's length, for the engine's scale: dlss_jitter_phases) + std::unordered_map depth_views; + uint64_t depth_views_gen = 0; + const char* reason = nullptr; + int reason_repeats = 0; + const TemporalEngine* told_first = nullptr; + bool told_camera = false; + // (actor motion: the skinned draws into the scene since the last upscale; the poses the engine last drew, by draw: the + // frame before's, where they were copied to in one of the two pose buffers (each frame's poses go to the other one)) + struct Skin { + SkinnedDraw k; + uint64_t key = 0; + uint32_t nbones = 0; + }; + std::vector skins; + struct Pose { + uint32_t at = 0, nbones = 0; // (bytes into its buffer: the origin, then the bones from at + 16) + uint64_t frame = 0; + }; + std::unordered_map poses; + std::unordered_map model_bones; // (by model: the bones its vertices use, 0 when unreadable) + struct PoseBuffer { + VkBuffer buffer = VK_NULL_HANDLE; + VmaAllocation memory = nullptr; + uint64_t address = 0; + } pose_buffers[2]; + int pose_at = 0; // (this frame's) + VkPipelineLayout actor_layout = VK_NULL_HANDLE; + VkPipeline actor = VK_NULL_HANDLE; + bool actor_tried = false; + struct Upload { + VkBuffer buffer = VK_NULL_HANDLE; + VmaAllocation memory = nullptr; + void* map = nullptr; + } uploads[6]; // (the cameras: a ring, one in use stays so for a few frames, as what later() frees) + int upload_at = 0; + uint64_t actor_draws = 0, actor_frames = 0, actor_skipped = 0; + int64_t actor_ns = 0; // (the CPU's time in it, since the last status line) + uint64_t status_draws = 0, status_frames = 0; // (actor_draws and _frames at the last status line: temporal_status) + uint64_t status_drawn = 0, status_passed = 0; // (since the last status line: frames an engine drew, and frames a network + // of the upscale's shape ran without it (its scene not this frame's)) + uint64_t engines_made = 0; // (prepare's new histories: a feature or context made again, each a stall in play) + float status_jitter[2]{}; // (the last evaluation's jitter, in render pixels, and whether it had motion) + bool status_motion = false; + struct Dead { uint64_t frame; std::function free; }; + std::vector dead; // (what work in flight may still use: freed a few frames on) + uint64_t asked = 0; // (the frame an upscale last asked for an engine: temporal_idle) + std::atomic holding{false}; // (an engine's state, images or frees still to do: temporal_idle has something to let go) +}; +State& st() { + static State s; + return s; +} + +/// The gameplay upscale's output size, as it is seen: the scene sizes the upscaler can take (temporal_scene_size). Until the +/// first one, none: that frame keeps the game's upscale +void note_output(const Texture* out, uint32_t level) { + State& s = st(); + s.out_w.store(std::max(1u, out->width >> level), std::memory_order_relaxed); + s.out_h.store(std::max(1u, out->height >> level), std::memory_order_relaxed); +} + +/// The chain's slot for the engine of an Upscaler value (DLSS and DLAA: DLSS's; FSR 4 and its native AA: FSR's) +Engine& engine_of(int mode) { + State& s = st(); + Engine& k = s.engines[ur::is_fsr(mode) ? 1 : 0]; + if (!k.e) k.e = ur::is_fsr(mode) ? fsr4_engine() : dlss_engine(); + return k; +} +const char* name_of(int mode) { + switch (mode) { + case ur::DLSS: return "NVIDIA DLSS"; + case ur::DLAA: return "NVIDIA DLAA"; + case ur::FSR: return "AMD FSR 4"; + case ur::FSR_AA: return "AMD FSR 4 Native AA"; + default: return "the game's own"; + } +} + +bool fail(const char* why) { // (the network runs: why, when it changes and every 600th time) + State& s = st(); + if (why != s.reason || ++s.reason_repeats % 600 == 0) + logf("vk: %s: the game's upscale runs (%s)", name_of(temporal_mode()), why); + if (why != s.reason) s.reason = why, s.reason_repeats = 0; + return false; +} + +void later(std::function f) { st().dead.push_back({texture_frame_no(), std::move(f)}); } +void free_dead(bool all) { + State& s = st(); + const uint64_t now = texture_frame_no(); + for (size_t i = 0; i < s.dead.size();) { + if (all || now - s.dead[i].frame > 4) { + s.dead[i].free(); + s.dead[i] = std::move(s.dead.back()); + s.dead.pop_back(); + } else { + i++; + } + } +} + +void drop_image(Image& im) { + if (!im.image) return; + const Image old = im; + later([old] { + vkDestroyImageView(dev.device, old.view, nullptr); + destroy_image(old.image, old.memory); + }); + im = {}; +} + +bool make_image(VkCommandBuffer cmd, Image& im, VkFormat format, uint32_t w, uint32_t h, VkImageUsageFlags extra = 0) { + if (im.image && im.format == format && im.width == w && im.height == h) return true; + drop_image(im); + VkImageCreateInfo ic{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + ic.imageType = VK_IMAGE_TYPE_2D, ic.format = format, ic.extent = {w, h, 1}, ic.mipLevels = 1, ic.arrayLayers = 1; + ic.samples = VK_SAMPLE_COUNT_1_BIT, ic.tiling = VK_IMAGE_TILING_OPTIMAL; + ic.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | extra; + if (!create_image(ic, im.image, im.memory)) return im = {}, false; + VkImageViewCreateInfo vc{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + vc.image = im.image, vc.viewType = VK_IMAGE_VIEW_TYPE_2D, vc.format = format; + vc.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + if (vkCreateImageView(dev.device, &vc, nullptr, &im.view) != VK_SUCCESS) { + destroy_image(im.image, im.memory); + return im = {}, false; + } + im.format = format, im.width = w, im.height = h; + VkImageMemoryBarrier2 b{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2}; // (UNDEFINED -> GENERAL, once) + b.srcStageMask = VK_PIPELINE_STAGE_2_NONE, b.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; + b.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT; + b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, b.newLayout = VK_IMAGE_LAYOUT_GENERAL; + b.srcQueueFamilyIndex = b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b.image = im.image, b.subresourceRange = vc.subresourceRange; + VkDependencyInfo di{VK_STRUCTURE_TYPE_DEPENDENCY_INFO}; + di.imageMemoryBarrierCount = 1, di.pImageMemoryBarriers = &b; + vkCmdPipelineBarrier2(cmd, &di); + return true; +} + +/// One compute pipeline with push descriptors; what it made is destroyed again when a step fails +bool make_pipeline(const uint32_t* code, size_t size, const VkDescriptorType* types, uint32_t n, uint32_t push, + VkDescriptorSetLayout& set, VkPipelineLayout& layout, VkPipeline& pipe) { + const auto undo = [&] { + if (layout) vkDestroyPipelineLayout(dev.device, layout, nullptr); + if (set) vkDestroyDescriptorSetLayout(dev.device, set, nullptr); + set = VK_NULL_HANDLE, layout = VK_NULL_HANDLE, pipe = VK_NULL_HANDLE; + return false; + }; + VkDescriptorSetLayoutBinding b[8]{}; + for (uint32_t i = 0; i < n; i++) b[i] = {i, types[i], 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}; + VkDescriptorSetLayoutCreateInfo li{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + li.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR; + li.bindingCount = n, li.pBindings = b; + if (vkCreateDescriptorSetLayout(dev.device, &li, nullptr, &set) != VK_SUCCESS) return set = VK_NULL_HANDLE, undo(); + const VkPushConstantRange pr{VK_SHADER_STAGE_COMPUTE_BIT, 0, push}; + VkPipelineLayoutCreateInfo pi{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + pi.setLayoutCount = 1, pi.pSetLayouts = &set, pi.pushConstantRangeCount = 1, pi.pPushConstantRanges = ≺ + if (vkCreatePipelineLayout(dev.device, &pi, nullptr, &layout) != VK_SUCCESS) return layout = VK_NULL_HANDLE, undo(); + VkShaderModuleCreateInfo mi{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + mi.codeSize = size, mi.pCode = code; + VkShaderModule m{}; + if (vkCreateShaderModule(dev.device, &mi, nullptr, &m) != VK_SUCCESS) return undo(); + VkComputePipelineCreateInfo ci{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + ci.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0, VK_SHADER_STAGE_COMPUTE_BIT, m, "main", nullptr}; + ci.layout = layout; + const bool ok = vkCreateComputePipelines(dev.device, VK_NULL_HANDLE, 1, &ci, nullptr, &pipe) == VK_SUCCESS; + vkDestroyShaderModule(dev.device, m, nullptr); + return ok || undo(); +} + +/// The two passes' pipelines and the samplers, once (temporal_upscale stops trying when this fails) +bool make_pipelines() { + State& s = st(); + if (s.prep && s.out && s.point && s.linear) return true; + if (!dev.push_descriptor || !vkCmdPushDescriptorSetKHR) return false; + const VkDescriptorType S = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, I = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + const VkDescriptorType prep_types[5] = {S, S, I, I, I}, out_types[2] = {S, I}; + if (!s.prep && !make_pipeline(DLSS_PREP_COMP_SPV, sizeof DLSS_PREP_COMP_SPV, prep_types, 5, 84, s.prep_set, s.prep_layout, s.prep)) + return false; + if (!s.out && !make_pipeline(DLSS_OUT_COMP_SPV, sizeof DLSS_OUT_COMP_SPV, out_types, 2, 8, s.out_set, s.out_layout, s.out)) + return false; + VkSamplerCreateInfo si{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + si.magFilter = si.minFilter = VK_FILTER_NEAREST; + si.addressModeU = si.addressModeV = si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + if (!s.point && vkCreateSampler(dev.device, &si, nullptr, &s.point) != VK_SUCCESS) return s.point = VK_NULL_HANDLE, false; + si.magFilter = si.minFilter = VK_FILTER_LINEAR; + if (!s.linear && vkCreateSampler(dev.device, &si, nullptr, &s.linear) != VK_SUCCESS) return s.linear = VK_NULL_HANDLE, false; + return true; +} + +/// A view of a depth texture's depth alone, for sampling (made once per image) +VkImageView depth_view(Texture* t) { + State& s = st(); + const uint64_t gen = texture_destroy_generation(); + if (gen != s.depth_views_gen) { // (a texture was destroyed: its image's view may be stale) + auto old = std::move(s.depth_views); + s.depth_views.clear(); + later([old] { + for (const auto& [image, view] : old) vkDestroyImageView(dev.device, view, nullptr); + }); + s.depth_views_gen = gen; + } + if (auto it = s.depth_views.find(t->image); it != s.depth_views.end()) return it->second; + VkImageViewCreateInfo vc{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + vc.image = t->image, vc.viewType = VK_IMAGE_VIEW_TYPE_2D, vc.format = t->format; + vc.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1}; + VkImageView v = VK_NULL_HANDLE; + if (vkCreateImageView(dev.device, &vc, nullptr, &v) != VK_SUCCESS) v = VK_NULL_HANDLE; + s.depth_views[t->image] = v; + return v; +} + +// ---- actor motion: the skinned draws drawn again into the motion, each pixel they show moved by its own bones ---- + +using ActorPush = dlss_actor::Push; + +/// Its pipeline, once (false: none, the motion stays the camera's) +bool make_actor_pipeline() { + State& s = st(); + if (s.actor) return true; + if (s.actor_tried) return false; + s.actor_tried = true; + VkPhysicalDeviceFeatures2 f{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2}; + vkGetPhysicalDeviceFeatures2(dev.physical, &f); + if (!dev.vk12.bufferDeviceAddress || !f.features.shaderInt64) { // (its shaders read the game's buffers by their addresses) + logf("vk: upscaler: no buffer device addresses or 64-bit integers in shaders: camera motion alone"); + return false; + } + VkDescriptorSetLayoutBinding b[2]{}; + b[0] = {0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}; + b[1] = {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}; + VkDescriptorSetLayoutCreateInfo li{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + li.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR; + li.bindingCount = 2, li.pBindings = b; + VkDescriptorSetLayout set{}; + if (vkCreateDescriptorSetLayout(dev.device, &li, nullptr, &set) != VK_SUCCESS) return false; + const VkPushConstantRange pr{VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ActorPush)}; + VkPipelineLayoutCreateInfo pi{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + pi.setLayoutCount = 1, pi.pSetLayouts = &set, pi.pushConstantRangeCount = 1, pi.pPushConstantRanges = ≺ + if (vkCreatePipelineLayout(dev.device, &pi, nullptr, &s.actor_layout) != VK_SUCCESS) return false; + VkShaderModule m[2]{}; + const uint32_t* code[2] = {DLSS_ACTOR_VERT_SPV, DLSS_ACTOR_FRAG_SPV}; + const size_t size[2] = {sizeof DLSS_ACTOR_VERT_SPV, sizeof DLSS_ACTOR_FRAG_SPV}; + for (int i = 0; i < 2; i++) { + VkShaderModuleCreateInfo mi{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + mi.codeSize = size[i], mi.pCode = code[i]; + if (vkCreateShaderModule(dev.device, &mi, nullptr, &m[i]) != VK_SUCCESS) return false; + } + VkPipelineShaderStageCreateInfo stages[2]{}; + for (int i = 0; i < 2; i++) { + stages[i] = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + stages[i].stage = i ? VK_SHADER_STAGE_FRAGMENT_BIT : VK_SHADER_STAGE_VERTEX_BIT; + stages[i].module = m[i], stages[i].pName = "main"; + } + VkPipelineVertexInputStateCreateInfo vi{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; + VkPipelineInputAssemblyStateCreateInfo ia{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + VkPipelineViewportStateCreateInfo vp{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; + vp.viewportCount = vp.scissorCount = 1; + VkPipelineRasterizationStateCreateInfo rs{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO}; + rs.polygonMode = VK_POLYGON_MODE_FILL, rs.cullMode = VK_CULL_MODE_NONE, rs.lineWidth = 1.0f; + VkPipelineMultisampleStateCreateInfo ms{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; + ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + VkPipelineColorBlendAttachmentState ba{}; + ba.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT; + VkPipelineColorBlendStateCreateInfo cb{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; + cb.attachmentCount = 1, cb.pAttachments = &ba; + const VkDynamicState dyn[2] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; + VkPipelineDynamicStateCreateInfo ds{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; + ds.dynamicStateCount = 2, ds.pDynamicStates = dyn; + const VkFormat format = VK_FORMAT_R16G16_SFLOAT; + VkPipelineRenderingCreateInfo ri{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO}; + ri.colorAttachmentCount = 1, ri.pColorAttachmentFormats = &format; + VkGraphicsPipelineCreateInfo gi{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + gi.pNext = &ri; + gi.stageCount = 2, gi.pStages = stages; + gi.pVertexInputState = &vi, gi.pInputAssemblyState = &ia, gi.pViewportState = &vp, gi.pRasterizationState = &rs; + gi.pMultisampleState = &ms, gi.pColorBlendState = &cb, gi.pDynamicState = &ds, gi.layout = s.actor_layout; + const bool ok = vkCreateGraphicsPipelines(dev.device, VK_NULL_HANDLE, 1, &gi, nullptr, &s.actor) == VK_SUCCESS; + for (VkShaderModule x : m) vkDestroyShaderModule(dev.device, x, nullptr); + if (!ok) s.actor = VK_NULL_HANDLE, logf("vk: upscaler: the actor motion's pipeline was not made: camera motion alone"); + return ok; +} + +/// A range of guest memory as this recording binds it, by its device address (the pools and their mirrors have them) +uint64_t device_address(const BufferRef& r) { + if (!r) return 0; + VkBufferDeviceAddressInfo da{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO}; + da.buffer = r.buffer; + const VkDeviceAddress base = vkGetBufferDeviceAddress(dev.device, &da); + return base ? base + r.offset : 0; +} + +constexpr uint64_t kPoseBytes = 4 << 20; // (a pose buffer: 280 bones of 48 bytes are 13 KB a draw, 300 of them) + +/// The skinned draws into the scene (cam's depth) with a pose of the frame before (the engine's last), drawn into the motion +/// image over the camera's motion. Each one's bones and origin are copied on the GPU, from where the game has them, into +/// this frame's pose buffer, for this frame and the next. before: the frame before's camera without its jitter. history: +/// the engine has the frame before. Returns the draws drawn +int actor_motion(VkCommandBuffer cmd, const Camera& cam, const float* before, uint32_t rw, uint32_t rh, VkImageView dview, + bool history) { + State& s = st(); + struct Timed { + State& s; + std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); + ~Timed() { s.actor_ns += (std::chrono::steady_clock::now() - t0).count(); } + } timed{s}; + std::vector skins = std::move(s.skins); + s.skins.clear(); + std::unordered_map old = std::move(s.poses); + s.poses.clear(); + static const bool off = env_switch("RX_VK_DLSS_ACTORS") == 0; + if (off || skins.empty() || !make_actor_pipeline()) return 0; + for (auto& b : s.pose_buffers) + if (!b.buffer) { + if (!create_device_buffer(kPoseBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + b.buffer, b.memory)) + return b = {}, 0; + b.address = device_address({b.buffer, 0, kPoseBytes, 0}); + } + const uint64_t now = texture_frame_no(); + const State::PoseBuffer& here = s.pose_buffers[s.pose_at]; + const State::PoseBuffer& there = s.pose_buffers[s.pose_at ^ 1]; + static const uint32_t tolerance = env_switch("RX_VK_DLSS_ACTOR_TOL") >= 0 ? (uint32_t)env_switch("RX_VK_DLSS_ACTOR_TOL") : 64; + std::vector draws; + std::vector counts; + uint32_t at = 0; + for (const auto& k : skins) { + if (k.k.depth != cam.depth) continue; + const uint32_t bytes = 16 + k.nbones * 48; + if (at + bytes > kPoseBytes) break; + // (this frame's pose, from where the game has it: its bones, then its origin at the object's 0x40) + const BufferRef bones = gpu_buffer_bind(k.k.bones, (uint64_t)k.nbones * 48, false); + const BufferRef origin = gpu_buffer_bind(k.k.object + 0x40, 12, false); + if (!bones || !origin) { + s.actor_skipped++; + continue; + } + const VkBufferCopy c0{origin.offset, at, 12}, c1{bones.offset, at + 16, (uint64_t)k.nbones * 48}; + vkCmdCopyBuffer(cmd, origin.buffer, here.buffer, 1, &c0); + vkCmdCopyBuffer(cmd, bones.buffer, here.buffer, 1, &c1); + s.poses[k.key] = {at, k.nbones, now}; + const uint32_t mine = at; + at += (bytes + 15) & ~15u; + const auto it = old.find(k.key); + if (!history || it == old.end() || it->second.frame != s.last_frame || it->second.nbones != k.nbones) { + s.actor_skipped++; + continue; + } + // (the vertices and indices where the game has them: its buffers as this recording binds them) + const uint64_t va = device_address(gpu_buffer_bind(k.k.vbuf, k.k.vsize, false)); + const uint64_t ia = device_address(gpu_buffer_bind(k.k.ibuf, (uint64_t)k.k.count << k.k.itype, false)); + if (!va || !ia || va % 4 || k.k.count <= 0 || k.k.itype < 0 || k.k.itype > 2) { + s.actor_skipped++; + continue; + } + ActorPush a{}; + a.vertices = va, a.count = (uint32_t)std::min(k.k.vsize / k.k.stride, UINT32_MAX); + dlss_actor::index_start(ia, k.k.itype, &a.indices, &a.first); + a.origins[0] = here.address + mine, a.origins[1] = there.address + it->second.at; + a.bones[0] = a.origins[0] + 16, a.bones[1] = a.origins[1] + 16; + a.nbones = k.nbones; + a.itype = (uint32_t)k.k.itype, a.base_vertex = k.k.base_vertex; + a.stride = k.k.stride / 4, a.pos = k.k.pos, a.sets = (uint32_t)k.k.sets; + for (int i = 0; i < 2; i++) a.joints[i] = k.k.joints[i], a.weights[i] = k.k.weights[i]; + a.formats = k.k.formats; + a.tolerance = tolerance; + draws.push_back(a); + counts.push_back((uint32_t)k.k.count); + } + s.pose_at ^= 1; + if (draws.empty()) return 0; + // (the cameras: this frame's as drawn, the frame before's without its jitter, the jitter, the render size) + State::Upload& u = s.uploads[s.upload_at]; + s.upload_at = (s.upload_at + 1) % (int)(sizeof s.uploads / sizeof s.uploads[0]); + if (!u.buffer && !create_buffer_cached(dlss_actor::kHeadWords * 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, u.buffer, u.memory, &u.map)) + return u = {}, 0; + float* head = (float*)u.map; + std::memcpy(head, cam.current, 64); + std::memcpy(head + 16, before, 64); + head[32] = cam.jitter[0], head[33] = cam.jitter[1], head[34] = (float)rw, head[35] = (float)rh; + flush_mapped(u.memory, dlss_actor::kHeadWords * 4); + full_barrier(cmd); // (the poses copied) + VkRenderingAttachmentInfo ca{VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO}; + ca.imageView = s.motion.view, ca.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + ca.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, ca.storeOp = VK_ATTACHMENT_STORE_OP_STORE; + VkRenderingInfo ri{VK_STRUCTURE_TYPE_RENDERING_INFO}; + ri.renderArea = {{0, 0}, {rw, rh}}, ri.layerCount = 1, ri.colorAttachmentCount = 1, ri.pColorAttachments = &ca; + vkCmdBeginRendering(cmd, &ri); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, s.actor); + const VkViewport viewport{0, 0, (float)rw, (float)rh, 0.0f, 1.0f}; + const VkRect2D scissor{{0, 0}, {rw, rh}}; + vkCmdSetViewport(cmd, 0, 1, &viewport); + vkCmdSetScissor(cmd, 0, 1, &scissor); + const VkDescriptorBufferInfo bi{u.buffer, 0, dlss_actor::kHeadWords * 4}; + const VkDescriptorImageInfo ii{s.point, dview, VK_IMAGE_LAYOUT_GENERAL}; + VkWriteDescriptorSet w[2]{{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}, {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}}; + w[0].dstBinding = 0, w[0].descriptorCount = 1, w[0].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, w[0].pBufferInfo = &bi; + w[1].dstBinding = 1, w[1].descriptorCount = 1, w[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + w[1].pImageInfo = ⅈ + vkCmdPushDescriptorSetKHR(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, s.actor_layout, 0, 2, w); + for (size_t i = 0; i < draws.size(); i++) { // (vertex i of a draw is its i-th index) + vkCmdPushConstants(cmd, s.actor_layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof draws[i], &draws[i]); + vkCmdDraw(cmd, counts[i], 1, 0, 0); + } + vkCmdEndRendering(cmd); + s.actor_draws += draws.size(), s.actor_frames++; + return (int)draws.size(); +} + +/// The bones a skinned draw's vertices use (the largest joint with a weight, plus one), read once a model; 0 when its +/// buffers cannot be read +uint32_t bones_used(const SkinnedDraw& k) { + if (k.count <= 0 || k.count > (1 << 20) || k.itype < 0 || k.itype > 2 || !k.stride) return 0; + std::vector idx((size_t)k.count << k.itype); + if (!memory_read_as_submitted(k.ibuf, idx.data(), idx.size())) return 0; + uint32_t lo = UINT32_MAX, hi = 0; + for (int i = 0; i < k.count; i++) { + uint32_t v = 0; + std::memcpy(&v, idx.data() + ((size_t)i << k.itype), (size_t)1 << k.itype); + lo = std::min(lo, v), hi = std::max(hi, v); + } + const int64_t first = (int64_t)k.base_vertex + lo, n = (int64_t)hi - lo + 1; + if (first < 0 || n > (1 << 20) || (uint64_t)(first + n) * k.stride > k.vsize) return 0; + std::vector v((size_t)n * k.stride); + if (!memory_read_as_submitted(k.vbuf + (uint64_t)first * k.stride, v.data(), v.size())) return 0; + uint32_t most = 0; + for (int64_t i = 0; i < n; i++) { + const uint8_t* q = v.data() + (size_t)i * k.stride; + for (int set = 0; set < k.sets; set++) + for (int j = 0; j < 4; j++) { + if (!q[k.weights[set] + j]) continue; + uint32_t joint = 0; + if (k.formats & (dlss_actor::kByteJoints << set)) joint = q[k.joints[set] + j]; + else std::memcpy(&joint, q + k.joints[set] + 2 * j, 2); + most = std::max(most, joint + 1); + } + } + return most; +} + +/// The chain's images and buffers let go (the engine's own: its release) +void drop_all() { + State& s = st(); + for (Image* im : {&s.color, &s.depth, &s.motion, &s.output}) drop_image(*im); + for (auto& pb : s.pose_buffers) // (the actor motion's poses and cameras) + if (pb.buffer) { + const State::PoseBuffer gone = pb; + later([gone] { destroy_buffer(gone.buffer, gone.memory); }); + pb = {}; + } + for (auto& u : s.uploads) + if (u.buffer) { + const State::Upload gone = u; + later([gone] { destroy_buffer(gone.buffer, gone.memory); }); + u = {}; + } + s.poses.clear(), s.skins.clear(), s.model_bones.clear(); + if (!s.depth_views.empty()) { + auto old = std::move(s.depth_views); + s.depth_views.clear(); + later([old] { + for (const auto& [image, view] : old) vkDestroyImageView(dev.device, view, nullptr); + }); + } +} + +} // namespace + +void temporal_later(std::function f) { later(std::move(f)); } + +void temporal_note_scene_draw(const Texture* color0, uint32_t width, uint32_t height, Texture* depth, uint64_t camera, + uint64_t camera_size) { + // (only a size the gameplay upscale's output can come from: 1 to 3 times smaller. A smaller target with a camera (the + // pause screen's model views, a reflection) is never the scene; one this size that is not either is left by the checks + // below: the format, the camera's own size and perspective, the largest in the frame, and the upscale's input) + if (!color0 || !depth || !camera || camera_size < 0x230 || !temporal_scene_size(width, height)) return; + if (color0->format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || !temporal_wanted()) return; + State& s = st(); + std::lock_guard lk(s.mu); + const uint64_t now = texture_frame_no(); + if (s.cam.valid && s.cam.frame == now && (uint64_t)width * height <= (uint64_t)s.cam.width * s.cam.height) + return; // (the largest one of the frame wins) + float c[0x230 / 4]; + if (!memory_read_as_submitted(camera, c, sizeof c)) return; + // (the target's size at 0x120; the projection at 0x70, its w row (0, 0, -1, 0) at 0xa0) + if (c[0x120 / 4] != (float)width || c[0x124 / 4] != (float)height || c[0xa0 / 4] != 0.0f || c[0xa4 / 4] != 0.0f || + c[0xa8 / 4] != -1.0f || c[0xac / 4] != 0.0f) + return; + Camera& k = s.cam; + std::memcpy(k.current, c + 0x30 / 4, sizeof k.current); + // (the offset sead's projection gave the frustum, in its third column: 2 * offset, which moves the picture by -that) + k.jitter[0] = -c[0x78 / 4], k.jitter[1] = -c[0x88 / 4]; + // (its planes and field of view, for an engine that wants them (FSR's depth heuristics): an OpenGL-style projection, depth + // -1 to 1, P[2][2] = -(f + n) / (f - n) and P[2][3] = -2fn / (f - n); 0 when they do not read as one) + const float p11 = c[0x84 / 4], p22 = c[0x98 / 4], p23 = c[0x9c / 4]; + const float n = p23 / (p22 - 1.0f), f = p23 / (p22 + 1.0f); + const bool plain = std::isfinite(n) && n > 0.0f && p11 > 0.0f && (!std::isfinite(f) || f > n); + k.near_z = plain ? n : 0.0f, k.far_z = plain ? (std::isfinite(f) ? f : 1e9f) : 0.0f; + k.fov_y = plain ? 2.0f * std::atan(1.0f / p11) : 0.0f; + k.width = width, k.height = height, k.depth = depth, k.depth_gen = texture_destroy_generation(), k.frame = now; + k.valid = true; +} + +bool temporal_scene_size(uint32_t width, uint32_t height) { + const State& s = st(); + return dlss_checks::scene_size_fits(width, height, s.out_w.load(std::memory_order_relaxed), + s.out_h.load(std::memory_order_relaxed)); +} + +uint32_t temporal_jitter_size(int axis) { + // (RX_VK_DLSS_JITTER=0: none. 0 too once no engine has drawn for a while: the camera as the game has it) + static const bool off = env_switch("RX_VK_DLSS_JITTER") == 0; + State& s = st(); + const int mode = temporal_mode(); + if (off || mode == ur::GAME) return 0; + const uint64_t last = s.last_frame.load(std::memory_order_relaxed); + if (s.engines[ur::is_fsr(mode) ? 1 : 0].failed.load(std::memory_order_relaxed) || !last || texture_frame_no() - last > 30) + return 0; + if (axis == 2) return s.jitter_phases.load(std::memory_order_relaxed); + return axis ? s.jitter_h.load(std::memory_order_relaxed) : s.jitter_w.load(std::memory_order_relaxed); +} + +int temporal_status(char* b, size_t n) { + State& s = st(); + const uint64_t last = s.last_frame.load(std::memory_order_relaxed); + const int mode = temporal_mode(); + if (mode == ur::GAME || !last || texture_frame_no() - last > 30) return 0; + std::lock_guard lk(s.mu); + const Engine& k = engine_of(mode); + if (k.failed || !k.e) return 0; + // (the chain's own video memory: the images and buffers it made here, and what the engine says it holds, when it says) + uint64_t own = 0; + for (const Image* im : {&s.color, &s.depth, &s.motion, &s.output}) + if (im->image) own += allocation_size(im->memory); + for (const auto& pb : s.pose_buffers) + if (pb.buffer) own += allocation_size(pb.memory); + for (const auto& u : s.uploads) + if (u.buffer) own += allocation_size(u.memory); + const uint64_t engine = k.e->memory(); + const uint64_t draws = s.actor_draws - s.status_draws, frames = s.actor_frames - s.status_frames; + char feature[48] = ""; + if (engine) snprintf(feature, sizeof feature, ", its own %llu MB", (unsigned long long)(engine >> 20)); + char passed[64] = ""; + if (s.status_passed) snprintf(passed, sizeof passed, ", %llu left to the game's network", (unsigned long long)s.status_passed); + snprintf(b, n, "; %s: %llu frames drawn%s, %llu made, jitter %+.3f %+.3f px%s, actor motion %llu draws in %llu " + "frames (%.3f ms CPU a frame), its images %llu MB%s", + name_of(mode), (unsigned long long)s.status_drawn, passed, (unsigned long long)s.engines_made, + s.status_jitter[0], s.status_jitter[1], s.status_motion ? "" : " (no motion)", + (unsigned long long)draws, (unsigned long long)frames, frames ? s.actor_ns / 1e6 / (double)frames : 0.0, + (unsigned long long)(own >> 20), feature); + s.status_draws = s.actor_draws, s.status_frames = s.actor_frames, s.actor_ns = 0, s.status_drawn = s.status_passed = 0; + return mode; +} + +void temporal_idle() { + State& s = st(); + if (!s.holding.load(std::memory_order_relaxed)) return; + std::lock_guard lk(s.mu); + const uint64_t now = texture_frame_no(); + if (now - s.asked <= 120) return; // (in use, or off for under 120 frames (2 s): kept, a row stepped through comes back at once) + if (s.drawing) { + logf("vk: %s: not used for 120 frames (the last at frame %llu, now %llu): what it made and its images let go; " + "the next upscale with it makes them again", s.drawing->name, (unsigned long long)s.asked, (unsigned long long)now); + s.drawing->release(); + s.drawing = nullptr; + } + drop_all(); + s.last_frame = 0; // (a new history when it comes back) + free_dead(false); + s.holding = !s.dead.empty(); +} + +void temporal_note_skinned_draw(const SkinnedDraw& k) { + static const bool off = env_switch("RX_VK_DLSS_ACTORS") == 0; + if (off || !temporal_wanted() || !k.depth || !k.bones || k.bones_size < 48 || !k.object || k.object_size < 0x4c) return; + State& s = st(); + std::lock_guard lk(s.mu); + if (s.skins.size() >= 512) s.skins.clear(); // (no upscale for a while: what was kept is stale) + struct Timed { + State& s; + std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); + ~Timed() { s.actor_ns += (std::chrono::steady_clock::now() - t0).count(); } + } timed{s}; + State::Skin d; + d.k = k; + const uint64_t model = dlss_actor::key(k.vbuf, k.ibuf, k.count, k.base_vertex, k.program, 0); + // (only the bones its vertices use are taken: a palette holds up to 280, a model uses a few dozen) + auto mb = s.model_bones.find(model); + if (mb == s.model_bones.end()) { + if (s.model_bones.size() > 4096) s.model_bones.clear(); + mb = s.model_bones.emplace(model, bones_used(k)).first; + } + d.nbones = (uint32_t)std::min(mb->second, std::min(k.bones_size / 48, 280)); + if (!d.nbones) return; + int nth = 0; // (the same model drawn twice in a frame: the n-th draw of it is the n-th the frame before) + for (const auto& o : s.skins) nth += dlss_actor::same_model(o.key, model); + d.key = dlss_actor::key(k.vbuf, k.ibuf, k.count, k.base_vertex, k.program, nth); + s.skins.push_back(std::move(d)); +} + +bool temporal_upscale(VkCommandBuffer cmd, uint64_t key, bool known, Texture* in, VkImageView in_view, Texture* out, + VkImageView out_view, uint32_t out_level) { + const int mode = temporal_mode(); + if (mode == ur::GAME) return false; + State& s = st(); + std::lock_guard lk(s.mu); + if (!known) { // (another network of the upscale's shape: only the scene into the picture, the rest untouched and unsaid) + const Camera& c = s.cam; + // (the gameplay networks' own formats: they sample the scene's B10G11R11 colour and write the B10G11R11 picture. + // Other cooperative-matrix networks of the same shape (one RGBA8 into A2B10G10R10, RGB9E5 into B10G11R11) are not it) + if (!in || !out || in->format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || out->format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || + (out->width >> out_level) <= in->width) + return false; + note_output(out, out_level); + // (one in the gameplay networks' formats whose input is not this frame's scene: counted for the status line) + if (!c.valid || texture_frame_no() - c.frame > 1 || in->width != c.width || in->height != c.height) + return s.status_passed++, false; + static std::atomic told{0}; + if (told.exchange(key) != key) logf("vk: upscaler: network %016llx is the gameplay upscale (%ux%u -> %ux%u)", + (unsigned long long)key, in->width, in->height, out->width >> out_level, + out->height >> out_level); + } + free_dead(false); + if (known && out && out->format == VK_FORMAT_B10G11R11_UFLOAT_PACK32) note_output(out, out_level); + const Camera cam = s.cam; + s.cam.valid = false; // (each frame's own camera) + struct SkinsDone { // (the skinned draws, each frame's own too: actor_motion takes them, or they go here) + State& s; + ~SkinsDone() { s.skins.clear(); } + } skins_done{s}; + + Engine& engine = engine_of(mode); + if (!engine.e || engine.failed) return fail("it failed earlier"); + s.asked = texture_frame_no(), s.holding = true; // (not after a failure: what it holds is let go, temporal_idle) + if (!in || !in_view || !out || !out_view) return fail("no input or output view"); + if (!cam.valid || texture_frame_no() - cam.frame > 1) return fail("no scene camera this frame"); + if (cam.depth_gen != texture_destroy_generation()) return fail("the scene depth may be gone"); + const uint32_t rw = in->width, rh = in->height; + const uint32_t ow = std::max(1u, out->width >> out_level), oh = std::max(1u, out->height >> out_level); + if (rw != cam.width || rh != cam.height) return fail("the input is not the scene's size"); + if (cam.depth->width != rw || cam.depth->height != rh || !(cam.depth->usage & VK_IMAGE_USAGE_SAMPLED_BIT) || + cam.depth->samples != VK_SAMPLE_COUNT_1_BIT || !(cam.depth->aspect & VK_IMAGE_ASPECT_DEPTH_BIT)) + return fail("unexpected scene depth"); + if (out->format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || !dlss_checks::scene_size_fits(rw, rh, ow, oh)) + return fail("unexpected output"); + if (!engine.started) engine.started = true, engine.ok = engine.e->start(); + if (!engine.ok) { + engine.failed = true; + return fail("not available"); + } + if (!make_pipelines()) { + engine.failed = true; + return fail("its shaders were not made"); + } + const VkImageView dview = depth_view(cam.depth); + if (!dview) return fail("no view of the scene depth"); + if (s.drawing != engine.e) { // (another engine than the one before: that one's state let go, and a new history) + if (s.drawing) { + logf("vk: upscaler: %s in place of %s (frame %llu)", engine.e->name, s.drawing->name, + (unsigned long long)texture_frame_no()); + s.drawing->release(); + } + s.drawing = engine.e; + s.last_frame = 0; + } + // (DLAA, FSR's native AA: the engine at the render size, anti-aliasing alone; its picture then scaled to the output's by + // dlss_out.comp. With them the 3D is drawn at full scale, temporal_full_scale) + const bool native = ur::full_scale(mode); + const uint32_t dw = native ? rw : ow, dh = native ? rh : oh; + // (from here commands are recorded, the engine's own too: whichever way this returns, the program's descriptor pushes are + // gone from the bind point and are pushed again) + struct Disturbed { + ~Disturbed() { descriptors_disturbed(); } + } disturbed; + uint32_t iw = rw, ih = rh; + const char* why = nullptr; + const int ready = engine.e->prepare(cmd, rw, rh, dw, dh, &iw, &ih, &why); + if (ready < 0) { + engine.failed = true; + return fail(why ? why : "it could not be made"); + } + if (ready == 0) return fail(why ? why : "not this frame"); + if (ready == 2) s.engines_made++, s.last_frame = 0; // (made just now: a new history) + if (!make_image(cmd, s.color, VK_FORMAT_R16G16B16A16_SFLOAT, iw, ih) || + !make_image(cmd, s.depth, VK_FORMAT_R32_SFLOAT, iw, ih) || + !make_image(cmd, s.motion, VK_FORMAT_R16G16_SFLOAT, iw, ih, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) || + !make_image(cmd, s.output, VK_FORMAT_R16G16B16A16_SFLOAT, dw, dh)) + return fail("its images were not made"); + + const uint64_t now = texture_frame_no(); + const uint64_t last = s.last_frame; + const bool reset = !last || now - last > 2; + struct { + float reproject[16]; + float jitter[2]; + int32_t size[2]; + int32_t flags; + } pc{}; + static const bool show_motion = getenv("RX_VK_DLSS_DEBUG") && !strcmp(getenv("RX_VK_DLSS_DEBUG"), "motion"); + // (the motion without the jitter: from this frame's jittered clip space to the frame before's without its jitter. + // A jitter moves clip x by jitter * w: taken off the previous matrix's first two rows) + // (the frame before's camera as the engine last drew it: the buffer's own slot for it, 0x180, holds zeros in this version) + float previous[16]; + std::memcpy(previous, s.last_vp, sizeof previous); + dlss_math::unjitter(previous, s.last_jitter); + const bool motion = !reset && dlss_math::reprojection(cam.current, previous, pc.reproject); + pc.jitter[0] = cam.jitter[0], pc.jitter[1] = cam.jitter[1]; + pc.size[0] = (int32_t)rw, pc.size[1] = (int32_t)rh, pc.flags = (motion ? 1 : 0) | (show_motion ? 2 : 0); + + const auto prep = [&] { + const VkDescriptorImageInfo info[5] = {{s.point, in_view, VK_IMAGE_LAYOUT_GENERAL}, + {s.point, dview, VK_IMAGE_LAYOUT_GENERAL}, + {VK_NULL_HANDLE, s.color.view, VK_IMAGE_LAYOUT_GENERAL}, + {VK_NULL_HANDLE, s.depth.view, VK_IMAGE_LAYOUT_GENERAL}, + {VK_NULL_HANDLE, s.motion.view, VK_IMAGE_LAYOUT_GENERAL}}; + VkWriteDescriptorSet w[5]{}; + for (uint32_t i = 0; i < 5; i++) { + w[i] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + w[i].dstBinding = i, w[i].descriptorCount = 1, w[i].pImageInfo = &info[i]; + w[i].descriptorType = i < 2 ? VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER : VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + } + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, s.prep); + vkCmdPushDescriptorSetKHR(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, s.prep_layout, 0, 5, w); + vkCmdPushConstants(cmd, s.prep_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof pc, &pc); + vkCmdDispatch(cmd, (rw + 7) / 8, (rh + 7) / 8, 1); + }; + full_barrier(cmd); // (the scene's colour and depth, written before) + prep(); + full_barrier(cmd); + // (what moves by itself: the skinned draws drawn again over the camera's motion, with their own) + const int actors = actor_motion(cmd, cam, previous, rw, rh, dview, motion); + if (actors) full_barrier(cmd); + if (show_motion && actors) { // (RX_VK_DLSS_DEBUG=motion: the motion as it is now, the actors' in it) + pc.flags = 4; + prep(); + full_barrier(cmd); + } + TemporalFrame f; + f.color = s.color.shown(), f.depth = s.depth.shown(), f.motion = s.motion.shown(), f.output = s.output.shown(); + f.render_w = rw, f.render_h = rh; + // (the jitter in render pixels, x right and y down) + dlss_math::jitter_to_pixels(cam.jitter, rw, rh, f.jitter); + f.reset = reset; + if (cam.near_z > 0.0f) f.near_z = cam.near_z, f.far_z = cam.far_z, f.fov_y = cam.fov_y; + if (!s.told_camera && cam.near_z > 0.0f) { + s.told_camera = true; + logf("vk: upscaler: the scene's camera: near %.3f, far %.1f, vertical field of view %.1f degrees", cam.near_z, + cam.far_z, cam.fov_y * 57.29578f); + } + if (!engine.e->evaluate(cmd, f)) { + logf("vk: %s: its evaluation failed: the game's upscale from now on", engine.e->name); + engine.failed = true; + return false; // (the network writes its output over whatever the evaluation did) + } + full_barrier(cmd); + { + const int32_t opc[2] = {(int32_t)ow, (int32_t)oh}; + const VkDescriptorImageInfo info[2] = {{native ? s.linear : s.point, s.output.view, VK_IMAGE_LAYOUT_GENERAL}, + {VK_NULL_HANDLE, out_view, VK_IMAGE_LAYOUT_GENERAL}}; + VkWriteDescriptorSet w[2]{{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}, {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}}; + w[0].dstBinding = 0, w[0].descriptorCount = 1, w[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + w[0].pImageInfo = &info[0]; + w[1].dstBinding = 1, w[1].descriptorCount = 1, w[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + w[1].pImageInfo = &info[1]; + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, s.out); + vkCmdPushDescriptorSetKHR(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, s.out_layout, 0, 2, w); + vkCmdPushConstants(cmd, s.out_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof opc, opc); + vkCmdDispatch(cmd, (ow + 7) / 8, (oh + 7) / 8, 1); + } + s.last_frame = now; + s.last_jitter[0] = cam.jitter[0], s.last_jitter[1] = cam.jitter[1]; + // (for the status line, temporal_status: the jitter the engine was given, in render pixels) + s.status_jitter[0] = f.jitter[0], s.status_jitter[1] = f.jitter[1], s.status_motion = motion; + s.status_drawn++; + + std::memcpy(s.last_vp, cam.current, sizeof s.last_vp); + s.jitter_w = rw, s.jitter_h = rh, s.jitter_phases = (uint32_t)dlss_jitter_phases(rw, rh, dw, dh); + if (s.reason) logf("vk: %s draws the upscale again (frame %llu)", engine.e->name, (unsigned long long)now); + s.reason = nullptr; + // (RX_VK_DLSS_DEBUG=alternate, a test's reference: every other frame the game's own upscale over the engine's, which still + // draws and keeps its history. A frame of it then has the game's picture of the frames either side to be measured against) + static const bool alternate = getenv("RX_VK_DLSS_DEBUG") && !strcmp(getenv("RX_VK_DLSS_DEBUG"), "alternate"); + if (alternate && (now & 1)) return false; + if (s.told_first != engine.e) { + s.told_first = engine.e; + logf("vk: %s draws OoT's upscale: %ux%u -> %ux%u%s", name_of(mode), rw, rh, ow, oh, + show_motion ? " (its motion shown)" : ""); + } + return true; +} + +} // namespace mkwvk diff --git a/gpu/vk/vk_temporal.h b/gpu/vk/vk_temporal.h new file mode 100644 index 000000000..e8984f1db --- /dev/null +++ b/gpu/vk/vk_temporal.h @@ -0,0 +1,114 @@ +// A temporal upscaler in place of OoT's gameplay upscale network (vk_temporal.cpp): the frame chain any such upscaler +// needs, and the engines that draw with it. The chain finds the scene's camera and depth, makes the inputs at the render +// size (the colour as the network samples it, the depth, motion vectors with the skinned draws' own), keeps the camera +// jitter's state for the host (nx2/oot_jitter.py) and writes the engine's picture into the network's output. An engine +// (NVIDIA DLSS: vk_dlss.cpp; AMD FSR 4: vk_fsr4.cpp) only turns those inputs into the picture. +#pragma once + +#include +#include + +#include "vk_internal.h" + +namespace mkwvk { + +struct Texture; + +/// What the Upscaler row runs now (upscaler_rows.h: GAME, DLSS, DLAA, FSR, FSR_AA): the row's choice (opts.upscaler), or +/// RX_VK_DLSS=1 / 2 or RX_VK_FSR=1 / 2 over it; GAME when the chosen engine cannot run here +int temporal_mode(); +bool temporal_wanted(); // (temporal_mode() != GAME) +/// DLAA or FSR's native anti-aliasing: the 3D drawn at full scale, whatever the Render resolution row says +bool temporal_full_scale(); + +/// A draw into a scene-sized target (colour target 0 and the depth target), with the vertex stage's uniform buffer 3 at +/// `camera` (its size): the camera of the frame, when that buffer holds one +void temporal_note_scene_draw(const Texture* color0, uint32_t width, uint32_t height, Texture* depth, uint64_t camera, + uint64_t camera_size); +/// Whether a target of width x height can be the scene the gameplay upscale takes (1 to 3 times smaller than its output, as +/// last seen; dlss_checks.h scene_size_fits): the camera, the actor motion and RX_VK_DLSS_TRACE take only those +bool temporal_scene_size(uint32_t width, uint32_t height); +/// A skinned draw into a scene-sized target (vk_draw.cpp finds them by their vertex layout): what the motion needs to draw +/// it again with this frame's bones and the frame before's (actor motion: Link, the others, what moves by itself) +struct SkinnedDraw { + const Texture* depth = nullptr; // (its depth target: only the scene's are taken) + uint64_t vbuf = 0, vsize = 0; // (vertex stream 0) + uint32_t stride = 0; + uint32_t pos = 0; // (byte offsets in a vertex: the position, RGB32F or RGBA16F) + uint32_t joints[2]{}, weights[2]{}; // (RGBA16UI or RGBA8UI joints and RGBA8 weights, four a set; sets: 1 or 2) + int sets = 0; + uint32_t formats = 0; // (dlss_actor::Push::formats) + uint64_t ibuf = 0; + int itype = 0, count = 0, base_vertex = 0; + uint64_t bones = 0, bones_size = 0; // (the palette: 3 x 4 row-major matrices, model space to the camera's world) + uint64_t object = 0, object_size = 0; // (the object's buffer: its origin at 0x40) + uint64_t program = 0; +}; +void temporal_note_skinned_draw(const SkinnedDraw& k); + +/// A gameplay upscale's dispatch (program `key`; vk_draw.cpp's temporal_stand_in finds them: the 4/3 network fd029a317a9a4b94 +/// at the game's usual 3/4, the same-size pass 9dc98871eb7fc101 at full size, and one network of its own for each other +/// share): its input as it samples it (in, in_view) and its output's storage view (out at level 0). known: one of the +/// gameplay networks by its key; else a network of that shape (the fixed shares' own), taken only when its input is this +/// frame's scene and its output the picture, and left to run without a word otherwise. true: the engine wrote the output +/// (the dispatch is left out); false: the network runs, the reason logged +bool temporal_upscale(VkCommandBuffer cmd, uint64_t key, bool known, Texture* in, VkImageView in_view, Texture* out, + VkImageView out_view, uint32_t out_level); + +/// Once a frame (the game's present): after 120 frames without an upscale by an engine (off, or the upscale not taken) the +/// engine's own state, the chain's images and deferred frees are let go, so their video memory comes back. Nothing to do +/// costs one atomic load +void temporal_idle(); + +/// For the renderer's status line (vk_replay.cpp status_tick): while an engine draws, its details since the last call into b +/// ("; DLSS: jitter ..., actor motion ..., its images ... MB") and temporal_mode(); else 0 and b left alone +int temporal_status(char* b, size_t n); + +/// The scene's width (axis 0) or height (1) for the host's camera jitter (nx2/oot_jitter.py, HOS_OPT_DLSS_JITTER_W and _H): +/// 0 when there is to be none (no engine drawing, or RX_VK_DLSS_JITTER=0). Axis 2: the jitter sequence's length for the +/// engine's scale (dlss_jitter_phases, HOS_OPT_DLSS_JITTER_PHASES) +uint32_t temporal_jitter_size(int axis); + +// ---- what an engine is given and does ---- + +/// One of the chain's images: GENERAL before and after an engine's evaluation +struct TemporalImage { + VkImage image = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; + VkFormat format = VK_FORMAT_UNDEFINED; + uint32_t width = 0, height = 0; +}; +/// One frame's evaluation. color: the scene as the network samples it (tonemapped, square-root encoded: LDR, RGBA16F); +/// depth: R32F in [0, 1], not reversed; motion: RG16F in render pixels, towards the frame before, the jitter taken off. They +/// are as large as the engine asked (in_w x in_h), the frame's part at the top left. output: RGBA16F, at the output's size +/// (DLAA, FSR's native AA: the render size) +struct TemporalFrame { + TemporalImage color, depth, motion, output; + uint32_t render_w = 0, render_h = 0; + float jitter[2]{}; // (the scene's sub-pixel offset this frame, in render pixels, x right and y down) + bool reset = false; // (no history: the first frame, or one after a gap or a camera cut the chain saw) + float near_z = 0.1f, far_z = 1000.0f, fov_y = 1.0f; // (the scene's projection's, when it is a plain perspective one) +}; +/// An engine: plain functions, each called with the chain's lock held, on the recording thread +struct TemporalEngine { + const char* name; // (for the log and the status line: "NVIDIA DLSS", "AMD FSR 4") + const char* short_name; // ("DLSS", "FSR") + /// Once, before its first upscale: false when it cannot run here (it says why in the log) + bool (*start)(); + /// Ready to scale rw x rh into ow x oh, made again when its sizes or settings changed (cmd: what that records). in_w and + /// in_h: how large the inputs are to be (the most the render size can be for what it made). 1 ready, 2 ready and made + /// just now (a new history), 0 not this frame (*why: the reason), -1 failed for good (*why too) + int (*prepare)(VkCommandBuffer cmd, uint32_t rw, uint32_t rh, uint32_t ow, uint32_t oh, uint32_t* in_w, uint32_t* in_h, + const char** why); + /// Records the frame: false when it failed (for good: the game's upscale from then on) + bool (*evaluate)(VkCommandBuffer cmd, const TemporalFrame& f); + /// What it made, let go (its feature or context): the chain unused for a while, or another engine chosen. Work in flight + /// may still use it, so what it frees goes through later + void (*release)(); + /// Its own video memory for the status line, in bytes (0: not known) + uint64_t (*memory)(); +}; +/// Runs f a few frames on, to free what work in flight may still use. Called by the engines with the chain's lock held +void temporal_later(std::function f); + +} // namespace mkwvk diff --git a/host/rx_backend.c b/host/rx_backend.c index 675041eb8..5d549c62b 100644 --- a/host/rx_backend.c +++ b/host/rx_backend.c @@ -306,13 +306,13 @@ void rx_nvn_present(const nvn_tex *t, const int crop[4]) { InterlockedIncrement(&presents); vk ? vk->nvn_present(t, crop) : wgr_nvn_present(t, crop); } -/* (hookshot, nx2/oot_jitter.py) NVIDIA DLSS's camera jitter: this frame's sub-pixel offset of the scene's projection, in clip +/* (hookshot, nx2/oot_jitter.py) the temporal upscaler's camera jitter (NVIDIA DLSS, AMD FSR 4: gpu/vk/vk_temporal.cpp): this frame's sub-pixel offset of the scene's projection, in clip space (what P[0][2] and P[1][2] get), into out, for the projection matrix at guest address projection (row major, as the - game fills its views' uniform buffers from it); 0 when there is none: DLSS off (or a renderer without it: rx_option + game fills its views' uniform buffers from it); 0 when there is none: DLSS and FSR off (or a renderer without them: rx_option HOS_OPT_DLSS_JITTER_W and _H, the scene's size in pixels, are 0), or a projection that is not one of the picture's (gpu/vk/dlss_math.h dlss_jitter_clip: a shadow map's, the HUD's). The renderer reads the offset back from - the camera's uniform buffer (gpu/vk/vk_dlss.cpp), so this only has to pick sub-pixel offsets that cover a pixel evenly: - the Halton (2, 3) sequence, over as many frames as DLSS's scale wants (HOS_OPT_DLSS_JITTER_PHASES: gpu/vk/dlss_math.h + the camera's uniform buffer (gpu/vk/vk_temporal.cpp), so this only has to pick sub-pixel offsets that cover a pixel evenly: + the Halton (2, 3) sequence, over as many frames as the upscaler's scale wants (HOS_OPT_DLSS_JITTER_PHASES: gpu/vk/dlss_math.h dlss_jitter_phases, 8 for DLAA up to 64). */ int hos_dlss_jitter_clip(uint64_t projection, float out[2]) { static LONG at = -1; diff --git a/nx2/build.py b/nx2/build.py index a5a59af51..e78883d48 100644 --- a/nx2/build.py +++ b/nx2/build.py @@ -197,6 +197,10 @@ def ngx_sdk(sdk, linux): NGX_SDK = ngx_sdk(os.environ.get("HOOKSHOT_NGX_SDK", ""), LINUX) # (llvm-mingw: what NVIDIA's library asks of Microsoft's runtime, linked in with it. Not compiled otherwise) NGX_MSVC_RT = "vk_ngx_msvc_rt.cpp" +# HOOKSHOT_FSR=1: the Vulkan back end with AMD FSR 4 (gpu/vk/vk_fsr4.cpp), from AMD's FSR API headers (third_party/fidelityfx, +# MIT). AMD's signed DLLs are not part of the build: the user's, loaded at run time. Off without it (make_game.py --fsr sets +# it, and puts the camera jitter into the game with it: nx2/oot_jitter.py) +FSR = os.environ.get("HOOKSHOT_FSR", "") == "1" def ngx_link(sdk, mingw): @@ -799,6 +803,11 @@ def main(): ngx_flags = ["-DHOOKSHOT_NGX", "-I" + os.path.join(NGX_SDK, "include")] if NGX_SDK else [] if NGX_SDK: print("the Vulkan back end: with NVIDIA DLSS (NVIDIA's NGX SDK in %s)" % NGX_SDK, flush=True) + # (AMD FSR 4, a build's option too: only vk_fsr4.cpp sees AMD's headers; without them it is the stubs that say FSR is + # not there) + fsr_flags = ["-DHOOKSHOT_FSR", "-I" + os.path.join(kit.HERE, "..", "third_party", "fidelityfx")] if FSR else [] + if FSR: + print("the Vulkan back end: with AMD FSR 4 (AMD's FSR API headers, third_party/fidelityfx)", flush=True) # (Eden's recompiler and sirit see only their own headers: a change to the back end's vk_*.h does not # rebuild their ~500 files; the back end itself depends on everything, suyu's texture decoders included) vk_lib_deps = tree_headers(os.path.join(vk, "eden"), os.path.join(vk, "sirit"), os.path.join(vk, "compat"), @@ -811,7 +820,8 @@ def main(): vk_jobs.append((cxx, src, os.path.join(obj_dir, "vk_" + name + ".o"), vflags + ([shader_stamp] if os.path.basename(src) == "vk_shader.cpp" else []) + ([stamp_def] if os.path.basename(src) == "vk_menu.cpp" else []) + - (ngx_flags if os.path.basename(src) == "vk_dlss.cpp" else []), + (ngx_flags if os.path.basename(src) == "vk_dlss.cpp" else []) + + (fsr_flags if os.path.basename(src) == "vk_fsr4.cpp" else []), vk_lib_deps if lib else vdeps + (logo_text if os.path.basename(src) == "vk_menu.cpp" else b""))) if NGX_SDK and not LINUX and toolchain.mingw(): # (llvm-mingw: NVIDIA's static library was built with Microsoft's compiler for its static runtime (/MT). What it asks diff --git a/nx2/make_game.py b/nx2/make_game.py index b0b7ec8c2..65d39dbeb 100644 --- a/nx2/make_game.py +++ b/nx2/make_game.py @@ -932,6 +932,17 @@ def ngx_built(): return bool(sdk) and os.path.isfile(ngx_script().library(sdk, ngx_platform())) +FSR_MARK = ".hookshot_fsr" # (in a game folder built with AMD FSR 4: --update builds it with FSR again) + + +def fsr_wanted(a, out): + """AMD FSR 4 in this build (--fsr; or --update of a folder built with it, unless --no-fsr): nothing to download, AMD's + MIT headers are in third_party/fidelityfx and its DLLs are the player's (the readme)""" + if getattr(a, "no_fsr", False): + return False + return bool(getattr(a, "fsr", False)) or bool(a.update and os.path.isfile(os.path.join(out, FSR_MARK))) + + def place_ngx_runtime(out): """(NVIDIA DLSS) a game built with NVIDIA's SDK (HOOKSHOT_NGX_SDK, as nx2/build.py takes it) gets DLSS's runtime in its ngx folder, where gpu/vk/vk_dlss.cpp looks first: nvngx_dlss.dll on Windows, libnvidia-ngx-dlss.so. on Linux. Never @@ -966,6 +977,9 @@ def parse_args(): # (NVIDIA DLSS, off unless asked: NVIDIA's SDK into HOOKSHOT_NGX_SDK first, under NVIDIA's license. scripts/ngx_sdk.py) p.add_argument("--fetch-ngx-sdk", action="store_true", help="download NVIDIA's DLSS SDK into HOOKSHOT_NGX_SDK before the build") p.add_argument("--accept-nvidia-license", action="store_true", help="NVIDIA's DLSS SDK license, accepted (with --fetch-ngx-sdk)") + # (AMD FSR 4, off unless asked: nothing is downloaded. gpu/vk/vk_fsr4.cpp) + p.add_argument("--fsr", action="store_true", help="build with AMD FSR 4 (AMD's DLLs are not included: see the readme)") + p.add_argument("--no-fsr", action="store_true", help="with --update: build without AMD FSR 4 even if the folder had it") a = p.parse_args() a.default_work = p.get_default("work") # (main: the builder's own work folder, stop_stray_compilers) return a @@ -1001,6 +1015,14 @@ def main(a=None): drop_stale_objects(work) if getattr(a, "fetch_ngx_sdk", False): fetch_ngx_sdk(a) + # (AMD FSR 4: the build's switch for nx2/build.py, and the folder's mark for the next --update) + if fsr_wanted(a, out): + os.environ["HOOKSHOT_FSR"] = "1" + open(os.path.join(out, FSR_MARK), "w").close() + else: + os.environ.pop("HOOKSHOT_FSR", None) + if os.path.isfile(os.path.join(out, FSR_MARK)): + os.remove(os.path.join(out, FSR_MARK)) exefs = os.path.join(work, "exefs") romfs = os.path.join(out, "romfs") @@ -1086,7 +1108,7 @@ def main(a=None): if os.path.exists(os.path.join(work, "hookshot_pieces.prof")) and profile_readable(os.path.join(work, "hookshot_pieces.prof")): env["RX_PGO"] = os.path.join(work, "hookshot_pieces.prof") - if ngx_built(): # (NVIDIA DLSS's camera jitter: only in a build with it, nx2/oot_jitter.py) + if ngx_built() or os.environ.get("HOOKSHOT_FSR") == "1": # (the camera jitter DLSS and FSR need: only in a build with one, nx2/oot_jitter.py) env["OOT_DLSS_JITTER"] = "1" say("stage", "lift", "Translating the game to PC code") # (the same game, tools and settings as the lift before: its C is what this lift would write, and nothing recompiles from it. A diff --git a/nx2/oot_jitter.py b/nx2/oot_jitter.py index cace22c9b..d9140d531 100644 --- a/nx2/oot_jitter.py +++ b/nx2/oot_jitter.py @@ -1,6 +1,7 @@ -"""NVIDIA DLSS's camera jitter for Ocarina of Time 1.1.1: C added after a few instructions of the game (a64_lift.py Lifter.after). +"""The temporal upscalers' camera jitter for Ocarina of Time 1.1.1 (NVIDIA DLSS, AMD FSR 4): C added after a few instructions of +the game (a64_lift.py Lifter.after). -DLSS (gpu/vk/vk_dlss.cpp) resolves detail finer than a pixel from frames whose camera moved by a different fraction of a +DLSS and FSR (gpu/vk/vk_temporal.cpp) resolve detail finer than a pixel from frames whose camera moved by a different fraction of a pixel each time. The game's cameras do not move so: these sites give the scene's projection a sub-pixel offset each frame, in the GPU's copy only. The game fills each view's uniform buffers in one function (main+0x6bc520): for each view it loads the view's projection matrix (the view at x28, its projection's rows at +0x1a8) and writes it, the view-projection made from @@ -15,13 +16,13 @@ the game set it. host/rx_backend.c hos_dlss_jitter_clip picks the jitter and checks the projection is one of the picture's: perspective, of -16:9 or the window's ratio (nx2/oot_wide.py). It is none (the game as shipped) unless DLSS runs, and the renderer reads the +16:9 or the window's ratio (nx2/oot_wide.py). It is none (the game as shipped) unless DLSS or FSR runs, and the renderer reads the offset back from the camera's uniform buffer. Which view is the scene's, the renderer learns from its draws, too late for this: the other views of the picture's ratio (a reflection, a portrait) are drawn without DLSS and move by under a pixel of the scene's size, too little to see. -Installed only in a build with NVIDIA's DLSS SDK (make_game.py sets OOT_DLSS_JITTER=1 for the lift then): other builds have -none of this code. +Installed only in a build with NVIDIA's DLSS SDK or with AMD FSR 4 (make_game.py sets OOT_DLSS_JITTER=1 for the lift then): +other builds have none of this code. """ import os @@ -49,7 +50,8 @@ def _text(xreg, yreg): def wanted(): - """A build with NVIDIA DLSS (make_game.py: HOOKSHOT_NGX_SDK with its library): OOT_DLSS_JITTER=1, in the lift's key""" + """A build with NVIDIA DLSS (make_game.py: HOOKSHOT_NGX_SDK with its library) or AMD FSR 4 (--fsr): OOT_DLSS_JITTER=1, in + the lift's key""" return os.environ.get("OOT_DLSS_JITTER") == "1" diff --git a/readme.md b/readme.md index 50b9ccb67..6553bfdff 100644 --- a/readme.md +++ b/readme.md @@ -220,9 +220,9 @@ choices and starts the game again, so progress since your last save is lost. | | Frame pacing | Off, On | Off | | | NVIDIA Reflex | Off, On, On + Boost (NVIDIA GPUs with `VK_NV_low_latency2`) | Off | | | Output resolution | 1920x1080, 2048x1152, 2560x1440, 3840x2160 (after a restart) | 2048x1152 | -| | Upscaler | Game's own, NVIDIA DLSS, NVIDIA DLAA (only in builds with NVIDIA's DLSS SDK, below; DLSS and DLAA on RTX GPUs) | Game's own | +| | Upscaler | Game's own, NVIDIA DLSS, NVIDIA DLAA (only in builds with NVIDIA's DLSS SDK, below; DLSS and DLAA on RTX GPUs), AMD FSR 4, AMD FSR 4 Native AA (only in builds with FSR, below) | Game's own | | | DLSS model | Transformer (K), Transformer 2 (M), Transformer 2 (L), NVIDIA default (builds with DLSS; used by DLSS and DLAA) | Transformer (K) | -| | Render resolution | Auto (the game's own dynamic resolution), 100%, 75%, 67%, 50% (each with its size). With DLSS: Auto, Quality (67%), Balanced (58%), Performance (50%), Ultra Performance (33%). With DLAA: Native | Auto | +| | Render resolution | Auto (the game's own dynamic resolution), 100%, 75%, 67%, 50% (each with its size). With DLSS or FSR 4: Auto, Quality (67%), Balanced (58%), Performance (50%), Ultra Performance (33%). With DLAA or FSR 4 Native AA: Native | Auto | | | Window scaling filter | Off, Catmull-Rom, Lanczos | Off | | | Sharpness | 0 to 100%, in steps of 10 | 0% | | | Handheld profile | Off, On (after a restart) | Off | @@ -251,7 +251,7 @@ choices and starts the game again, so progress since your last save is lost. - **Quality preset** sets Render resolution, Window scaling filter, Sharpness, Frame generation, Frame pacing, Game speed, Handheld profile and the video memory limits for a card with that much memory. It leaves the Upscaler as you - chose it: with DLSS, the preset's resolution becomes DLSS's nearest mode. Changing most other rows yourself sets it + chose it: with DLSS or FSR 4, the preset's resolution becomes their nearest mode. Changing most other rows yourself sets it back to Custom. - **GPU device** matters on a PC with more than one graphics card (a laptop's integrated and dedicated GPUs, or a second card for Lossless Scaling). Auto prefers a dedicated one, and only cards with Vulkan 1.3 are listed. @@ -260,8 +260,9 @@ choices and starts the game again, so progress since your last save is lost. - **Frame generation** makes extra frames between the game's own. It adds a little input delay, and fast motion can show artifacts. **Frame pacing** shows frames at an even interval: less stutter, up to one frame of delay. - **The scaling rows**, in the picture's order: **Output resolution** is the size of the game's whole frame, HUD - included. The **Upscaler** fills it from the 3D: the game's own network, or NVIDIA DLSS or DLAA (the 3D at full size, - edges smoothed). Both look softer than the game's own: set **Sharpness** to around 80% with them. **DLSS model** + included. The **Upscaler** fills it from the 3D: the game's own network, NVIDIA DLSS or DLAA (the 3D at full size, + edges smoothed), or AMD FSR 4 or its Native AA (the same for AMD cards). DLSS and DLAA look softer than the game's own: + set **Sharpness** to around 80% with them. **DLSS model** chooses their network (below). **Render resolution** is the 3D's share of the output. The **Window scaling filter** then scales the frame to your window, and **Sharpness** sharpens it, by the picture's pixels: when the window is much larger than the picture (the Handheld @@ -528,6 +529,29 @@ of its own downloads (`C:\ProgramData\NVIDIA\NGX\models`) before it uses the gam +
+AMD FSR 4 (optional, AMD's DLLs not included) + +A game built with FSR offers **AMD FSR 4** and **AMD FSR 4 Native AA** in the Graphics tab's **Upscaler** row: AMD's +temporal upscaler in place of the game's own, on the same inputs as DLSS (the scene's colour and depth, the camera's and +the characters' motion, and the camera jitter). Native AA draws the 3D at full size and smooths its edges. Choose it in +the menu (`dlss=3` or `4` in `hookshot_options.ini`, `RX_VK_FSR=1` or `2` for one run, `RX_VK_FSR=0` to leave it out). + +- **Windows, the Hookshot Builder:** tick **AMD FSR 4** before you build. Nothing is downloaded. +- **The command line:** `make_game.py --fsr` (or `HOOKSHOT_FSR=1` for `nx2/build.py`). Rebuilding a folder built with + it (`--update`) keeps it; `--no-fsr` takes it out. +- **AMD's files:** Hookshot includes only AMD's MIT-licensed API headers (`third_party/fidelityfx`). Put + `amd_fidelityfx_loader_vk.dll` and `amd_fidelityfx_upscaler_vk.dll` from the + [FidelityFX SDK](https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK) 2.4 release (`Kits/FidelityFX/signedbin`) + beside the game's exe (`bin\hookshot.exe`), or in a folder named by `RX_VK_FSR_DIR`. Without them the Upscaler row + says so and offers the rest. On Linux, a Vulkan FSR 4 library (`amd_fidelityfx_upscaler_vk.so`, such as fsr4-vk's) + takes their place. + +The log's `vk: AMD FSR 4:` lines say which of AMD's upscalers it found for the card and which it uses (the newest FSR 4; +never an older FSR in its place), and why not when it did not start. Tested on an RX 6700 XT (RDNA 2). + +
+
Switches for testing (hookshot_env.txt) @@ -608,13 +632,17 @@ Hookshot exists thanks to the work of many people: - **[mstorsjo/llvm-mingw](https://github.com/mstorsjo/llvm-mingw)**: the bundled Windows compiler (no Visual Studio needed). - **NVIDIA DLSS**: builds made with NVIDIA's DLSS SDK (optional, downloaded by each build, never bundled) use NVIDIA DLSS Super Resolution, NVIDIA's technology. NVIDIA and DLSS are trademarks of NVIDIA Corporation. +- **AMD FSR 4**: builds made with FSR (optional; AMD's DLLs are never bundled) use AMD FidelityFX Super Resolution 4, AMD's + technology, through AMD's MIT-licensed FSR API headers. AMD, Radeon and FidelityFX are trademarks of Advanced Micro + Devices, Inc. - **[kinnay/zbic](https://github.com/kinnay/zbic)**, **Opus** and **SDL**: decompression, audio and controllers. - **[sostenesfreitas/ocarina-recomp](https://github.com/sostenesfreitas/ocarina-recomp)**: this README's layout, and its notes confirmed the mesh stage's control section. - **[MythosOfs/Orchid](https://github.com/MythosOfs/Orchid)**: the fog and exposure workaround for Ocarina of Time. - **Mesa (NAK)**: the reference for Ampere's instruction encoding. -- Everyone who sent fixes: SkippySteve (Linux and the AppImage, and many more), monsterlag, klimovich008, hckrman101 (the - exposure flicker and fog fixes, build speed, CI), shayderrr, gab9281, Wpaif, nicholasgower, ZurielAtr, Joey-Magnus, Zorkats, +- Everyone who sent fixes: SkippySteve (Linux and the AppImage, and many more), monsterlag, klimovich008, hckrman101 (NVIDIA + DLSS, DLAA and Reflex, the exposure flicker and fog fixes, build speed, CI), madebyjordan (AMD FSR 4.1), N3oTraX (FSR 4 on + Linux), shayderrr, gab9281, Wpaif, nicholasgower, ZurielAtr, Joey-Magnus, Zorkats, d3zessete, chrispository, vzeol, leonidasmark, Segmentree, ShyVortex, hiKareeem and i900gaming, among others. ## License diff --git a/tests/test_dlss_actor.py b/tests/test_dlss_actor.py index e0aa20bef..a5a08e9fc 100644 --- a/tests/test_dlss_actor.py +++ b/tests/test_dlss_actor.py @@ -42,9 +42,9 @@ def main(): got = fields((SHADERS / name).read_text(encoding="utf-8"), "uniform P {", "} pc;") if got != want: raise SystemExit("%s: its push constants %s are not dlss_actor::Push's %s" % (name, got, want)) - dlss = (ROOT / "gpu" / "vk" / "vk_dlss.cpp").read_text(encoding="utf-8") - if "using ActorPush = dlss_actor::Push;" not in dlss or "dlss_actor::index_start(" not in dlss: - raise SystemExit("vk_dlss.cpp does not use dlss_actor.h") + chain = (ROOT / "gpu" / "vk" / "vk_temporal.cpp").read_text(encoding="utf-8") + if "using ActorPush = dlss_actor::Push;" not in chain or "dlss_actor::index_start(" not in chain: + raise SystemExit("vk_temporal.cpp does not use dlss_actor.h") glslang = shutil.which("glslangValidator") with tempfile.TemporaryDirectory(prefix="dlss-actor-") as tmp: if glslang: diff --git a/tests/test_dlss_checks.py b/tests/test_dlss_checks.py index fa8f0432b..9a84eefed 100644 --- a/tests/test_dlss_checks.py +++ b/tests/test_dlss_checks.py @@ -18,11 +18,13 @@ def check_sources(): dlss = (ROOT / "gpu" / "vk" / "vk_dlss.cpp").read_text(encoding="utf-8") draw = (ROOT / "gpu" / "vk" / "vk_draw.cpp").read_text(encoding="utf-8") + # (the frame chain DLSS and FSR share: the scene camera and the actor motion) + chain = (ROOT / "gpu" / "vk" / "vk_temporal.cpp").read_text(encoding="utf-8") # (no fixed floor on the scene's size: it follows the output) - for name, src in (("vk_dlss.cpp", dlss), ("vk_draw.cpp", draw)): + for name, src in (("vk_dlss.cpp", dlss), ("vk_draw.cpp", draw), ("vk_temporal.cpp", chain)): assert not re.search(r"\b(width|height)\s*<\s*(640|360)\b", src), name + ": a fixed scene size floor" - assert "!dlss_scene_size(width, height)" in dlss, "dlss_note_scene_draw: the output's sizes" - assert "!dlss_scene_size(at.width, at.height)" in draw, "dlss_skin_check: the output's sizes" + assert "!temporal_scene_size(width, height)" in chain, "temporal_note_scene_draw: the output's sizes" + assert "!temporal_scene_size(at.width, at.height)" in draw, "skin_check: the output's sizes" # (the Linux wide(): decoded, not each byte widened) widened = [l.strip() for l in dlss.splitlines() if "std::wstring(s.begin(), s.end())" in l] assert widened == ["if (dlss_checks::ascii(s)) return std::wstring(s.begin(), s.end());"], \ diff --git a/tests/test_make_game_ngx.py b/tests/test_make_game_ngx.py index 035a2564b..60fcfe649 100644 --- a/tests/test_make_game_ngx.py +++ b/tests/test_make_game_ngx.py @@ -76,6 +76,24 @@ def test_jitter_only_in_a_build_with_the_sdk(self): with mock.patch.dict(os.environ, {}, clear=True): self.assertFalse(oot_jitter.wanted()) + def test_fsr_build_and_its_jitter(self): + """--fsr: HOOKSHOT_FSR=1 for nx2/build.py and the camera jitter in the lift; an --update of that folder keeps it (its + mark), --no-fsr takes it out""" + out = str(self.out) + mark = self.out / make_game.FSR_MARK + plain = SimpleNamespace(fsr=False, no_fsr=False, update=None) + self.assertFalse(make_game.fsr_wanted(plain, out)) + self.assertTrue(make_game.fsr_wanted(SimpleNamespace(fsr=True, no_fsr=False, update=None), out)) + update = SimpleNamespace(fsr=False, no_fsr=False, update=out) + self.assertFalse(make_game.fsr_wanted(update, out)) + mark.write_text("") + self.assertTrue(make_game.fsr_wanted(update, out)) + self.assertFalse(make_game.fsr_wanted(SimpleNamespace(fsr=False, no_fsr=True, update=out), out)) + self.assertFalse(make_game.fsr_wanted(plain, out)) # (a new build without --fsr: the mark is for --update only) + source = (ROOT / "nx2" / "make_game.py").read_text(encoding="utf-8") + if 'if ngx_built() or os.environ.get("HOOKSHOT_FSR") == "1":' not in source: + self.fail("make_game.py: the camera jitter (OOT_DLSS_JITTER) is not in a build with FSR") + def test_linux_runtime(self): self.sdk_files("linux") self.assertEqual(Path(self.place("linux")), self.out / "ngx" / "libnvidia-ngx-dlss.so.310.9.16") diff --git a/tests/test_rumble_menu.py b/tests/test_rumble_menu.py index d8f0670c8..92aa7f404 100644 --- a/tests/test_rumble_menu.py +++ b/tests/test_rumble_menu.py @@ -66,8 +66,10 @@ def one_line(text, head): /* (stand-ins for the rest of set_row, which the Rumble strength row never reaches: the Upscaler's rows, the restart offer) */ bool row_fixed(const int*) { return false; } int upscaler() { return 0; } +bool dlss_ok() { return false; } +bool fsr_ok() { return false; } namespace ur { -int step_upscaler(int, int, bool) { return 0; } +int step_upscaler(int, int, bool, bool) { return 0; } int step(int, int v, int dir) { return v + dir; } int snap(int, int v) { return v; } } diff --git a/tests/test_shader_stamp_cache.py b/tests/test_shader_stamp_cache.py index 4cac5f14b..4eb6a9906 100644 --- a/tests/test_shader_stamp_cache.py +++ b/tests/test_shader_stamp_cache.py @@ -48,6 +48,7 @@ def setUp(self): "gpu/vk/vk_state.h": "struct State {};\n", "gpu/vk/vk_api.h": "struct Api {};\n", "gpu/vk/vk_dlss.cpp": "int dlss;\n", + "gpu/vk/vk_fsr4.cpp": "int fsr;\n", "gpu/vk/vk_ngx_msvc_rt.cpp": "int msvc_rt;\n", "host/mingw_msvc_shims.c": "int shims;\n", "gpu/vk/eden/translate.cpp": "int translator;\n", @@ -70,7 +71,7 @@ def setUp(self): path.write_bytes(text.encode()) self.code = production_code(BUILD_SCRIPT) - def jobs(self, linux, version="fixture", release=False, ngx="", mingw="llvm-mingw"): + def jobs(self, linux, version="fixture", release=False, ngx="", mingw="llvm-mingw", fsr=False): namespace = { "os": os, "hashlib": hashlib, "GPU": str(self.root / "gpu"), "POSIX": str(self.root / "host/posix"), "HOST": str(self.root / "host"), @@ -85,6 +86,7 @@ def jobs(self, linux, version="fixture", release=False, ngx="", mingw="llvm-ming "stamp_def": '-DHOOKSHOT_VERSION="%s"' % version, "logo_text": b"", "gen_dir": str(self.root / "gen"), "NGX_SDK": ngx, # ("": no NVIDIA NGX SDK, vk_dlss.cpp built without DLSS, as by default) "NGX_MSVC_RT": "vk_ngx_msvc_rt.cpp", + "FSR": fsr, # (HOOKSHOT_FSR=1: vk_fsr4.cpp built with AMD FSR 4) } with mock.patch.dict(os.environ, {"RX_RELEASE": "1"} if release else {}, clear=True), warnings.catch_warnings(): # The production key/header helpers use short-lived open(...).read() expressions. @@ -178,6 +180,17 @@ def test_ngx_sdk_reaches_only_vk_dlss_and_the_mingw_runtime_shims(self): {"gpu/vk/vk_dlss.cpp"} | ({"gpu/vk/vk_ngx_msvc_rt.cpp"} if mingw and not linux else set())) self.assertEqual(self.changed(plain, {n: ngx[n] for n in plain}), {"gpu/vk/vk_dlss.cpp"}) + def test_fsr_reaches_only_vk_fsr4(self): + for linux in (False, True): + with self.subTest(linux=linux): + plain = self.jobs(linux) + self.assertFalse([n for n, j in plain.items() if "-DHOOKSHOT_FSR" in j["flags"]]) + with mock.patch("builtins.print"): + fsr = self.jobs(linux, fsr=True) + self.assertEqual(set(fsr), set(plain)) # (no other files: AMD's headers alone, nothing linked) + self.assertEqual({n for n, j in fsr.items() if "-DHOOKSHOT_FSR" in j["flags"]}, {"gpu/vk/vk_fsr4.cpp"}) + self.assertEqual(self.changed(plain, fsr), {"gpu/vk/vk_fsr4.cpp"}) + class NgxSdkTests(unittest.TestCase): """nx2/build.py's HOOKSHOT_NGX_SDK: which library each platform needs, and what the Windows link adds""" diff --git a/tests/test_status_line.py b/tests/test_status_line.py index 4a82d9fc7..333f7be20 100644 --- a/tests/test_status_line.py +++ b/tests/test_status_line.py @@ -20,15 +20,15 @@ def main(): replay = (vk / "vk_replay.cpp").read_text(encoding="utf-8") tick = replay[replay.index("void status_tick() {"):] tick = tick[:tick.index("\n}\n")] - for use in ("vram_usage_now(", "sl::crossed(", "sl::over_text(", "sl::vram_text(", "dlss_status(", "sl::kEvery"): + for use in ("vram_usage_now(", "sl::crossed(", "sl::over_text(", "sl::vram_text(", "temporal_status(", "sl::kEvery"): if use not in tick: raise SystemExit("status_tick does not use %s" % use) present = replay[replay.index("void do_present("):] if "status_tick();" not in present[:present.index("\n}\n")]: raise SystemExit("do_present does not call status_tick: no status line") - dlss = (vk / "vk_dlss.cpp").read_text(encoding="utf-8") - if "frames drawn; this one's jitter" in dlss: - raise SystemExit("vk_dlss.cpp still logs DLSS's own 600-frame line beside the status line") + for name in ("vk_dlss.cpp", "vk_fsr4.cpp", "vk_temporal.cpp"): + if "frames drawn; this one's jitter" in (vk / name).read_text(encoding="utf-8"): + raise SystemExit("%s still logs an upscaler's own 600-frame line beside the status line" % name) with tempfile.TemporaryDirectory(prefix="status-line-") as tmp: exe = Path(tmp) / "status-line" subprocess.run([args.cxx, "-std=c++20", "-Wall", "-Wextra", "-Werror", str(ROOT / "tests" / "test_status_line.cpp"), diff --git a/tests/test_upscaler_rows.cpp b/tests/test_upscaler_rows.cpp index a12a55328..d12e6602c 100644 --- a/tests/test_upscaler_rows.cpp +++ b/tests/test_upscaler_rows.cpp @@ -2,6 +2,7 @@ #include #include +#include #define CHECK(name, condition) do { if (!(condition)) { std::fprintf(stderr, "FAIL %s at line %d\n", name, __LINE__); return 1; } } while (0) @@ -9,6 +10,8 @@ int main() { namespace ur = mkwvk::upscaler_rows; using ur::DLAA; using ur::DLSS; + using ur::FSR; + using ur::FSR_AA; using ur::GAME; // (the values saved before keep their shares; the new ones are DLSS's ratios, each a twelfth of the output) CHECK("saved values keep their shares", ur::percent(0) == 0 && ur::percent(1) == 100 && ur::percent(2) == 75 && @@ -19,10 +22,15 @@ int main() { const int k = (int)(ur::percent(v) * 12 / 100.0 + 0.5); CHECK("each share is a twelfth", k * 100 / 12 == ur::percent(v) || (k * 100 + 11) / 12 == ur::percent(v)); } - CHECK("DLSS's mode names", !std::strcmp(ur::dlss_mode_name(3), "Quality") && !std::strcmp(ur::dlss_mode_name(5), "Balanced") && - !std::strcmp(ur::dlss_mode_name(4), "Performance") && - !std::strcmp(ur::dlss_mode_name(6), "Ultra Performance")); - CHECK("no mode name for Auto, 100% or 75%", !ur::dlss_mode_name(0) && !ur::dlss_mode_name(1) && !ur::dlss_mode_name(2)); + CHECK("DLSS's and FSR's mode names", !std::strcmp(ur::mode_name(3), "Quality") && !std::strcmp(ur::mode_name(5), "Balanced") && + !std::strcmp(ur::mode_name(4), "Performance") && + !std::strcmp(ur::mode_name(6), "Ultra Performance")); + CHECK("no mode name for Auto, 100% or 75%", !ur::mode_name(0) && !ur::mode_name(1) && !ur::mode_name(2)); + // which engine each Upscaler value is, and which draw the 3D at full scale + CHECK("engines", ur::is_dlss(DLSS) && ur::is_dlss(DLAA) && !ur::is_dlss(FSR) && !ur::is_dlss(GAME) && ur::is_fsr(FSR) && + ur::is_fsr(FSR_AA) && !ur::is_fsr(DLAA) && !ur::is_fsr(GAME)); + CHECK("full scale", ur::full_scale(DLAA) && ur::full_scale(FSR_AA) && !ur::full_scale(DLSS) && !ur::full_scale(FSR) && + !ur::full_scale(GAME)); // the game's own: Auto, 100, 75, 67, 50, as before CHECK("game: Right", ur::step(GAME, 0, 1) == 1 && ur::step(GAME, 1, 1) == 2 && ur::step(GAME, 3, 1) == 4 && ur::step(GAME, 4, 1) == 0); @@ -37,6 +45,11 @@ int main() { CHECK("game from 58%", ur::step(GAME, 5, 1) == 4 && ur::step(GAME, 5, -1) == 3); CHECK("game from 33%", ur::step(GAME, 6, 1) == 0 && ur::step(GAME, 6, -1) == 4); CHECK("DLAA does not step", ur::step(DLAA, 3, 1) == 3 && ur::step(DLAA, 0, -1) == 0); + // FSR 4: DLSS's steps (FSR's own ratios are the same twelfths); its native AA does not step + for (int v = 0; v < ur::kValues; v++) + for (int dir : {1, -1}) + CHECK("fsr steps as dlss", ur::step(FSR, v, dir) == ur::step(DLSS, v, dir) && ur::snap(FSR, v) == ur::snap(DLSS, v)); + CHECK("FSR's native AA does not step", ur::step(FSR_AA, 3, 1) == 3 && ur::snap(FSR_AA, 5) == 5); // another upscaler: the nearest step (the larger on a tie), Auto stays Auto CHECK("snap to DLSS", ur::snap(DLSS, 0) == 0 && ur::snap(DLSS, 1) == 3 && ur::snap(DLSS, 2) == 3 && ur::snap(DLSS, 3) == 3 && @@ -45,17 +58,25 @@ int main() { CHECK("DLAA keeps the value", ur::snap(DLAA, 5) == 5 && ur::snap(DLAA, 1) == 1); CHECK("a value past the row snaps to Auto", ur::snap(GAME, 9) == 0 && ur::snap(DLSS, -3) == 0); - // the Upscaler row: all three where DLSS runs, the game's own alone elsewhere - CHECK("upscaler with DLSS", ur::step_upscaler(0, 1, true) == 1 && ur::step_upscaler(1, 1, true) == 2 && - ur::step_upscaler(2, 1, true) == 0 && ur::step_upscaler(0, -1, true) == 2); - CHECK("upscaler without DLSS", ur::step_upscaler(0, 1, false) == 0 && ur::step_upscaler(2, -1, false) == 0); - CHECK("upscaler from a bad value", ur::step_upscaler(7, 1, true) == 1); + // the Upscaler row: each engine's two where it runs, the game's own always + CHECK("upscaler with DLSS", ur::step_upscaler(0, 1, true, false) == 1 && ur::step_upscaler(1, 1, true, false) == 2 && + ur::step_upscaler(2, 1, true, false) == 0 && ur::step_upscaler(0, -1, true, false) == 2); + CHECK("upscaler with FSR", ur::step_upscaler(0, 1, false, true) == FSR && ur::step_upscaler(FSR, 1, false, true) == FSR_AA && + ur::step_upscaler(FSR_AA, 1, false, true) == 0 && ur::step_upscaler(0, -1, false, true) == FSR_AA); + CHECK("upscaler with both", ur::step_upscaler(DLAA, 1, true, true) == FSR && ur::step_upscaler(FSR, -1, true, true) == DLAA && + ur::step_upscaler(FSR_AA, 1, true, true) == 0 && ur::step_upscaler(0, -1, true, true) == FSR_AA); + CHECK("upscaler with neither", ur::step_upscaler(0, 1, false, false) == 0 && ur::step_upscaler(2, -1, false, false) == 0); + CHECK("upscaler from a bad value", ur::step_upscaler(7, 1, true, false) == 1 && ur::step_upscaler(-2, -1, false, true) == FSR_AA); + // a saved engine that cannot run here: the next one that can (FSR saved, only DLSS here) + CHECK("upscaler from one that cannot run", ur::step_upscaler(FSR, 1, true, false) == 0 && ur::step_upscaler(FSR, -1, true, false) == DLAA); // the Upscaler row only in a build with DLSS; the rows with nothing to choose CHECK("upscaler row shown", ur::upscaler_shown(true) && !ur::upscaler_shown(false)); - CHECK("upscaler fixed where DLSS cannot run", ur::upscaler_fixed(false) && !ur::upscaler_fixed(true)); - CHECK("render resolution fixed with DLAA only", ur::resolution_fixed(DLAA) && !ur::resolution_fixed(DLSS) && - !ur::resolution_fixed(GAME)); + CHECK("upscaler fixed where neither engine runs", ur::upscaler_fixed(false, false) && !ur::upscaler_fixed(true, false) && + !ur::upscaler_fixed(false, true)); + CHECK("render resolution fixed at full scale only", ur::resolution_fixed(DLAA) && ur::resolution_fixed(FSR_AA) && + !ur::resolution_fixed(DLSS) && !ur::resolution_fixed(FSR) && + !ur::resolution_fixed(GAME)); // the Window scaling filter: needed only when the window is not the picture's size (unknown: needed) CHECK("filter not needed at the output's size", !ur::filter_needed(2560, 1440, 2560, 1440)); CHECK("filter needed otherwise", ur::filter_needed(2048, 1152, 2560, 1440) && ur::filter_needed(2560, 1440, 2560, 1080) && @@ -74,5 +95,6 @@ int main() { CHECK("model row shown with DLSS built", ur::model_shown(true) && !ur::model_shown(false)); CHECK("model row fixed with the game's own or the switch", ur::model_fixed(GAME, false) && !ur::model_fixed(DLSS, false) && !ur::model_fixed(DLAA, false) && ur::model_fixed(DLSS, true)); + CHECK("model row fixed with FSR", ur::model_fixed(FSR, false) && ur::model_fixed(FSR_AA, false)); std::puts("upscaler rows: all checks passed"); } diff --git a/tests/test_upscaler_rows.py b/tests/test_upscaler_rows.py index 78ceff627..608029d77 100644 --- a/tests/test_upscaler_rows.py +++ b/tests/test_upscaler_rows.py @@ -1,6 +1,6 @@ -"""The Insert menu's scaling rows: the Upscaler (opts.dlss) and the Render resolution's steps for it (gpu/vk/upscaler_rows.h), -their order on the Graphics tab, options files from before still loading, and their help (why DLSS is not offered, the -Sharpness DLSS wants). No game or GPU required. +"""The Insert menu's scaling rows: the Upscaler (opts.upscaler: the game's own, NVIDIA DLSS and DLAA, AMD FSR 4 and its native +AA) and the Render resolution's steps for it (gpu/vk/upscaler_rows.h), their order on the Graphics tab, options files from +before still loading, and their help (why DLSS or FSR is not offered, the Sharpness DLSS wants). No game or GPU required. python -I -B tests/test_upscaler_rows.py [--cxx g++] """ @@ -29,7 +29,7 @@ def main(): menu = (ROOT / "gpu" / "vk" / "vk_menu.cpp").read_text(encoding="utf-8") # the picture's way, top to bottom; Reflex with the frame rows order = ['"Frame generation", &opts.framegen', '"Frame pacing", &opts.pace', '"NVIDIA Reflex", &opts.reflex', - '"Output resolution", &opts.output_res', '"Upscaler", &opts.dlss', '"DLSS model", &opts.dlss_model', + '"Output resolution", &opts.output_res', '"Upscaler", &opts.upscaler', '"DLSS model", &opts.dlss_model', '"Render resolution", &opts.resolution', '"Window scaling filter", &opts.upscale', '"Sharpness", &opts.sharpness'] at = [menu.find(row) for row in order] @@ -37,17 +37,17 @@ def main(): raise SystemExit("vk_menu.cpp: a scaling row is missing: " + order[at.index(-1)]) if at != sorted(at): raise SystemExit("vk_menu.cpp: the scaling rows are not in the picture's order") - if '"NVIDIA DLSS", &opts.dlss' in menu: - raise SystemExit("vk_menu.cpp: DLSS still has a row of its own") + if '"NVIDIA DLSS", &opts.' in menu or '"AMD FSR 4", &opts.' in menu: + raise SystemExit("vk_menu.cpp: DLSS or FSR has a row of its own beside the Upscaler") # saved options: the renamed rows keep their keys (data, so read from the source) key = function(menu, "const char* ini_key(const Row& r) {") - for value, name in (("opts.resolution", "resolution"), ("opts.upscale", "upscaling"), ("opts.dlss", "dlss")): + for value, name in (("opts.resolution", "resolution"), ("opts.upscale", "upscaling"), ("opts.upscaler", "dlss")): if not re.search(r"&%s\)\s*return\s*\"%s\"" % (re.escape(value), name), key): raise SystemExit("ini_key: %s is not saved as %s (an options file from before would lose it)" % (value, name)) # the menu's rules are upscaler_rows.h's, checked by behaviour in test_upscaler_rows.cpp: here only that the menu uses them uses = { "row_fixed": ("ur::upscaler_fixed(", "ur::resolution_fixed(", "reflex_available()", "ur::model_fixed("), - "row_shown": ("ur::upscaler_shown(dlss_built())", "ur::model_shown(dlss_built())"), + "row_shown": ("ur::upscaler_shown(dlss_built() || fsr4_built())", "ur::model_shown(dlss_built())"), "set_row": ("ur::step_upscaler(", "ur::step(", "ur::snap(", "row_fixed("), } for name, calls in uses.items(): @@ -59,9 +59,9 @@ def main(): # DLSS's values ask for more Sharpness (it looks softer than the game's own) upscaler = function(menu, "const char* hn_upscaler(int v, int variant) {") for why in ("not in this build", "RX_VK_DLSS=0", "NVIDIA RTX GPU", "newer NVIDIA driver", "runtime was not found", - "(int)dlss_why()"): + "(int)dlss_why()", "RX_VK_FSR=0", "AMD's files were not found", "no FSR 4 for this GPU", "(int)fsr4_why()"): if why not in upscaler: - raise SystemExit("hn_upscaler: the help no longer says why DLSS is not offered (%s)" % why) + raise SystemExit("hn_upscaler: the help no longer says why DLSS or FSR is not offered (%s)" % why) if upscaler.count("Sharpness to") < 2: raise SystemExit("hn_upscaler: NVIDIA DLSS and DLAA do not say to set Sharpness to around 80%") resolution = function(menu, "const char* hn_resolution(int v, int variant) {") @@ -70,7 +70,7 @@ def main(): measure = function(menu, "int measure_help(int s) {") if "r.hint(v, k)" not in measure: raise SystemExit("measure_help leaves the values' lines out: the help's room would jump as a value changes") - for row, hint in (('"Upscaler", &opts.dlss', "hn_upscaler"), ('"DLSS model", &opts.dlss_model', "hn_dlss_model"), ('"Render resolution", &opts.resolution', "hn_resolution"), + for row, hint in (('"Upscaler", &opts.upscaler', "hn_upscaler"), ('"DLSS model", &opts.dlss_model', "hn_dlss_model"), ('"Render resolution", &opts.resolution', "hn_resolution"), ('"NVIDIA Reflex", &opts.reflex', "hn_reflex"), ('"Window scaling filter", &opts.upscale', "hn_filter")): entry = menu[menu.index(row):] if hint not in entry[:entry.index("},")]: @@ -87,13 +87,16 @@ def main(): if "if (src) {" not in present[:note] or present.rindex("if (src) {", 0, note) < present.rindex("\n }", 0, note): raise SystemExit("present_image: the start-up screen's frames (no src) count as the game's picture for the filter row") dlss = (ROOT / "gpu" / "vk" / "vk_dlss.cpp").read_text(encoding="utf-8") - feature = function(dlss, "bool make_feature(") + feature = function(dlss, "int prepare(") if "wanted_preset()" not in feature or "s.feature_preset == preset" not in feature: raise SystemExit("vk_dlss.cpp: DLSS's feature is not made again for another DLSS model") if "upscaler_rows::dlss_preset(opts.dlss_model" not in function(dlss, "unsigned wanted_preset() {"): raise SystemExit("vk_dlss.cpp: the DLSS model row (and RX_VK_DLSS_PRESET over it) is not the preset asked for") - if "dlss_why() != DLSS_OK" not in function(dlss, "int dlss_mode() {"): - raise SystemExit("dlss_mode asks for DLSS where it cannot run (DLAA would hold the 3D at full scale for nothing)") + temporal = (ROOT / "gpu" / "vk" / "vk_temporal.cpp").read_text(encoding="utf-8") + mode = function(temporal, "int temporal_mode() {") + if "dlss_why() != DLSS_OK" not in mode or "fsr4_why() != FSR_OK" not in mode: + raise SystemExit("temporal_mode asks for an engine where it cannot run (DLAA or FSR's native AA would hold the 3D at " + "full scale for nothing)") with tempfile.TemporaryDirectory(prefix="upscaler-rows-") as tmp: exe = Path(tmp) / "upscaler-rows" subprocess.run([args.cxx, "-std=c++20", "-Wall", "-Wextra", "-Werror", diff --git a/third_party/fidelityfx/LICENSE.md b/third_party/fidelityfx/LICENSE.md new file mode 100644 index 000000000..aef7adddb --- /dev/null +++ b/third_party/fidelityfx/LICENSE.md @@ -0,0 +1,992 @@ +The following license applies to all files except as noted below. + +Copyright (C)  Advanced Micro Devices, Inc. All rights reserved. + +REDISTRIBUTION: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to install, reproduce, copy and distribute copies of the Software, in binary form only, and to permit persons to whom the Software is provided to do the same, provided that the following conditions are met: + +No reverse engineering, decompilation, or disassembly of this Software is permitted. + +Redistributions must reproduce the above copyright notice, this permission notice, and the following disclaimers and notices in the Software documentation and/or other materials provided with the Software. + +DISCLAIMER: THE USE OF THE SOFTWARE IS AT YOUR SOLE RISK.  THE SOFTWARE +IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF ANY KIND AND COPYRIGHT +HOLDER AND ITS LICENSORS EXPRESSLY DISCLAIM ALL WARRANTIES, EXPRESS AND +IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. +COPYRIGHT HOLDER AND ITS LICENSORS DO NOT WARRANT THAT THE SOFTWARE WILL +MEET YOUR REQUIREMENTS, OR THAT THE OPERATION OF THE SOFTWARE WILL BE +UNINTERRUPTED OR ERROR-FREE.  THE ENTIRE RISK ASSOCIATED WITH THE USE OF +THE SOFTWARE IS ASSUMED BY YOU.  FURTHERMORE, COPYRIGHT HOLDER AND ITS +LICENSORS DO NOT WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE +OR THE RESULTS OF THE USE OF THE SOFTWARE IN TERMS OF ITS CORRECTNESS, +ACCURACY, RELIABILITY, CURRENTNESS, OR OTHERWISE. + +DISCLAIMER: UNDER NO CIRCUMSTANCES INCLUDING NEGLIGENCE, SHALL COPYRIGHT HOLDER AND ITS LICENSORS OR ITS DIRECTORS, OFFICERS, EMPLOYEES OR AGENTS ("AUTHORIZED REPRESENTATIVES") BE LIABLE FOR ANY INCIDENTAL, INDIRECT, +SPECIAL OR CONSEQUENTIAL DAMAGES (INCLUDING DAMAGES FOR LOSS OF BUSINESS +PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, AND THE +LIKE) ARISING OUT OF THE USE, MISUSE OR INABILITY TO USE THE SOFTWARE, +BREACH OR DEFAULT, INCLUDING THOSE ARISING FROM INFRINGEMENT OR ALLEGED +INFRINGEMENT OF ANY PATENT, TRADEMARK, COPYRIGHT OR OTHER INTELLECTUAL +PROPERTY RIGHT EVEN IF COPYRIGHT HOLDER AND ITS AUTHORIZED +REPRESENTATIVES HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.  IN +NO EVENT SHALL COPYRIGHT HOLDER OR ITS AUTHORIZED REPRESENTATIVES TOTAL +LIABILITY FOR ALL DAMAGES, LOSSES, AND CAUSES OF ACTION (WHETHER IN +CONTRACT, TORT (INCLUDING NEGLIGENCE) OR OTHERWISE) EXCEED THE AMOUNT OF +US$10. + +These source files + +- `3rdpartynotice.md` +- `ClearMediaCache.bat` +- `docs\getting-started\building-samples.md` +- `docs\getting-started\index.md` +- `docs\getting-started\media\component-dependencies-dark.png` +- `docs\getting-started\media\component-dependencies.png` +- `docs\getting-started\media\config_init.jpg` +- `docs\getting-started\media\lighting-ui.jpg` +- `docs\getting-started\media\limiter-ui.jpg` +- `docs\getting-started\media\magnifier-ui.jpg` +- `docs\getting-started\media\particle-ui.jpg` +- `docs\getting-started\media\postprocessing-ui.jpg` +- `docs\getting-started\media\shadowraster-ui.jpg` +- `docs\getting-started\media\skydome-ui.jpg` +- `docs\getting-started\media\taa-ui.jpg` +- `docs\getting-started\running-samples.md` +- `docs\getting-started\sdk-structure.md` +- `docs\license.md` +- `docs\media\AMD_FidelityFX_Lockup_Black.png` +- `docs\media\AMD_FidelityFX_SDK_Colored.jpg` +- `docs\media\fidelityfxsdk-logo-rescaled.png` +- `docs\samples\denoiser.md` +- `docs\samples\index.md` +- `docs\samples\media\blur\blur.jpg` +- `docs\samples\media\blur\blur.png` +- `docs\samples\media\brixelizergi\sample.png` +- `docs\samples\media\contrast-adaptive-sharpening\cas-sample.png` +- `docs\samples\media\denoiser\fsr-ray-regeneration-sample.png` +- `docs\samples\media\hybrid-reflections\hybrid-reflections.jpg` +- `docs\samples\media\hybrid-reflections\hybrid-reflections.png` +- `docs\samples\media\hybrid-shadows\hybrid-shadows-sample.jpg` +- `docs\samples\media\hybrid-shadows\hybrid-shadows-sample.png` +- `docs\samples\media\lens\lens-sample_resized.jpg` +- `docs\samples\media\lens\lens-sample.jpg` +- `docs\samples\media\luma-preserving-mapper\LPM.jpg` +- `docs\samples\media\luma-preserving-mapper\LPM.png` +- `docs\samples\media\luma-preserving-mapper\NoLPM.jpg` +- `docs\samples\media\luma-preserving-mapper\NoLPM.png` +- `docs\samples\media\parallel-sort\parallel-sort_resized.jpg` +- `docs\samples\media\parallel-sort\parallel-sort.jpg` +- `docs\samples\media\radiance-caching\nrc-preview-sample.png` +- `docs\samples\media\single-pass-downsampler\spd-sample.jpg` +- `docs\samples\media\stochastic-screen-space-reflections\sssr-sample_resized.jpg` +- `docs\samples\media\stochastic-screen-space-reflections\sssr-sample.jpg` +- `docs\samples\media\super-resolution\frame-interpolation-debug-overlay.svg` +- `docs\samples\media\super-resolution\fsr3-sample_resized.jpg` +- `docs\samples\media\super-resolution\fsr3-sample.jpg` +- `docs\samples\media\super-resolution\upscaler-debug-overlay.svg` +- `docs\samples\media\variable-shading\variable-shading.jpg` +- `docs\samples\media\variable-shading\variable-shading.png` +- `docs\samples\radiance-cache.md` +- `docs\samples\super-resolution.md` +- `docs\sdk.css` +- `docs\tools\index.md` +- `docs\tools\media-delivery.md` +- `Kits\Cauldron2\dx12\Cauldron_vs2022.vcxproj` +- `Kits\Cauldron2\dx12\Cauldron_vs2022.vcxproj.filters` +- `Kits\Cauldron2\dx12\vcpkg.json` +- `Kits\Cauldron2\framework\config\cauldronconfig.json` +- `Kits\Cauldron2\framework\core\backend_interface.h` +- `Kits\Cauldron2\framework\core\component.cpp` +- `Kits\Cauldron2\framework\core\component.h` +- `Kits\Cauldron2\framework\core\components\animationcomponent.cpp` +- `Kits\Cauldron2\framework\core\components\animationcomponent.h` +- `Kits\Cauldron2\framework\core\components\cameracomponent.cpp` +- `Kits\Cauldron2\framework\core\components\cameracomponent.h` +- `Kits\Cauldron2\framework\core\components\lightcomponent.cpp` +- `Kits\Cauldron2\framework\core\components\lightcomponent.h` +- `Kits\Cauldron2\framework\core\components\meshcomponent.cpp` +- `Kits\Cauldron2\framework\core\components\meshcomponent.h` +- `Kits\Cauldron2\framework\core\components\particlespawnercomponent.cpp` +- `Kits\Cauldron2\framework\core\components\particlespawnercomponent.h` +- `Kits\Cauldron2\framework\core\contentloader.h` +- `Kits\Cauldron2\framework\core\contentmanager.cpp` +- `Kits\Cauldron2\framework\core\contentmanager.h` +- `Kits\Cauldron2\framework\core\entity.cpp` +- `Kits\Cauldron2\framework\core\entity.h` +- `Kits\Cauldron2\framework\core\framework.cpp` +- `Kits\Cauldron2\framework\core\framework.h` +- `Kits\Cauldron2\framework\core\inputmanager.cpp` +- `Kits\Cauldron2\framework\core\inputmanager.h` +- `Kits\Cauldron2\framework\core\loaders\gltfloader.cpp` +- `Kits\Cauldron2\framework\core\loaders\gltfloader.h` +- `Kits\Cauldron2\framework\core\loaders\particleloader.cpp` +- `Kits\Cauldron2\framework\core\loaders\particleloader.h` +- `Kits\Cauldron2\framework\core\loaders\textureloader.cpp` +- `Kits\Cauldron2\framework\core\loaders\textureloader.h` +- `Kits\Cauldron2\framework\core\scene.cpp` +- `Kits\Cauldron2\framework\core\scene.h` +- `Kits\Cauldron2\framework\core\taskmanager.cpp` +- `Kits\Cauldron2\framework\core\taskmanager.h` +- `Kits\Cauldron2\framework\core\uibackend.cpp` +- `Kits\Cauldron2\framework\core\uibackend.h` +- `Kits\Cauldron2\framework\core\uimanager.cpp` +- `Kits\Cauldron2\framework\core\uimanager.h` +- `Kits\Cauldron2\framework\core\win\framework_win.cpp` +- `Kits\Cauldron2\framework\core\win\framework_win.h` +- `Kits\Cauldron2\framework\core\win\inputmanager_win.cpp` +- `Kits\Cauldron2\framework\core\win\inputmanager_win.h` +- `Kits\Cauldron2\framework\core\win\uibackend_win.cpp` +- `Kits\Cauldron2\framework\core\win\uibackend_win.h` +- `Kits\Cauldron2\framework\misc\assert.h` +- `Kits\Cauldron2\framework\misc\corecounts.cpp` +- `Kits\Cauldron2\framework\misc\corecounts.h` +- `Kits\Cauldron2\framework\misc\fileio.cpp` +- `Kits\Cauldron2\framework\misc\fileio.h` +- `Kits\Cauldron2\framework\misc\helpers.h` +- `Kits\Cauldron2\framework\misc\log.cpp` +- `Kits\Cauldron2\framework\misc\log.h` +- `Kits\Cauldron2\framework\misc\math.cpp` +- `Kits\Cauldron2\framework\misc\math.h` +- `Kits\Cauldron2\framework\misc\sync.h` +- `Kits\Cauldron2\framework\misc\threadsafe_queue.h` +- `Kits\Cauldron2\framework\misc\threadsafe_ringbuffer.h` +- `Kits\Cauldron2\framework\render\animation.cpp` +- `Kits\Cauldron2\framework\render\animation.h` +- `Kits\Cauldron2\framework\render\backend_shader_reloader\backend_shader_reloader_common.cpp` +- `Kits\Cauldron2\framework\render\backend_shader_reloader\backend_shader_reloader_common.h` +- `Kits\Cauldron2\framework\render\backend_shader_reloader\backend_shader_reloader.cpp` +- `Kits\Cauldron2\framework\render\backend_shader_reloader\backend_shader_reloader.h` +- `Kits\Cauldron2\framework\render\backend_shader_reloader\CMakeLists.txt` +- `Kits\Cauldron2\framework\render\backend_shader_reloader\command_execution.cpp` +- `Kits\Cauldron2\framework\render\backend_shader_reloader\command_execution.h` +- `Kits\Cauldron2\framework\render\backend_shader_reloader\native_backend_shader_reloader.cpp` +- `Kits\Cauldron2\framework\render\backend_shader_reloader\native_backend_shader_reloader.h` +- `Kits\Cauldron2\framework\render\buffer.cpp` +- `Kits\Cauldron2\framework\render\buffer.h` +- `Kits\Cauldron2\framework\render\color_conversion.cpp` +- `Kits\Cauldron2\framework\render\color_conversion.h` +- `Kits\Cauldron2\framework\render\commandlist.cpp` +- `Kits\Cauldron2\framework\render\commandlist.h` +- `Kits\Cauldron2\framework\render\copyresource.cpp` +- `Kits\Cauldron2\framework\render\copyresource.h` +- `Kits\Cauldron2\framework\render\device.cpp` +- `Kits\Cauldron2\framework\render\device.h` +- `Kits\Cauldron2\framework\render\dx12\buffer_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\buffer_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\commandlist_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\commandlist_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\copyresource_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\copyresource_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\defines_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\device_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\device_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\dynamicbufferpool_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\dynamicbufferpool_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\gpuresource_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\gpuresource_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\indirectworkload_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\indirectworkload_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\parameterset_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\parameterset_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\pipelinedesc_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\pipelinedesc_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\pipelineobject_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\pipelineobject_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\profiler_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\profiler_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\resourceview_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\resourceview_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\resourceviewallocator_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\resourceviewallocator_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\rootsignature_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\rootsignature_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\rootsignaturedesc_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\rootsignaturedesc_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\rtresources_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\rtresources_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\sampler_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\sampler_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\swapchain_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\swapchain_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\texture_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\texture_dx12.h` +- `Kits\Cauldron2\framework\render\dx12\uploadheap_dx12.cpp` +- `Kits\Cauldron2\framework\render\dx12\uploadheap_dx12.h` +- `Kits\Cauldron2\framework\render\dynamicbufferpool.cpp` +- `Kits\Cauldron2\framework\render\dynamicbufferpool.h` +- `Kits\Cauldron2\framework\render\dynamicresourcepool.cpp` +- `Kits\Cauldron2\framework\render\dynamicresourcepool.h` +- `Kits\Cauldron2\framework\render\gpuresource.cpp` +- `Kits\Cauldron2\framework\render\gpuresource.h` +- `Kits\Cauldron2\framework\render\indirectworkload.h` +- `Kits\Cauldron2\framework\render\material.cpp` +- `Kits\Cauldron2\framework\render\material.h` +- `Kits\Cauldron2\framework\render\mesh.cpp` +- `Kits\Cauldron2\framework\render\mesh.h` +- `Kits\Cauldron2\framework\render\parameterset.cpp` +- `Kits\Cauldron2\framework\render\parameterset.h` +- `Kits\Cauldron2\framework\render\particle.cpp` +- `Kits\Cauldron2\framework\render\particle.h` +- `Kits\Cauldron2\framework\render\pipelinedesc.cpp` +- `Kits\Cauldron2\framework\render\pipelinedesc.h` +- `Kits\Cauldron2\framework\render\pipelineobject.h` +- `Kits\Cauldron2\framework\render\profiler.cpp` +- `Kits\Cauldron2\framework\render\profiler.h` +- `Kits\Cauldron2\framework\render\rasterview.cpp` +- `Kits\Cauldron2\framework\render\rasterview.h` +- `Kits\Cauldron2\framework\render\renderdefines.h` +- `Kits\Cauldron2\framework\render\rendermodule.cpp` +- `Kits\Cauldron2\framework\render\rendermodule.h` +- `Kits\Cauldron2\framework\render\rendermodules\fpslimiter\fpslimiterrendermodule.cpp` +- `Kits\Cauldron2\framework\render\rendermodules\fpslimiter\fpslimiterrendermodule.h` +- `Kits\Cauldron2\framework\render\rendermodules\raytracing\raytracingrendermodule.cpp` +- `Kits\Cauldron2\framework\render\rendermodules\raytracing\raytracingrendermodule.h` +- `Kits\Cauldron2\framework\render\rendermodules\rsr\runtimeshaderrecompilerrendermodule.cpp` +- `Kits\Cauldron2\framework\render\rendermodules\rsr\runtimeshaderrecompilerrendermodule.h` +- `Kits\Cauldron2\framework\render\rendermodules\skinning\skinningrendermodule.cpp` +- `Kits\Cauldron2\framework\render\rendermodules\skinning\skinningrendermodule.h` +- `Kits\Cauldron2\framework\render\rendermodules\swapchain\swapchainrendermodule.cpp` +- `Kits\Cauldron2\framework\render\rendermodules\swapchain\swapchainrendermodule.h` +- `Kits\Cauldron2\framework\render\rendermodules\tonemapping\tonemappingrendermodule.cpp` +- `Kits\Cauldron2\framework\render\rendermodules\tonemapping\tonemappingrendermodule.h` +- `Kits\Cauldron2\framework\render\rendermodules\ui\uirendermodule.cpp` +- `Kits\Cauldron2\framework\render\rendermodules\ui\uirendermodule.h` +- `Kits\Cauldron2\framework\render\resourceresizedlistener.cpp` +- `Kits\Cauldron2\framework\render\resourceresizedlistener.h` +- `Kits\Cauldron2\framework\render\resourceview.cpp` +- `Kits\Cauldron2\framework\render\resourceview.h` +- `Kits\Cauldron2\framework\render\resourceviewallocator.cpp` +- `Kits\Cauldron2\framework\render\resourceviewallocator.h` +- `Kits\Cauldron2\framework\render\rootsignature.h` +- `Kits\Cauldron2\framework\render\rootsignaturedesc.cpp` +- `Kits\Cauldron2\framework\render\rootsignaturedesc.h` +- `Kits\Cauldron2\framework\render\rtresources.h` +- `Kits\Cauldron2\framework\render\sampler.h` +- `Kits\Cauldron2\framework\render\shaderbuilder.h` +- `Kits\Cauldron2\framework\render\shaderbuilderhelper.cpp` +- `Kits\Cauldron2\framework\render\shaderbuilderhelper.h` +- `Kits\Cauldron2\framework\render\shadowmapresourcepool.cpp` +- `Kits\Cauldron2\framework\render\shadowmapresourcepool.h` +- `Kits\Cauldron2\framework\render\swapchain.cpp` +- `Kits\Cauldron2\framework\render\swapchain.h` +- `Kits\Cauldron2\framework\render\texture.cpp` +- `Kits\Cauldron2\framework\render\texture.h` +- `Kits\Cauldron2\framework\render\uploadheap.cpp` +- `Kits\Cauldron2\framework\render\uploadheap.h` +- `Kits\Cauldron2\framework\render\vk\buffer_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\buffer_vk.h` +- `Kits\Cauldron2\framework\render\vk\commandlist_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\commandlist_vk.h` +- `Kits\Cauldron2\framework\render\vk\copyresource_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\copyresource_vk.h` +- `Kits\Cauldron2\framework\render\vk\device_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\device_vk.h` +- `Kits\Cauldron2\framework\render\vk\dynamicbufferpool_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\dynamicbufferpool_vk.h` +- `Kits\Cauldron2\framework\render\vk\gpuresource_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\gpuresource_vk.h` +- `Kits\Cauldron2\framework\render\vk\helpers.cpp` +- `Kits\Cauldron2\framework\render\vk\helpers.h` +- `Kits\Cauldron2\framework\render\vk\indirectworkload_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\indirectworkload_vk.h` +- `Kits\Cauldron2\framework\render\vk\parameterset_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\parameterset_vk.h` +- `Kits\Cauldron2\framework\render\vk\pipelinedesc_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\pipelinedesc_vk.h` +- `Kits\Cauldron2\framework\render\vk\pipelineobject_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\pipelineobject_vk.h` +- `Kits\Cauldron2\framework\render\vk\profiler_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\profiler_vk.h` +- `Kits\Cauldron2\framework\render\vk\resourceview_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\resourceview_vk.h` +- `Kits\Cauldron2\framework\render\vk\resourceviewallocator_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\resourceviewallocator_vk.h` +- `Kits\Cauldron2\framework\render\vk\rootsignature_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\rootsignature_vk.h` +- `Kits\Cauldron2\framework\render\vk\rootsignaturedesc_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\rootsignaturedesc_vk.h` +- `Kits\Cauldron2\framework\render\vk\rtresources_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\rtresources_vk.h` +- `Kits\Cauldron2\framework\render\vk\sampler_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\sampler_vk.h` +- `Kits\Cauldron2\framework\render\vk\swapchain_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\swapchain_vk.h` +- `Kits\Cauldron2\framework\render\vk\texture_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\texture_vk.h` +- `Kits\Cauldron2\framework\render\vk\uploadheap_vk.cpp` +- `Kits\Cauldron2\framework\render\vk\uploadheap_vk.h` +- `Kits\Cauldron2\framework\render\win\shaderbuilder_win.cpp` +- `Kits\Cauldron2\framework\shaders\autoexposure.hlsl` +- `Kits\Cauldron2\framework\shaders\builddistortionfield.hlsl` +- `Kits\Cauldron2\framework\shaders\commonintersect.hlsl` +- `Kits\Cauldron2\framework\shaders\computeskinning.hlsl` +- `Kits\Cauldron2\framework\shaders\copytexture.hlsl` +- `Kits\Cauldron2\framework\shaders\fidelityfx\ffx_common_types.h` +- `Kits\Cauldron2\framework\shaders\fidelityfx\ffx_core_cpu.h` +- `Kits\Cauldron2\framework\shaders\fidelityfx\ffx_core_gpu_common_half.h` +- `Kits\Cauldron2\framework\shaders\fidelityfx\ffx_core_gpu_common.h` +- `Kits\Cauldron2\framework\shaders\fidelityfx\ffx_core_hlsl.h` +- `Kits\Cauldron2\framework\shaders\fidelityfx\ffx_core_portability.h` +- `Kits\Cauldron2\framework\shaders\fidelityfx\ffx_core.h` +- `Kits\Cauldron2\framework\shaders\fidelityfx\parallelsort\ffx_parallelsort_common.h` +- `Kits\Cauldron2\framework\shaders\fidelityfx\parallelsort\ffx_parallelsort.h` +- `Kits\Cauldron2\framework\shaders\fidelityfx\spd\ffx_spd.h` +- `Kits\Cauldron2\framework\shaders\fpslimiter.hlsl` +- `Kits\Cauldron2\framework\shaders\fullscreen.hlsl` +- `Kits\Cauldron2\framework\shaders\hudlessblit.hlsl` +- `Kits\Cauldron2\framework\shaders\lensdistortion.h` +- `Kits\Cauldron2\framework\shaders\lightfunctionscommon.hlsl` +- `Kits\Cauldron2\framework\shaders\lightingcommon.h` +- `Kits\Cauldron2\framework\shaders\particlecommon.h` +- `Kits\Cauldron2\framework\shaders\rasterlightfunctions.hlsl` +- `Kits\Cauldron2\framework\shaders\raytracingcommon.hlsl` +- `Kits\Cauldron2\framework\shaders\raytracinglightfunctions.hlsl` +- `Kits\Cauldron2\framework\shaders\shadercommon.h` +- `Kits\Cauldron2\framework\shaders\shadowFiltering.h` +- `Kits\Cauldron2\framework\shaders\surfacerendercommon.h` +- `Kits\Cauldron2\framework\shaders\tonemappercommon.h` +- `Kits\Cauldron2\framework\shaders\tonemappers.hlsl` +- `Kits\Cauldron2\framework\shaders\tonemapping.hlsl` +- `Kits\Cauldron2\framework\shaders\transferFunction.h` +- `Kits\Cauldron2\framework\shaders\transformVS.hlsl` +- `Kits\Cauldron2\framework\shaders\ui.hlsl` +- `Kits\Cauldron2\framework\shaders\uicommon.h` +- `Kits\Cauldron2\framework\shaders\upscaler.h` +- `Kits\Cauldron2\rendermodules\animatedtextures\animatedtexturesrendermodule.cpp` +- `Kits\Cauldron2\rendermodules\animatedtextures\animatedtexturesrendermodule.h` +- `Kits\Cauldron2\rendermodules\animatedtextures\shaders\AnimatedTexture.hlsl` +- `Kits\Cauldron2\rendermodules\gbuffer\config\gbufferrendermodule.json` +- `Kits\Cauldron2\rendermodules\gbuffer\gbufferrendermodule.cpp` +- `Kits\Cauldron2\rendermodules\gbuffer\gbufferrendermodule.h` +- `Kits\Cauldron2\rendermodules\gbuffer\shaders\gbufferps.hlsl` +- `Kits\Cauldron2\rendermodules\gpuparticle\ffx_assert.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\ffx_error.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\ffx_interface.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\ffx_parallelsort_private.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\ffx_parallelsort.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\ffx_types.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\ffx_util.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\gpuparticlerendermodule.cpp` +- `Kits\Cauldron2\rendermodules\gpuparticle\gpuparticlerendermodule.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort.cpp` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort_callbacks_glsl.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort_callbacks_hlsl.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort_common.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort_reduce.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort_resources.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort_scan_add.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort_scan.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort_scatter.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort_setup_indirect_args.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort_sum.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\parallelsort\ffx_parallelsort.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\parallelsort_common_ffx.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\parallelsort_reduce_pass.hlsl` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\parallelsort_scan_add_pass.hlsl` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\parallelsort_scan_pass.hlsl` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\parallelsort_scatter_pass.hlsl` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\parallelsort_setup_indirect_args.hlsl` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\parallelsort_sum_pass.hlsl` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\particleemit.hlsl` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\particlesimulation.hlsl` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\particlesimulationcommon.h` +- `Kits\Cauldron2\rendermodules\gpuparticle\shaders\simulationbindings.h` +- `Kits\Cauldron2\rendermodules\lighting\lightingrendermodule.cpp` +- `Kits\Cauldron2\rendermodules\lighting\lightingrendermodule.h` +- `Kits\Cauldron2\rendermodules\lighting\shaders\lighting.hlsl` +- `Kits\Cauldron2\rendermodules\rastershadow\config\rastershadowrendermodule.json` +- `Kits\Cauldron2\rendermodules\rastershadow\rastershadowrendermodule.cpp` +- `Kits\Cauldron2\rendermodules\rastershadow\rastershadowrendermodule.h` +- `Kits\Cauldron2\rendermodules\rastershadow\shaders\rastershadowps.hlsl` +- `Kits\Cauldron2\rendermodules\rendermoduleregistry.cpp` +- `Kits\Cauldron2\rendermodules\rendermoduleregistry.h` +- `Kits\Cauldron2\rendermodules\skydome\config\skydomerendermodule.json` +- `Kits\Cauldron2\rendermodules\skydome\shaders\skydome.hlsl` +- `Kits\Cauldron2\rendermodules\skydome\shaders\skydomecommon.h` +- `Kits\Cauldron2\rendermodules\skydome\shaders\skydomeproc.hlsl` +- `Kits\Cauldron2\rendermodules\skydome\skydomerendermodule.cpp` +- `Kits\Cauldron2\rendermodules\skydome\skydomerendermodule.h` +- `Kits\Cauldron2\rendermodules\taa\config\taarendermodule.json` +- `Kits\Cauldron2\rendermodules\taa\shaders\taa.hlsl` +- `Kits\Cauldron2\rendermodules\taa\shaders\taacommon.h` +- `Kits\Cauldron2\rendermodules\taa\shaders\taaPost.hlsl` +- `Kits\Cauldron2\rendermodules\taa\taarendermodule.cpp` +- `Kits\Cauldron2\rendermodules\taa\taarendermodule.h` +- `Kits\Cauldron2\rendermodules\translucency\config\translucencyrendermodule.json` +- `Kits\Cauldron2\rendermodules\translucency\shaders\particlerender.hlsl` +- `Kits\Cauldron2\rendermodules\translucency\shaders\particlerendercommon.h` +- `Kits\Cauldron2\rendermodules\translucency\shaders\translucencyps.hlsl` +- `Kits\Cauldron2\rendermodules\translucency\translucencyrendermodule.cpp` +- `Kits\Cauldron2\rendermodules\translucency\translucencyrendermodule.h` +- `Kits\Cauldron2\vk\Cauldron_vs2022.vcxproj` +- `Kits\Cauldron2\vk\Cauldron_vs2022.vcxproj.filters` +- `Kits\Cauldron2\vk\vcpkg.json` +- `Kits\FidelityFX\api\include\dx12\ffx_api_dx12.h` +- `Kits\FidelityFX\api\include\dx12\ffx_api_dx12.hpp` +- `Kits\FidelityFX\api\include\ffx_api_loader.h` +- `Kits\FidelityFX\api\include\ffx_api_matrix_helpers.h` +- `Kits\FidelityFX\api\include\ffx_api_matrix_helpers.hpp` +- `Kits\FidelityFX\api\include\ffx_api_types.h` +- `Kits\FidelityFX\api\include\ffx_api.h` +- `Kits\FidelityFX\api\include\ffx_api.hpp` +- `Kits\FidelityFX\api\include\vk\ffx_api_vk.h` +- `Kits\FidelityFX\api\include\vk\ffx_api_vk.hpp` +- `Kits\FidelityFX\api\internal\ffx_api_helper.h` +- `Kits\FidelityFX\api\internal\ffx_api.cpp` +- `Kits\FidelityFX\api\internal\ffx_assert.cpp` +- `Kits\FidelityFX\api\internal\ffx_assert.h` +- `Kits\FidelityFX\api\internal\ffx_backends.h` +- `Kits\FidelityFX\api\internal\ffx_blob_compression.h` +- `Kits\FidelityFX\api\internal\ffx_blob_decompress.h` +- `Kits\FidelityFX\api\internal\ffx_error.h` +- `Kits\FidelityFX\api\internal\ffx_interface.h` +- `Kits\FidelityFX\api\internal\ffx_internal_types.h` +- `Kits\FidelityFX\api\internal\ffx_message.cpp` +- `Kits\FidelityFX\api\internal\ffx_message.h` +- `Kits\FidelityFX\api\internal\ffx_object_management.cpp` +- `Kits\FidelityFX\api\internal\ffx_object_management.h` +- `Kits\FidelityFX\api\internal\ffx_provider.h` +- `Kits\FidelityFX\api\internal\ffx_query_fallback.cpp` +- `Kits\FidelityFX\api\internal\ffx_util.h` +- `Kits\FidelityFX\api\internal\gpu\ffx_common_types.h` +- `Kits\FidelityFX\api\internal\gpu\ffx_core_cpu.h` +- `Kits\FidelityFX\api\internal\gpu\ffx_core_gpu_common_half.h` +- `Kits\FidelityFX\api\internal\gpu\ffx_core_gpu_common.h` +- `Kits\FidelityFX\api\internal\gpu\ffx_core_hlsl.h` +- `Kits\FidelityFX\api\internal\gpu\ffx_core_portability.h` +- `Kits\FidelityFX\api\internal\gpu\ffx_core.h` +- `Kits\FidelityFX\backend\dx12\d3dx12.h` +- `Kits\FidelityFX\backend\dx12\ffx_backends_dx12.cpp` +- `Kits\FidelityFX\backend\dx12\ffx_dx12.cpp` +- `Kits\FidelityFX\backend\dx12\ffx_dx12.h` +- `Kits\FidelityFX\backend\vk\ffx_backends_vk.cpp` +- `Kits\FidelityFX\backend\vk\ffx_vk.cpp` +- `Kits\FidelityFX\backend\vk\ffx_vk.h` +- `Kits\FidelityFX\denoisers\include\ffx_denoiser.h` +- `Kits\FidelityFX\denoisers\include\ffx_denoiser.hpp` +- `Kits\FidelityFX\docs\getting-started\ffx-api.md` +- `Kits\FidelityFX\docs\getting-started\index.md` +- `Kits\FidelityFX\docs\getting-started\media\component-dependencies-dark.png` +- `Kits\FidelityFX\docs\getting-started\media\component-dependencies.png` +- `Kits\FidelityFX\docs\getting-started\media\config_init.jpg` +- `Kits\FidelityFX\docs\getting-started\media\lighting-ui.jpg` +- `Kits\FidelityFX\docs\getting-started\media\limiter-ui.jpg` +- `Kits\FidelityFX\docs\getting-started\media\magnifier-ui.jpg` +- `Kits\FidelityFX\docs\getting-started\media\particle-ui.jpg` +- `Kits\FidelityFX\docs\getting-started\media\postprocessing-ui.jpg` +- `Kits\FidelityFX\docs\getting-started\media\shadowraster-ui.jpg` +- `Kits\FidelityFX\docs\getting-started\media\skydome-ui.jpg` +- `Kits\FidelityFX\docs\getting-started\media\taa-ui.jpg` +- `Kits\FidelityFX\docs\getting-started\naming-guidelines.md` +- `Kits\FidelityFX\docs\getting-started\sdk-structure.md` +- `Kits\FidelityFX\docs\license.md` +- `Kits\FidelityFX\docs\media\AMD_FSR_SDK_Lockup_RGB_Blk.png` +- `Kits\FidelityFX\docs\media\AMD_FSR_SDK_Lockup_RGB_Wht.png` +- `Kits\FidelityFX\docs\techniques\denoising.md` +- `Kits\FidelityFX\docs\techniques\frame-interpolation-api.md` +- `Kits\FidelityFX\docs\techniques\frame-interpolation-ml.md` +- `Kits\FidelityFX\docs\techniques\frame-interpolation-swap-chain.md` +- `Kits\FidelityFX\docs\techniques\frame-interpolation.md` +- `Kits\FidelityFX\docs\techniques\index.md` +- `Kits\FidelityFX\docs\techniques\media\cross.png` +- `Kits\FidelityFX\docs\techniques\media\denoiser\fsr-ray-regeneration-brdf-lut.jpg` +- `Kits\FidelityFX\docs\techniques\media\denoiser\fsr-ray-regeneration-debug-view.png` +- `Kits\FidelityFX\docs\techniques\media\denoiser\fsr-ray-regeneration-denoised-sample.png` +- `Kits\FidelityFX\docs\techniques\media\denoiser\fsr-ray-regeneration-dispatch.png` +- `Kits\FidelityFX\docs\techniques\media\denoiser\fsr-ray-regeneration-noisy-sample.png` +- `Kits\FidelityFX\docs\techniques\media\denoiser\fsr-ray-regeneration-sample.png` +- `Kits\FidelityFX\docs\techniques\media\frame-interpolation-swapchain\frame-interpolation-swap-chain.svg` +- `Kits\FidelityFX\docs\techniques\media\frame-interpolation\frame-interpolation-debug-overlay.svg` +- `Kits\FidelityFX\docs\techniques\media\frame-pacing\fixed-high.svg` +- `Kits\FidelityFX\docs\techniques\media\frame-pacing\fixed-low.svg` +- `Kits\FidelityFX\docs\techniques\media\frame-pacing\pacing-overview.svg` +- `Kits\FidelityFX\docs\techniques\media\frame-pacing\variable-inside.svg` +- `Kits\FidelityFX\docs\techniques\media\radiance-cache\banner.jpg` +- `Kits\FidelityFX\docs\techniques\media\radiance-cache\path-tracer-schematic-querying.png` +- `Kits\FidelityFX\docs\techniques\media\radiance-cache\path-tracer-schematic-self-train.png` +- `Kits\FidelityFX\docs\techniques\media\radiance-cache\path-tracer-schematic-subpaths.png` +- `Kits\FidelityFX\docs\techniques\media\radiance-cache\pipeline-one-pass.png` +- `Kits\FidelityFX\docs\techniques\media\radiance-cache\pipeline-two-pass.png` +- `Kits\FidelityFX\docs\techniques\media\radiance-cache\sub-path-eqn.png` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-interpolation\01_FSR3_DataFlow.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-interpolation\02_FSR3_Flow.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-interpolation\03_FSR3_FlowAsyncQueue.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-interpolation\04_FSR3_UiCallback.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-interpolation\05_FSR3_NoFG_UiCallback.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-interpolation\06_FSR3_UiTex.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-interpolation\07_FSR3_NoFG_UiTex.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-interpolation\08_FSR3_HUDLess.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-ml\fsr4-sample.jpg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-ml\pipeline-placement.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-temporal\fsr2-sample_resized.jpg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-temporal\fsr2-sample.jpg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-temporal\fsr3-sample_resized.jpg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-temporal\fsr3-sample.jpg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-temporal\jitter-space.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-temporal\motion-vectors.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-temporal\overview.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-temporal\pipeline-placement.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-upscaler\overview.svg` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-upscaler\upscaler-debug-fsr4.png` +- `Kits\FidelityFX\docs\techniques\media\super-resolution-upscaler\upscaler-debug-overlay.svg` +- `Kits\FidelityFX\docs\techniques\media\tick.png` +- `Kits\FidelityFX\docs\techniques\radiance-cache.md` +- `Kits\FidelityFX\docs\techniques\super-resolution-ml.md` +- `Kits\FidelityFX\docs\techniques\super-resolution-temporal.md` +- `Kits\FidelityFX\docs\techniques\super-resolution-upscaler.md` +- `Kits\FidelityFX\docs\whats-new\index.md` +- `Kits\FidelityFX\docs\whats-new\version_1_0.md` +- `Kits\FidelityFX\docs\whats-new\version_1_1_1.md` +- `Kits\FidelityFX\docs\whats-new\version_1_1_2.md` +- `Kits\FidelityFX\docs\whats-new\version_1_1_3.md` +- `Kits\FidelityFX\docs\whats-new\version_1_1_4.md` +- `Kits\FidelityFX\docs\whats-new\version_1_1.md` +- `Kits\FidelityFX\docs\whats-new\version_2_0_0.md` +- `Kits\FidelityFX\docs\whats-new\version_2_1_0.md` +- `Kits\FidelityFX\docs\whats-new\version_2_2_0.md` +- `Kits\FidelityFX\docs\whats-new\version_2_3_0.md` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\antilag2\ffx_antilag2_dx12.h` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\ffx_provider_fsr3framegenerationswapchain_dx12.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainDebugPacingPS.h` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainDebugPacingVS.h` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainDX12_DebugPacing.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainDX12_DebugPacing.h` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainDX12_Helpers.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainDX12_Helpers.h` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainDX12_UiComposition.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainDX12_UiComposition.h` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainDX12.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainDX12.h` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainUiCompositionPremulPS.h` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainUiCompositionPremulVS.h` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainUiCompositionPS.h` +- `Kits\FidelityFX\framegeneration\fsr3\dx12\FrameInterpolationSwapchainUiCompositionVS.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\ffx_frameinterpolation.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\ffx_opticalflow.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\ffx_provider_fsr3framegeneration.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\ffx_provider_fsr3framegenerationswapchain.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_callbacks_hlsl.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_common.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_compute_game_vector_field_inpainting_pyramid.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_compute_inpainting_pyramid.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_debug_view.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_disocclusion_mask.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_game_motion_vector_field.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_inpainting.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_optical_flow_vector_field.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_reconstruct_dilated_velocity_and_previous_depth.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_reconstruct_previous_depth.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_resources.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation_setup.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\frameinterpolation\ffx_frameinterpolation.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\opticalflow\ffx_opticalflow_callbacks_hlsl.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\opticalflow\ffx_opticalflow_common.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\opticalflow\ffx_opticalflow_compute_luminance_pyramid.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\opticalflow\ffx_opticalflow_compute_optical_flow_v5.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\opticalflow\ffx_opticalflow_compute_scd_divergence.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\opticalflow\ffx_opticalflow_filter_optical_flow_v5.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\opticalflow\ffx_opticalflow_generate_scd_histogram.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\opticalflow\ffx_opticalflow_prepare_luma.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\opticalflow\ffx_opticalflow_resources.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\opticalflow\ffx_opticalflow_scale_optical_flow_advanced_v5.h` +- `Kits\FidelityFX\framegeneration\fsr3\include\gpu\spd\ffx_spd.h` +- `Kits\FidelityFX\framegeneration\fsr3\internal\ffx_frameinterpolation_private.h` +- `Kits\FidelityFX\framegeneration\fsr3\internal\ffx_frameinterpolation_shaderblobs.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\internal\ffx_frameinterpolation_shaderblobs.h` +- `Kits\FidelityFX\framegeneration\fsr3\internal\ffx_frameinterpolation.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\internal\ffx_opticalflow_private.h` +- `Kits\FidelityFX\framegeneration\fsr3\internal\ffx_opticalflow_shaderblobs.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\internal\ffx_opticalflow_shaderblobs.h` +- `Kits\FidelityFX\framegeneration\fsr3\internal\ffx_opticalflow.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\internal\ffx_provider_fsr3framegeneration.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_compute_game_vector_field_inpainting_pyramid_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_compute_inpainting_pyramid_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_debug_view_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_disocclusion_mask_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_game_motion_vector_field_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_inpainting_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_optical_flow_vector_field_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_reconstruct_and_dilate_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_reconstruct_previous_depth_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_frameinterpolation_setup_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_opticalflow_compute_luminance_pyramid_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_opticalflow_compute_optical_flow_advanced_pass_v5.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_opticalflow_compute_scd_divergence_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_opticalflow_filter_optical_flow_pass_v5.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_opticalflow_generate_scd_histogram_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_opticalflow_prepare_luma_pass.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\ffx_opticalflow_scale_optical_flow_advanced_pass_v5.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\FrameInterpolationSwapchainDebugPacing.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\internal\shaders\FrameInterpolationSwapchainUiComposition.hlsl` +- `Kits\FidelityFX\framegeneration\fsr3\vk\ffx_provider_framegenerationswapchain_vk.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainDebugPacingPS.h` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainDebugPacingVS.h` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainUiCompositionPremulPS.h` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainUiCompositionPremulVS.h` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainUiCompositionPS.h` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainUiCompositionVS.h` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainVK_DebugPacing.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainVK_DebugPacing.h` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainVK_Helpers.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainVK_Helpers.h` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainVK_UiComposition.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainVK_UiComposition.h` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainVK.cpp` +- `Kits\FidelityFX\framegeneration\fsr3\vk\FrameInterpolationSwapchainVK.h` +- `Kits\FidelityFX\framegeneration\include\dx12\ffx_api_framegeneration_dx12.h` +- `Kits\FidelityFX\framegeneration\include\dx12\ffx_api_framegeneration_dx12.hpp` +- `Kits\FidelityFX\framegeneration\include\ffx_framegeneration_api_types.h` +- `Kits\FidelityFX\framegeneration\include\ffx_framegeneration.h` +- `Kits\FidelityFX\framegeneration\include\ffx_framegeneration.hpp` +- `Kits\FidelityFX\framegeneration\include\vk\ffx_api_framegeneration_vk.h` +- `Kits\FidelityFX\framegeneration\include\vk\ffx_api_framegeneration_vk.hpp` +- `Kits\FidelityFX\framegeneration\internal\ffx_framegeneration_internal.h` +- `Kits\FidelityFX\radiancecache\include\ffx_radiancecache.h` +- `Kits\FidelityFX\radiancecache\include\ffx_radiancecache.hpp` +- `Kits\FidelityFX\readme.md` +- `Kits\FidelityFX\signedbin\amd_fidelityfx_denoiser_dx12.dll` +- `Kits\FidelityFX\signedbin\amd_fidelityfx_framegeneration_dx12.dll` +- `Kits\FidelityFX\signedbin\amd_fidelityfx_framegeneration_vk.dll` +- `Kits\FidelityFX\signedbin\amd_fidelityfx_loader_dx12.dll` +- `Kits\FidelityFX\signedbin\amd_fidelityfx_loader_dx12.lib` +- `Kits\FidelityFX\signedbin\amd_fidelityfx_loader_vk.dll` +- `Kits\FidelityFX\signedbin\amd_fidelityfx_loader_vk.lib` +- `Kits\FidelityFX\signedbin\amd_fidelityfx_radiancecache_dx12.dll` +- `Kits\FidelityFX\signedbin\amd_fidelityfx_upscaler_dx12.dll` +- `Kits\FidelityFX\signedbin\amd_fidelityfx_upscaler_vk.dll` +- `Kits\FidelityFX\upscalers\fsr3\include\ffx_fsr2.h` +- `Kits\FidelityFX\upscalers\fsr3\include\ffx_fsr3upscaler.h` +- `Kits\FidelityFX\upscalers\fsr3\include\ffx_provider_fsr2.h` +- `Kits\FidelityFX\upscalers\fsr3\include\ffx_provider_fsr3upscale.h` +- `Kits\FidelityFX\upscalers\fsr3\include\ffx_spd.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr1\ffx_fsr1.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_accumulate.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_callbacks_hlsl.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_common.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_compute_luminance_pyramid.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_depth_clip.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_lock.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_postprocess_lock_status.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_rcas.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_reconstruct_previous_depth.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_reproject.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_resources.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_sample.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_tcr_autogen.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr2\ffx_fsr2_upsample.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3\ffx_fsr3_callbacks_hlsl.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3\ffx_fsr3_common.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3\ffx_fsr3_resources.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_accumulate.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_callbacks_hlsl.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_common.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_debug_view.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_luma_instability.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_luma_pyramid.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_prepare_inputs.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_prepare_reactivity.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_rcas.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_reproject.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_resources.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_sample.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_shading_change_pyramid.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_shading_change.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\fsr3upscaler\ffx_fsr3upscaler_upsample.h` +- `Kits\FidelityFX\upscalers\fsr3\include\gpu\spd\ffx_spd.h` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_fsr2_maximum_bias.h` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_fsr2_private.h` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_fsr2_shaderblobs.cpp` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_fsr2_shaderblobs.h` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_fsr2.cpp` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_fsr3_private.h` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_fsr3upscaler_private.h` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_fsr3upscaler_shaderblobs.cpp` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_fsr3upscaler_shaderblobs.h` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_fsr3upscaler.cpp` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_provider_fsr2.cpp` +- `Kits\FidelityFX\upscalers\fsr3\internal\ffx_provider_fsr3upscale.cpp` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr2_accumulate_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr2_autogen_reactive_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr2_compute_luminance_pyramid_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr2_debug_blit.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr2_depth_clip_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr2_lock_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr2_rcas_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr2_reconstruct_previous_depth_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr2_tcr_autogen_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr3upscaler_accumulate_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr3upscaler_autogen_reactive_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr3upscaler_debug_view_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr3upscaler_luma_instability_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr3upscaler_luma_pyramid_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr3upscaler_prepare_inputs_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr3upscaler_prepare_reactivity_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr3upscaler_rcas_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr3upscaler_shading_change_pass.hlsl` +- `Kits\FidelityFX\upscalers\fsr3\internal\shaders\ffx_fsr3upscaler_shading_change_pyramid_pass.hlsl` +- `Kits\FidelityFX\upscalers\include\ffx_upscale.h` +- `Kits\FidelityFX\upscalers\include\ffx_upscale.hpp` +- `Kits\OpenSource\amd\acs\amd_acs_ARM64_2022_MD.lib` +- `Kits\OpenSource\amd\acs\amd_acs_ARM64_2022_MDd.lib` +- `Kits\OpenSource\amd\acs\amd_acs_ARM64_2022_MT.lib` +- `Kits\OpenSource\amd\acs\amd_acs_ARM64_2022_MTd.lib` +- `Kits\OpenSource\amd\acs\amd_acs_ARM64.dll` +- `Kits\OpenSource\amd\acs\amd_acs_ARM64.exp` +- `Kits\OpenSource\amd\acs\amd_acs_ARM64.lib` +- `Kits\OpenSource\amd\acs\amd_acs_ARM64d.exp` +- `Kits\OpenSource\amd\acs\amd_acs_ARM64d.lib` +- `Kits\OpenSource\amd\acs\amd_acs_x64_2019_MD.lib` +- `Kits\OpenSource\amd\acs\amd_acs_x64_2019_MDd.lib` +- `Kits\OpenSource\amd\acs\amd_acs_x64_2019_MT.lib` +- `Kits\OpenSource\amd\acs\amd_acs_x64_2019_MTd.lib` +- `Kits\OpenSource\amd\acs\amd_acs_x64.dll` +- `Kits\OpenSource\amd\acs\amd_acs_x64.exp` +- `Kits\OpenSource\amd\acs\amd_acs_x64.lib` +- `Kits\OpenSource\amd\acs\amd_acs_x64d.exp` +- `Kits\OpenSource\amd\acs\amd_acs_x64d.lib` +- `Kits\OpenSource\amd\acs\amd_acs.h` +- `Kits\OpenSource\amd\acs\LICENSE.txt` +- `Kits\OpenSource\amd\acs\README.md` +- `Kits\OpenSource\amd\ags\ags_shader_intrinsics_dx12.hlsl` +- `Kits\OpenSource\amd\ags\amd_ags_x64.dll` +- `Kits\OpenSource\amd\ags\amd_ags_x64.lib` +- `Kits\OpenSource\amd\ags\amd_ags.chm` +- `Kits\OpenSource\amd\ags\amd_ags.h` +- `Kits\OpenSource\amd\ags\LICENSE.txt` +- `Kits\OpenSource\amd\ags\README.md` +- `Kits\OpenSource\amd\antilag2\ffx_antilag2_dx12.h` +- `Kits\OpenSource\amd\antilag2\LICENSE.txt` +- `Kits\OpenSource\amd\memoryallocator\D3D12MemAlloc.cpp` +- `Kits\OpenSource\amd\memoryallocator\D3D12MemAlloc.h` +- `Kits\OpenSource\amd\memoryallocator\license.txt` +- `Kits\OpenSource\amd\memoryallocator\memoryallocator.cpp` +- `Kits\OpenSource\amd\memoryallocator\memoryallocator.h` +- `Kits\OpenSource\amd\memoryallocator\vk_mem_alloc.h` +- `Kits\OpenSource\imgui\backends\imgui_impl_dx12.cpp` +- `Kits\OpenSource\imgui\backends\imgui_impl_dx12.h` +- `Kits\OpenSource\imgui\backends\imgui_impl_win32.cpp` +- `Kits\OpenSource\imgui\backends\imgui_impl_win32.h` +- `Kits\OpenSource\imgui\docs\BACKENDS.md` +- `Kits\OpenSource\imgui\docs\CHANGELOG.txt` +- `Kits\OpenSource\imgui\docs\CONTRIBUTING.md` +- `Kits\OpenSource\imgui\docs\EXAMPLES.md` +- `Kits\OpenSource\imgui\docs\FAQ.md` +- `Kits\OpenSource\imgui\docs\FONTS.md` +- `Kits\OpenSource\imgui\docs\README.md` +- `Kits\OpenSource\imgui\docs\TODO.txt` +- `Kits\OpenSource\imgui\imconfig.h` +- `Kits\OpenSource\imgui\imgui_demo.cpp` +- `Kits\OpenSource\imgui\imgui_draw.cpp` +- `Kits\OpenSource\imgui\imgui_internal.h` +- `Kits\OpenSource\imgui\imgui_tables.cpp` +- `Kits\OpenSource\imgui\imgui_widgets.cpp` +- `Kits\OpenSource\imgui\imgui.cpp` +- `Kits\OpenSource\imgui\imgui.h` +- `Kits\OpenSource\imgui\imstb_rectpack.h` +- `Kits\OpenSource\imgui\imstb_textedit.h` +- `Kits\OpenSource\imgui\imstb_truetype.h` +- `Kits\OpenSource\imgui\LICENSE.txt` +- `Kits\OpenSource\imgui\misc\cpp\imgui_stdlib.cpp` +- `Kits\OpenSource\imgui\misc\cpp\imgui_stdlib.h` +- `Kits\OpenSource\imgui\misc\cpp\README.txt` +- `Kits\OpenSource\imgui\misc\debuggers\imgui.gdb` +- `Kits\OpenSource\imgui\misc\debuggers\imgui.natstepfilter` +- `Kits\OpenSource\imgui\misc\debuggers\imgui.natvis` +- `Kits\OpenSource\imgui\misc\debuggers\README.txt` +- `Kits\OpenSource\imgui\misc\fonts\binary_to_compressed_c.cpp` +- `Kits\OpenSource\imgui\misc\fonts\Cousine-Regular.ttf` +- `Kits\OpenSource\imgui\misc\fonts\DroidSans.ttf` +- `Kits\OpenSource\imgui\misc\fonts\Karla-Regular.ttf` +- `Kits\OpenSource\imgui\misc\fonts\ProggyClean.ttf` +- `Kits\OpenSource\imgui\misc\fonts\ProggyTiny.ttf` +- `Kits\OpenSource\imgui\misc\fonts\Roboto-Medium.ttf` +- `Kits\OpenSource\imgui\misc\freetype\imgui_freetype.cpp` +- `Kits\OpenSource\imgui\misc\freetype\imgui_freetype.h` +- `Kits\OpenSource\imgui\misc\freetype\README.md` +- `Kits\OpenSource\imgui\misc\README.txt` +- `Kits\OpenSource\imgui\misc\single_file\imgui_single_file.h` +- `Kits\OpenSource\nlohmann\json_fwd.hpp` +- `Kits\OpenSource\nlohmann\json.hpp` +- `Kits\OpenSource\nlohmann\LICENSE.MIT` +- `Kits\OpenSource\nlohmann\version.txt` +- `Kits\OpenSource\stb\LICENSE.MIT` +- `Kits\OpenSource\stb\stb_image_write.h` +- `Kits\OpenSource\stb\stb_image.h` +- `Kits\OpenSource\vectormath\common.hpp` +- `Kits\OpenSource\vectormath\docs\VectorMath-Library-Overview.pdf` +- `Kits\OpenSource\vectormath\LICENSE.txt` +- `Kits\OpenSource\vectormath\README.md` +- `Kits\OpenSource\vectormath\scalar\matrix.hpp` +- `Kits\OpenSource\vectormath\scalar\quaternion.hpp` +- `Kits\OpenSource\vectormath\scalar\vector.hpp` +- `Kits\OpenSource\vectormath\scalar\vectormath.hpp` +- `Kits\OpenSource\vectormath\sse\boolinvec.hpp` +- `Kits\OpenSource\vectormath\sse\floatinvec.hpp` +- `Kits\OpenSource\vectormath\sse\internal.hpp` +- `Kits\OpenSource\vectormath\sse\matrix.hpp` +- `Kits\OpenSource\vectormath\sse\quaternion.hpp` +- `Kits\OpenSource\vectormath\sse\vecidx.hpp` +- `Kits\OpenSource\vectormath\sse\vector.hpp` +- `Kits\OpenSource\vectormath\sse\vectormath.hpp` +- `Kits\OpenSource\vectormath\vec2d.hpp` +- `Kits\OpenSource\vectormath\vectormath.hpp` +- `readme.md` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\config\denoiserconfig.json` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\debugoverlayrendermodule.cpp` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\debugoverlayrendermodule.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\denoiserrendermodule.cpp` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\denoiserrendermodule.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\denoisersettingshandler.cpp` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\denoisersettingshandler.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\dpiawarescaling.manifest` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\FidelityFX_Denoiser_Sample_2022.props` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\FidelityFX_Denoiser_Sample_2022.sln` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\FidelityFX_Denoiser_Sample_2022.vcxproj` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\FidelityFX_Denoiser_Sample_2022.vcxproj.filters` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\fsrapirendermodule.cpp` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\fsrapirendermodule.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\fsrsettingshandler.cpp` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\fsrsettingshandler.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\main.cpp` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\pathtracingrendermodule.cpp` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\pathtracingrendermodule.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\resources\cauldron.rc` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\resources\GPUOpenChip.ico` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\resources\resource.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\sample.cpp` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\sample.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\sampleautomation.cpp` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\sampleautomation.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Basis.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Bindings.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Color.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\DebugOverlay.cs.hlsl` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\DebugOverlayData.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\DenoisingCompose.cs.hlsl` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\DenoisingData.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\DenoisingPrepare.cs.hlsl` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Dispatch.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Geometry.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Lighting.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Material.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Numeric.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\PackedData.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\PathTracing.cs.hlsl` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\PathTracing.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\PathTracingData.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Random.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Sampling.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Shadow.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Transformation.hlsli` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\Types.h` +- `Samples\Denoisers\FidelityFX_Denoiser\dx12\shaders\UnitVector.hlsli` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\config.json` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\D3DBase.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\D3DPipeline.cpp` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\D3DPipeline.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\dpiawarescaling.manifest` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\DXC.cpp` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\DXC.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\FidelityFX_NRC_2022.props` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\FidelityFX_NRC_2022.sln` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\FidelityFX_NRC_2022.vcxproj` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\FidelityFX_NRC_2022.vcxproj.filters` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\RenderManager.cpp` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\RenderManager.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\resources\cauldron.rc` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\resources\GPUOpenChip.ico` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\resources\resource.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\ShaderGraph.cpp` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\ShaderGraph.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Basis.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\bsdf\Beckmann.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\bsdf\BSDF.hlsli` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\bsdf\GGX.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\bsdf\Simple.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Camera.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Cel.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\color\Color.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\color\Grade.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Constants.hlsli` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Ctor.hlsli` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\FrameCtx.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Half.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Hash.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Hatch.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\integrator\PT.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\light\Planar.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Math.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\RadianceCache.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\random\Halton.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\random\OwenSobol.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\random\PCG.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\random\Random.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Ray.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\texture\Simple.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\tracable\Boolean.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\tracable\Intersector.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\tracable\KIFS.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\tracable\Primitive.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\tracable\SimpleCSG.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Transform.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\common\Triplanar.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\scene\Composite.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\scene\Display.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\scene\Meta.json` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\shaders\scene\Renderer.hlsl` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\Assert.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\Backtrace.cpp` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\Backtrace.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\FilesystemUtils.cpp` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\FilesystemUtils.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\HighResTimer.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\Json.cpp` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\Json.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\Log.cpp` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\Log.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\StringUtils.cpp` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\utils\StringUtils.h` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\vcpkg.json` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\Win32.cpp` +- `Samples\RadianceCaches\FidelityFX_NRC\dx12\Win32.h` +- `Samples\Upscalers\FidelityFX_FSR\dx12\config\fsrapiconfig.json` +- `Samples\Upscalers\FidelityFX_FSR\dx12\dpiawarescaling.manifest` +- `Samples\Upscalers\FidelityFX_FSR\dx12\FidelityFX_FSR_2022.props` +- `Samples\Upscalers\FidelityFX_FSR\dx12\FidelityFX_FSR_2022.sln` +- `Samples\Upscalers\FidelityFX_FSR\dx12\FidelityFX_FSR_2022.vcxproj` +- `Samples\Upscalers\FidelityFX_FSR\dx12\FidelityFX_FSR_2022.vcxproj.filters` +- `Samples\Upscalers\FidelityFX_FSR\dx12\resources\cauldron.rc` +- `Samples\Upscalers\FidelityFX_FSR\dx12\resources\GPUOpenChip.ico` +- `Samples\Upscalers\FidelityFX_FSR\dx12\resources\resource.h` +- `Samples\Upscalers\FidelityFX_FSR\internal\fsrapirendermodule.cpp` +- `Samples\Upscalers\FidelityFX_FSR\internal\fsrapirendermodule.h` +- `Samples\Upscalers\FidelityFX_FSR\internal\fsrsettingshandler.cpp` +- `Samples\Upscalers\FidelityFX_FSR\internal\fsrsettingshandler.h` +- `Samples\Upscalers\FidelityFX_FSR\internal\main.cpp` +- `Samples\Upscalers\FidelityFX_FSR\internal\sample.cpp` +- `Samples\Upscalers\FidelityFX_FSR\internal\sample.h` +- `Samples\Upscalers\FidelityFX_FSR\vk\config\fsrapiconfig.json` +- `Samples\Upscalers\FidelityFX_FSR\vk\dpiawarescaling.manifest` +- `Samples\Upscalers\FidelityFX_FSR\vk\FidelityFX_FSR_2022.props` +- `Samples\Upscalers\FidelityFX_FSR\vk\FidelityFX_FSR_2022.sln` +- `Samples\Upscalers\FidelityFX_FSR\vk\FidelityFX_FSR_2022.vcxproj` +- `Samples\Upscalers\FidelityFX_FSR\vk\FidelityFX_FSR_2022.vcxproj.filters` +- `Samples\Upscalers\FidelityFX_FSR\vk\fsrapirendermodule.cpp` +- `Samples\Upscalers\FidelityFX_FSR\vk\resources\cauldron.rc` +- `Samples\Upscalers\FidelityFX_FSR\vk\resources\GPUOpenChip.ico` +- `Samples\Upscalers\FidelityFX_FSR\vk\resources\resource.h` +- `Tools\media_delivery\MediaDelivery.exe` +- `UpdateMedia.bat` + +are subject to the following license: + + +Copyright (C)  Advanced Micro Devices, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/third_party/fidelityfx/README.md b/third_party/fidelityfx/README.md new file mode 100644 index 000000000..2a6f99a3c --- /dev/null +++ b/third_party/fidelityfx/README.md @@ -0,0 +1,10 @@ +# AMD FSR API headers (FidelityFX SDK v2.4.0) + +Unmodified MIT-licensed headers from AMD's FidelityFX SDK, tag `v2.4.0` +(, `Kits/FidelityFX/api/include` and +`Kits/FidelityFX/upscalers/include`). See `LICENSE.md`. + +Only the headers are compiled in (`gpu/vk/vk_fsr4.cpp`, in a build with `HOOKSHOT_FSR=1`). AMD's signed +`amd_fidelityfx_loader_vk.dll` and `amd_fidelityfx_upscaler_vk.dll` (from `Kits/FidelityFX/signedbin` of the same release) +are not vendored: they are loaded at run time, from `RX_VK_FSR_DIR` or the folder of `hookshot.exe`, when the Upscaler row +is on AMD FSR 4. diff --git a/third_party/fidelityfx/api/include/ffx_api.h b/third_party/fidelityfx/api/include/ffx_api.h new file mode 100644 index 000000000..d351c168e --- /dev/null +++ b/third_party/fidelityfx/api/include/ffx_api.h @@ -0,0 +1,213 @@ +// This file is part of the FidelityFX SDK. +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once + +#if defined(__cplusplus) +extern "C" { +#endif // #if defined(__cplusplus) + +#define FFX_API_ENTRY __declspec(dllexport) + +#include + +enum FfxApiReturnCodes +{ + FFX_API_RETURN_OK = 0, ///< The oparation was successful. + FFX_API_RETURN_ERROR = 1, ///< An error occurred that is not further specified. + FFX_API_RETURN_ERROR_UNKNOWN_DESCTYPE = 2, ///< The structure type given was not recognized for the function or context with which it was used. This is likely a programming error. + FFX_API_RETURN_ERROR_RUNTIME_ERROR = 3, ///< The underlying runtime (e.g. D3D12, Vulkan) or effect returned an error code. + FFX_API_RETURN_NO_PROVIDER = 4, ///< No provider was found for the given structure type. This is likely a programming error. + FFX_API_RETURN_ERROR_MEMORY = 5, ///< A memory allocation failed. + FFX_API_RETURN_ERROR_PARAMETER = 6, ///< A parameter was invalid, e.g. a null pointer, empty resource or out-of-bounds enum value. + FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE = 7, ///< The structure type given is new and not supported in the old provider. This is likely fixed with driver upgrade or effect DLL upgrade. +}; + +typedef void* ffxContext; +typedef uint32_t ffxReturnCode_t; + +#define FFX_API_EFFECT_MASK 0x00ff0000u +#define FFX_API_BACKEND_MASK 0xff000000u +#define FFX_API_EFFECT_ID_GENERAL 0x00000000u + +// Base Descriptor types +typedef uint64_t ffxStructType_t; +typedef struct ffxApiHeader +{ + ffxStructType_t type; ///< The structure type. Must always be set to the corresponding value for any structure (found nearby with a similar name). + struct ffxApiHeader* pNext; ///< Pointer to next structure, used for optional parameters and extensions. Can be null. +} ffxApiHeader; + +typedef ffxApiHeader ffxCreateContextDescHeader; +typedef ffxApiHeader ffxConfigureDescHeader; +typedef ffxApiHeader ffxQueryDescHeader; +typedef ffxApiHeader ffxDispatchDescHeader; + +// Extensions for global debug +#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_SILENCE 0x0000000u +#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_ERRORS 0x0000001u +#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_WARNINGS 0x0000002u +#define FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_VERBOSE 0xfffffffu + +enum FfxApiMsgType +{ + FFX_API_MESSAGE_TYPE_ERROR = 0, + FFX_API_MESSAGE_TYPE_WARNING = 1, + FFX_API_MESSAGE_TYPE_COUNT +}; + +typedef void (*ffxApiMessage)(uint32_t type, const wchar_t* message); + +#define FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG 7u +struct ffxConfigureDescGlobalDebug +{ + ffxConfigureDescHeader header; + uint64_t effectId; + ffxApiMessage fpMessage; ///< A pointer to a function that can receive messages from the runtime. May be null. + uint32_t debugLevel; +}; + +#define FFX_API_QUERY_DESC_TYPE_GET_VERSIONS 4u +struct ffxQueryDescGetVersions +{ + ffxQueryDescHeader header; + uint64_t createDescType; ///< Create description for the effect whose versions should be enumerated. + void* device; ///< For DX12: pointer to ID3D12Device. For VK, pointer to VkDeviceContext. + uint64_t *outputCount; ///< Input capacity of id and name arrays. Output number of returned versions. If initially zero, output is number of available versions. + uint64_t *versionIds; ///< Output array of version ids to be used as version overrides. If null, only names and count are returned. + const char** versionNames; ///< Output array of version names for display. If null, only ids and count are returned. If both this and versionIds are null, only count is returned. +}; + +#define FFX_API_DESC_TYPE_OVERRIDE_VERSION 5u +struct ffxOverrideVersion +{ + ffxApiHeader header; + uint64_t versionId; ///< Id of version to use. Must be a value returned from a query in ffxQueryDescGetVersions.versionIds array. +}; + +#define FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION 6u +struct ffxQueryGetProviderVersion +{ + ffxQueryDescHeader header; + uint64_t versionId; ///< Id of provider being used for queried context. 0 if invalid. + const char* versionName; ///< Version name for display. If nullptr, the query was invalid. +}; + +// Memory allocation function. Must return a valid pointer to at least size bytes of memory aligned to hold any type. +// May return null to indicate failure. Standard library malloc fulfills this requirement. +typedef void* (*ffxAlloc)(void* pUserData, uint64_t size); + +// Memory deallocation function. May be called with null pointer as second argument. +typedef void (*ffxDealloc)(void* pUserData, void* pMem); + +typedef struct ffxAllocationCallbacks +{ + void* pUserData; + ffxAlloc alloc; + ffxDealloc dealloc; +} ffxAllocationCallbacks; + +// Creates a FFX object context. +// Depending on the desc structures provided to this function, the context will be created with the desired version and attributes. +// Non-zero return indicates error code. +// Pointers passed in desc must remain live until ffxDestroyContext is called on the context. +// MemCb may be null; the system allocator (malloc/free) will be used in this case. +FFX_API_ENTRY ffxReturnCode_t ffxCreateContext(ffxContext* context, ffxCreateContextDescHeader* desc, const ffxAllocationCallbacks* memCb); +typedef ffxReturnCode_t (*PfnFfxCreateContext)(ffxContext* context, ffxCreateContextDescHeader* desc, const ffxAllocationCallbacks* memCb); + +// Destroys an FFX object context. +// Non-zero return indicates error code. +// MemCb must be compatible with the callbacks passed into ffxCreateContext. +FFX_API_ENTRY ffxReturnCode_t ffxDestroyContext(ffxContext* context, const ffxAllocationCallbacks* memCb); +typedef ffxReturnCode_t (*PfnFfxDestroyContext)(ffxContext* context, const ffxAllocationCallbacks* memCb); + +// Configures the provided FFX object context. +// If context is null, configure operates on any global state. +// Non-zero return indicates error code. +FFX_API_ENTRY ffxReturnCode_t ffxConfigure(ffxContext* context, const ffxConfigureDescHeader* desc); +typedef ffxReturnCode_t (*PfnFfxConfigure)(ffxContext* context, const ffxConfigureDescHeader* desc); + +// Queries the provided FFX object context. +// If context is null, query operates on any global state. +// Non-zero return indicates error code. +FFX_API_ENTRY ffxReturnCode_t ffxQuery(ffxContext* context, ffxQueryDescHeader* desc); +typedef ffxReturnCode_t (*PfnFfxQuery)(ffxContext* context, ffxQueryDescHeader* desc); + +// Dispatches work on the given FFX object context defined by the dispatch descriptor. +// Non-zero return indicates error code. +FFX_API_ENTRY ffxReturnCode_t ffxDispatch(ffxContext* context, const ffxDispatchDescHeader* desc); +typedef ffxReturnCode_t (*PfnFfxDispatch)(ffxContext* context, const ffxDispatchDescHeader* desc); + +// FFX_API_EFFECT_IDs +#define FFX_API_EFFECT_ID_UPSCALE 0x00010000u +#define FFX_API_EFFECT_ID_FRAMEGENERATION 0x00020000u +#define FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN 0x00030000u +// Need to keep this ID around for the deprecated VK frame gen swapchain +#define FFX_API_EFFECT_ID_FRAMEGENERATIONSWAPCHAIN_VK 0x00040000u +#define FFX_API_EFFECT_ID_DENOISER 0x00050000u +#define FFX_API_EFFECT_ID_RADIANCECACHE 0x00060000u +#define FFX_API_EFFECT_COUNT 6u + +#define FFX_API_MAKE_EFFECT_SUB_ID(effectId, subversion) ((effectId & FFX_API_EFFECT_MASK) | (subversion & ~FFX_API_EFFECT_MASK)) + +// FFX_APID_BACKEND_IDs +#define FFX_API_BACKEND_ID_DX12 0x00000000u +#define FFX_API_BACKEND_ID_VK 0x01000000u // For new effects going forward, please use this backend ID for vulkan specifics +#define FFX_API_BACKEND_ID_XBOX 0x02000000u + +#define FFX_API_MAKE_BACKEND_SUB_ID(backendId, subversion) ((backendId & FFX_API_BACKEND_MASK) | (subversion & ~FFX_API_BACKEND_MASK)) + +// Combiner for BACKEND-specific EFFECT sub-Ids +#define FFX_API_MAKE_BACKEND_EFFECT_SUB_ID(backendId, effectId, subversion) ((subversion & ~FFX_API_EFFECT_MASK) | (effectId & FFX_API_EFFECT_MASK) | (backendId & FFX_API_BACKEND_MASK) | (subversion & ~(FFX_API_BACKEND_MASK | FFX_API_EFFECT_MASK))) + +// Pragma macros for controlling warnings so that deprecations take affect externally but can be suppressed internally. +// This is so we can maintain the API/ABI until we are ready to make the breaking change. +// These are sadly compiler specific. +#if defined(_MSC_VER) && !defined(__clang__) && !defined(__GNUC__) && !defined(__INTEL_COMPILER) +#define FFX_PRAGMA_WARNING_PUSH warning( push ) +#define FFX_PRAGMA_WARNING_POP warning( pop ) +#define FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS warning( disable: 4996 ) +#define FFX_PRAGMA_WARNING_WARN_DEPRECATIONS warning( default: 4996 ) +#elif defined(__clang__) || defined(__GNUC__) +#define FFX_PRAGMA_WARNING_PUSH GCC diagnostic push +#define FFX_PRAGMA_WARNING_POP GCC diagnostic pop +#define FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS GCC diagnostic ignored "-Wdeprecated" +#define FFX_PRAGMA_WARNING_WARN_DEPRECATIONS GCC diagnostic warning "-Wdeprecated" +#else +#define FFX_PRAGMA_WARNING_PUSH +#define FFX_PRAGMA_WARNING_POP +#define FFX_PRAGMA_WARNING_DISABLE_DEPRECATIONS +#endif + +#define FFX_DEPRECATION(message) [[deprecated(message)]] + +#define FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG1 0x0000001u +struct FFX_DEPRECATION("Use ffxConfigureDescGlobalDebug instead of ffxConfigureDescGlobalDebug1.") ffxConfigureDescGlobalDebug1 +{ + ffxConfigureDescHeader header; + ffxApiMessage fpMessage; + uint32_t debugLevel; +}; + +#if defined(__cplusplus) +} +#endif // #if defined(__cplusplus) diff --git a/third_party/fidelityfx/api/include/ffx_api_types.h b/third_party/fidelityfx/api/include/ffx_api_types.h new file mode 100644 index 000000000..380c4c0a1 --- /dev/null +++ b/third_party/fidelityfx/api/include/ffx_api_types.h @@ -0,0 +1,257 @@ +// This file is part of the FidelityFX SDK. +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once + +#include +#include + +/// An enumeration of surface formats. Needs to match enum FfxApiSurfaceFormat +enum FfxApiSurfaceFormat +{ + FFX_API_SURFACE_FORMAT_UNKNOWN, ///< Unknown format + FFX_API_SURFACE_FORMAT_R32G32B32A32_TYPELESS, ///< 32 bit per channel, 4 channel typeless format + FFX_API_SURFACE_FORMAT_R32G32B32A32_UINT, ///< 32 bit per channel, 4 channel uint format + FFX_API_SURFACE_FORMAT_R32G32B32A32_FLOAT, ///< 32 bit per channel, 4 channel float format + FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT, ///< 16 bit per channel, 4 channel float format + FFX_API_SURFACE_FORMAT_R32G32B32_FLOAT, ///< 32 bit per channel, 3 channel float format + FFX_API_SURFACE_FORMAT_R32G32_FLOAT, ///< 32 bit per channel, 2 channel float format + FFX_API_SURFACE_FORMAT_R8_UINT, ///< 8 bit per channel, 1 channel float format + FFX_API_SURFACE_FORMAT_R32_UINT, ///< 32 bit per channel, 1 channel float format + FFX_API_SURFACE_FORMAT_R8G8B8A8_TYPELESS, ///< 8 bit per channel, 4 channel typeless format + FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM, ///< 8 bit per channel, 4 channel unsigned normalized format + FFX_API_SURFACE_FORMAT_R8G8B8A8_SNORM, ///< 8 bit per channel, 4 channel signed normalized format + FFX_API_SURFACE_FORMAT_R8G8B8A8_SRGB, ///< 8 bit per channel, 4 channel srgb normalized + FFX_API_SURFACE_FORMAT_B8G8R8A8_TYPELESS, ///< 8 bit per channel, 4 channel typeless format + FFX_API_SURFACE_FORMAT_B8G8R8A8_UNORM, ///< 8 bit per channel, 4 channel unsigned normalized format + FFX_API_SURFACE_FORMAT_B8G8R8A8_SRGB, ///< 8 bit per channel, 4 channel srgb normalized + FFX_API_SURFACE_FORMAT_R11G11B10_FLOAT, ///< 32 bit 3 channel float format + FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM, ///< 10 bit per 3 channel, 2 bit for 1 channel normalized format + FFX_API_SURFACE_FORMAT_R16G16_FLOAT, ///< 16 bit per channel, 2 channel float format + FFX_API_SURFACE_FORMAT_R16G16_UINT, ///< 16 bit per channel, 2 channel unsigned int format + FFX_API_SURFACE_FORMAT_R16G16_SINT, ///< 16 bit per channel, 2 channel signed int format + FFX_API_SURFACE_FORMAT_R16_FLOAT, ///< 16 bit per channel, 1 channel float format + FFX_API_SURFACE_FORMAT_R16_UINT, ///< 16 bit per channel, 1 channel unsigned int format + FFX_API_SURFACE_FORMAT_R16_UNORM, ///< 16 bit per channel, 1 channel unsigned normalized format + FFX_API_SURFACE_FORMAT_R16_SNORM, ///< 16 bit per channel, 1 channel signed normalized format + FFX_API_SURFACE_FORMAT_R8_UNORM, ///< 8 bit per channel, 1 channel unsigned normalized format + FFX_API_SURFACE_FORMAT_R8G8_UNORM, ///< 8 bit per channel, 2 channel unsigned normalized format + FFX_API_SURFACE_FORMAT_R8G8_UINT, ///< 8 bit per channel, 2 channel unsigned integer format + FFX_API_SURFACE_FORMAT_R32_FLOAT, ///< 32 bit per channel, 1 channel float format + FFX_API_SURFACE_FORMAT_R9G9B9E5_SHAREDEXP, ///< 9 bit per channel, 5 bit exponent format + + FFX_API_SURFACE_FORMAT_R16G16B16A16_TYPELESS, ///< 16 bit per channel, 4 channel typeless format + FFX_API_SURFACE_FORMAT_R32G32_TYPELESS, ///< 32 bit per channel, 2 channel typeless format + FFX_API_SURFACE_FORMAT_R10G10B10A2_TYPELESS, ///< 10 bit per 3 channel, 2 bit for 1 channel typeless format + FFX_API_SURFACE_FORMAT_R16G16_TYPELESS, ///< 16 bit per channel, 2 channel typless format + FFX_API_SURFACE_FORMAT_R16_TYPELESS, ///< 16 bit per channel, 1 channel typeless format + FFX_API_SURFACE_FORMAT_R8_TYPELESS, ///< 8 bit per channel, 1 channel typeless format + FFX_API_SURFACE_FORMAT_R8G8_TYPELESS, ///< 8 bit per channel, 2 channel typeless format + FFX_API_SURFACE_FORMAT_R32_TYPELESS, ///< 32 bit per channel, 1 channel typeless format + FFX_API_SURFACE_FORMAT_R32G32_UINT, ///< 32 bit per channel, 2 channel uint format + + FFX_API_SURFACE_FORMAT_R8_SNORM, ///< 8 bit per channel, 1 channel signed normalized format +}; + +/// An enumeration of resource usage. +enum FfxApiResourceUsage +{ + FFX_API_RESOURCE_USAGE_READ_ONLY = 0, ///< No usage flags indicate a resource is read only. + FFX_API_RESOURCE_USAGE_RENDERTARGET = (1<<0), ///< Indicates a resource will be used as render target. + FFX_API_RESOURCE_USAGE_UAV = (1<<1), ///< Indicates a resource will be used as UAV. + FFX_API_RESOURCE_USAGE_DEPTHTARGET = (1<<2), ///< Indicates a resource will be used as depth target. + FFX_API_RESOURCE_USAGE_INDIRECT = (1<<3), ///< Indicates a resource will be used as indirect argument buffer + FFX_API_RESOURCE_USAGE_ARRAYVIEW = (1<<4), ///< Indicates a resource that will generate array views. Works on 2D and cubemap textures + FFX_API_RESOURCE_USAGE_STENCILTARGET = (1<<5), ///< Indicates a resource will be used as stencil target. + + FFX_API_RESOURCE_USAGE_DCC_RENDERTARGET = (1<<15), ///< Indicates a resource that should specify optimal render target memory access flags (for console use) +}; + + +/// An enumeration of resource states. +enum FfxApiResourceState +{ + FFX_API_RESOURCE_STATE_COMMON = (1 << 0), + FFX_API_RESOURCE_STATE_UNORDERED_ACCESS = (1 << 1), ///< Indicates a resource is in the state to be used as UAV. + FFX_API_RESOURCE_STATE_COMPUTE_READ = (1 << 2), ///< Indicates a resource is in the state to be read by compute shaders. + FFX_API_RESOURCE_STATE_PIXEL_READ = (1 << 3), ///< Indicates a resource is in the state to be read by pixel shaders. + FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ = (FFX_API_RESOURCE_STATE_PIXEL_READ | FFX_API_RESOURCE_STATE_COMPUTE_READ), ///< Indicates a resource is in the state to be read by pixel or compute shaders. + FFX_API_RESOURCE_STATE_COPY_SRC = (1 << 4), ///< Indicates a resource is in the state to be used as source in a copy command. + FFX_API_RESOURCE_STATE_COPY_DEST = (1 << 5), ///< Indicates a resource is in the state to be used as destination in a copy command. + FFX_API_RESOURCE_STATE_GENERIC_READ = (FFX_API_RESOURCE_STATE_COPY_SRC | FFX_API_RESOURCE_STATE_COMPUTE_READ), ///< Indicates a resource is in generic (slow) read state. + FFX_API_RESOURCE_STATE_INDIRECT_ARGUMENT = (1 << 6), ///< Indicates a resource is in the state to be used as an indirect command argument + FFX_API_RESOURCE_STATE_PRESENT = (1 << 7), ///< Indicates a resource is in the state to be used to present to the swap chain + FFX_API_RESOURCE_STATE_RENDER_TARGET = (1 << 8), ///< Indicates a resource is in the state to be used as render target + FFX_API_RESOURCE_STATE_DEPTH_ATTACHMENT = (1 << 9), ///< Indicates a resource is in the state to be used as depth attachment +}; + +/// An enumeration of surface dimensions. +enum FfxApiResourceDimension +{ + FFX_API_RESOURCE_DIMENSION_TEXTURE_1D, ///< A resource with a single dimension. + FFX_API_RESOURCE_DIMENSION_TEXTURE_2D, ///< A resource with two dimensions. +}; + +/// An enumeration of resource flags. +enum FfxApiResourceFlags +{ + FFX_API_RESOURCE_FLAGS_NONE = 0, ///< No flags. + FFX_API_RESOURCE_FLAGS_ALIASABLE = (1 << 0), ///< A bit indicating a resource does not need to persist across frames. + FFX_API_RESOURCE_FLAGS_UNDEFINED = (1 << 1), ///< Special case flag used internally when importing resources that require additional setup +}; + +// An enumeration for different resource types +enum FfxApiResourceType +{ + FFX_API_RESOURCE_TYPE_BUFFER, ///< The resource is a buffer. + FFX_API_RESOURCE_TYPE_TEXTURE1D, ///< The resource is a 1-dimensional texture. + FFX_API_RESOURCE_TYPE_TEXTURE2D, ///< The resource is a 2-dimensional texture. + FFX_API_RESOURCE_TYPE_TEXTURE_CUBE, ///< The resource is a cube map. + FFX_API_RESOURCE_TYPE_TEXTURE3D, ///< The resource is a 3-dimensional texture. +}; + +enum FfxApiBackbufferTransferFunction +{ + FFX_API_BACKBUFFER_TRANSFER_FUNCTION_SRGB, + FFX_API_BACKBUFFER_TRANSFER_FUNCTION_PQ, + FFX_API_BACKBUFFER_TRANSFER_FUNCTION_SCRGB +}; + +/// A structure encapsulating a 2-dimensional point, using 32bit unsigned integers. +struct FfxApiDimensions2D +{ + uint32_t width; ///< The width of a 2-dimensional range. + uint32_t height; ///< The height of a 2-dimensional range. +}; + +/// A structure encapsulating a 2-dimensional set of floating point coordinates. +struct FfxApiFloatCoords2D +{ + float x; ///< The x coordinate of a 2-dimensional point. + float y; ///< The y coordinate of a 2-dimensional point. +}; + +/// A structure encapsulating a 3-dimensional set of floating point coordinates. +struct FfxApiFloatCoords3D +{ + float x; ///< The x coordinate of a 3-dimensional point. + float y; ///< The y coordinate of a 3-dimensional point. + float z; ///< The z coordinate of a 3-dimensional point. +}; + +/// A structure encapsulating a 2-dimensional rect. +struct FfxApiRect2D +{ + int32_t left; + int32_t top; + int32_t width; + int32_t height; +}; + +/// A structure describing a resource. +/// +/// @ingroup SDKTypes +struct FfxApiResourceDescription +{ + uint32_t type; ///< The type of the resource. + uint32_t format; ///< The surface format. + union { + uint32_t width; ///< The width of the texture resource. + uint32_t size; ///< The size of the buffer resource. + }; + + union { + uint32_t height; ///< The height of the texture resource. + uint32_t stride; ///< The stride of the buffer resource. + }; + + union { + uint32_t depth; ///< The depth of the texture resource. + uint32_t alignment; ///< The alignment of the buffer resource. + }; + + uint32_t mipCount; ///< Number of mips (or 0 for full mipchain). + uint32_t flags; ///< A set of resource flags. + uint32_t usage; ///< Resource usage flags. +}; + +#define FFX_RESOURCE_NAME_SIZE 64 + +struct FfxApiResource +{ + void* resource; + struct FfxApiResourceDescription description; + uint32_t state; +}; + +//struct definition matches FfxApiEffectMemoryUsage +typedef struct FfxApiEffectMemoryUsage +{ + uint64_t totalUsageInBytes; + uint64_t aliasableUsageInBytes; +} FfxApiEffectMemoryUsage; + +/// A structure describing a constant buffer allocation. +/// +/// @ingroup SDKTypes +typedef struct FfxApiConstantBufferAllocation +{ + struct FfxApiResource resource; ///< The resource representing the constant buffer resource. + uint64_t handle; ///< The binding handle for the constant buffer + +} FfxApiConstantBufferAllocation; + +/// A function definition for a constant buffer allocation callback +/// Used to provide a constant buffer allocator to the calling backend +/// +/// @param [in] data The constant buffer data. +/// @param [in] dataSize The size of the constant buffer data. +/// +/// @ingroup SDKTypes +typedef FfxApiConstantBufferAllocation(*FfxApiConstantBufferAllocator)(void* data, const uint64_t dataSize); + +/// A 4-component floating point vector +typedef struct FfxApiFloat4 +{ + float x; + float y; + float z; + float w; +} FfxApiFloat4; + +/// A 4x4 matrix in row-major layout with row-vector convention. +/// This is the canonical format expected by FFX APIs. +/// +/// Memory layout: rows[row_index][column_index] +/// Mathematical convention: v * M (row vector multiplied by matrix) +typedef struct FfxApiMatrix4x4 +{ + FfxApiFloat4 rows[4]; ///< Four rows of the matrix. Access as rows[row].x, rows[row].y etc. +} FfxApiMatrix4x4; + +/// An inclusive floating-point interval [min, max]. +/// Valid instances must satisfy min <= max. +typedef struct FfxApiFloatBounds +{ + float min; ///< Lower bound of the interval. + float max; ///< Upper bound of the interval. +} FfxApiFloatBounds; diff --git a/third_party/fidelityfx/api/include/vk/ffx_api_vk.h b/third_party/fidelityfx/api/include/vk/ffx_api_vk.h new file mode 100644 index 000000000..1bbe1d9e4 --- /dev/null +++ b/third_party/fidelityfx/api/include/vk/ffx_api_vk.h @@ -0,0 +1,308 @@ +// This file is part of the FidelityFX SDK. +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once +#include "../ffx_api.h" +#include "../ffx_api_types.h" +#include + +/// FFX specific callback type when submitting a command buffer to a queue. +typedef VkResult(*PFN_vkQueueSubmitFFXAPI)(uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence); + +/// Helper stucture +struct VkQueueInfoFFXAPI +{ + VkQueue queue; ///< the vulkan queue + uint32_t familyIndex; ///< the queue family index, that will be used to perform queue family ownership transfer + PFN_vkQueueSubmitFFXAPI submitFunc; ///< an optional submit function in case there might be some concurrent submissions +}; + +// We need to keep this one alive because of VK verison of FSR3.1.4 +// Deprecated version of the VK backend create context descriptor, please use the non-deprecated version below. +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK_DEPRECATED 0x0000003u +struct ffxCreateBackendVKDesc_DEPRECATED +{ + ffxCreateContextDescHeader header; + VkDevice vkDevice; ///< the logical device used by the program. + VkPhysicalDevice vkPhysicalDevice; ///< the physical device used by the program. + PFN_vkGetDeviceProcAddr vkDeviceProcAddr; ///< function pointer to get device procedure addresses +}; + +/// Convenience structure to hold all VK-related device information. +typedef struct VkDeviceContext { + VkDevice vkDevice; ///< The Vulkan device + VkPhysicalDevice vkPhysicalDevice; ///< The Vulkan physical device + PFN_vkGetDeviceProcAddr vkDeviceProcAddr; ///< The device's function address table +} VkDeviceContext; + +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK FFX_API_MAKE_BACKEND_SUB_ID(FFX_API_BACKEND_ID_VK, 0x03) +struct ffxCreateBackendVKDesc +{ + ffxCreateContextDescHeader header; + VkDeviceContext deviceContext; ///< the VK device information used by the program. +}; + +/// Resource allocation callback function. (TODO) +typedef ffxReturnCode_t(*PfnFfxResourceAllocatorFunc)(uint32_t effectId/*, + D3D12_RESOURCE_STATES initialState, + const D3D12_HEAP_PROPERTIES* pHeapProps, + const D3D12_RESOURCE_DESC* pD3DDesc, + const FfxApiResourceDescription* pFfxDesc, + const D3D12_CLEAR_VALUE* pOptimizedClear, + ID3D12Resource** ppD3DResource*/); + +/// Resource destruction callback function. (TODO) +typedef ffxReturnCode_t(*PfnFfxResourceDeallocatorFunc)(uint32_t effectId/*, ID3D12Resource* pResource*/); + +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK_ALLOCATION_CALLBACKS FFX_API_MAKE_BACKEND_SUB_ID(FFX_API_BACKEND_ID_VK, 0x04) +struct ffxCreateBackendVKAllocationCallbacksDesc +{ + ffxCreateContextDescHeader header; + PfnFfxResourceAllocatorFunc pfnFfxResourceAllocator; ///< Resource allocation function (can be null) <------- NOT IMPL YET, SEE DX12/XBOX IMPL + PfnFfxResourceDeallocatorFunc pfnFfxResourceDeallocator; ///< Resource deallocation function (can be null) <------- NOT IMPL YET, SEE DX12/XBOX IMPL + FfxApiConstantBufferAllocator pfnFfxConstantBufferAllocator; ///< Constant buffer allocation function (can be null) +}; + +#if defined(__cplusplus) + +static inline uint32_t ffxApiGetSurfaceFormatVK(VkFormat fmt) +{ + switch (fmt) + { + case VK_FORMAT_R32G32B32A32_SFLOAT: + return FFX_API_SURFACE_FORMAT_R32G32B32A32_FLOAT; + case VK_FORMAT_R32G32B32_SFLOAT: + return FFX_API_SURFACE_FORMAT_R32G32B32_FLOAT; + case VK_FORMAT_R32G32B32A32_UINT: + return FFX_API_SURFACE_FORMAT_R32G32B32A32_UINT; + case VK_FORMAT_R16G16B16A16_SFLOAT: + return FFX_API_SURFACE_FORMAT_R16G16B16A16_FLOAT; + case VK_FORMAT_R32G32_SFLOAT: + return FFX_API_SURFACE_FORMAT_R32G32_FLOAT; + case VK_FORMAT_R32_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_X8_D24_UNORM_PACK32: + return FFX_API_SURFACE_FORMAT_R32_UINT; + case VK_FORMAT_R8G8B8A8_UNORM: + return FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM; + case VK_FORMAT_R8G8B8A8_SNORM: + return FFX_API_SURFACE_FORMAT_R8G8B8A8_SNORM; + case VK_FORMAT_R8G8B8A8_SRGB: + return FFX_API_SURFACE_FORMAT_R8G8B8A8_SRGB; + case VK_FORMAT_B8G8R8A8_UNORM: + return FFX_API_SURFACE_FORMAT_B8G8R8A8_UNORM; + case VK_FORMAT_B8G8R8A8_SRGB: + return FFX_API_SURFACE_FORMAT_B8G8R8A8_SRGB; + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: + return FFX_API_SURFACE_FORMAT_R11G11B10_FLOAT; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + return FFX_API_SURFACE_FORMAT_R10G10B10A2_UNORM; + case VK_FORMAT_R16G16_SFLOAT: + return FFX_API_SURFACE_FORMAT_R16G16_FLOAT; + case VK_FORMAT_R16G16_UINT: + return FFX_API_SURFACE_FORMAT_R16G16_UINT; + case VK_FORMAT_R16G16_SINT: + return FFX_API_SURFACE_FORMAT_R16G16_SINT; + case VK_FORMAT_R16_SFLOAT: + return FFX_API_SURFACE_FORMAT_R16_FLOAT; + case VK_FORMAT_R16_UINT: + return FFX_API_SURFACE_FORMAT_R16_UINT; + case VK_FORMAT_R16_UNORM: + case VK_FORMAT_D16_UNORM: + case VK_FORMAT_D16_UNORM_S8_UINT: + return FFX_API_SURFACE_FORMAT_R16_UNORM; + case VK_FORMAT_R16_SNORM: + return FFX_API_SURFACE_FORMAT_R16_SNORM; + case VK_FORMAT_R8_UNORM: + return FFX_API_SURFACE_FORMAT_R8_UNORM; + case VK_FORMAT_R8_SNORM: + return FFX_API_SURFACE_FORMAT_R8_SNORM; + case VK_FORMAT_R8_UINT: + case VK_FORMAT_S8_UINT: + return FFX_API_SURFACE_FORMAT_R8_UINT; + case VK_FORMAT_R8G8_UNORM: + return FFX_API_SURFACE_FORMAT_R8G8_UNORM; + case VK_FORMAT_R8G8_UINT: + return FFX_API_SURFACE_FORMAT_R8G8_UINT; + case VK_FORMAT_R32_SFLOAT: + case VK_FORMAT_D32_SFLOAT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return FFX_API_SURFACE_FORMAT_R32_FLOAT; + case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: + return FFX_API_SURFACE_FORMAT_R9G9B9E5_SHAREDEXP; + case VK_FORMAT_UNDEFINED: + return FFX_API_SURFACE_FORMAT_UNKNOWN; + + default: + // NOTE: we do not support typeless formats here + //FFX_ASSERT_MESSAGE(false, "Format not yet supported"); + return FFX_API_SURFACE_FORMAT_UNKNOWN; + } +} + +static inline uint32_t ffxApiGetSurfaceFormatToGamma(uint32_t fmt) +{ + switch (fmt) + { + case (FFX_API_SURFACE_FORMAT_R8G8B8A8_UNORM): + return FFX_API_SURFACE_FORMAT_R8G8B8A8_SRGB; + case (FFX_API_SURFACE_FORMAT_B8G8R8A8_UNORM): + return FFX_API_SURFACE_FORMAT_B8G8R8A8_SRGB; + default: + return fmt; + } +} + +static inline bool ffxApiIsDepthFormat(VkFormat fmt) +{ + switch (fmt) + { + case VK_FORMAT_D16_UNORM: + case VK_FORMAT_X8_D24_UNORM_PACK32: + case VK_FORMAT_D32_SFLOAT: + case VK_FORMAT_D16_UNORM_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return true; + + default: + return false; + } +} + +static inline bool ffxApiIsStencilFormat(VkFormat fmt) +{ + switch (fmt) + { + case VK_FORMAT_S8_UINT: + case VK_FORMAT_D16_UNORM_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return true; + + default: + return false; + } +} + +static inline FfxApiResourceDescription ffxApiGetBufferResourceDescriptionVK(const VkBuffer buffer, + const VkBufferCreateInfo createInfo, + uint32_t additionalUsages) +{ + FfxApiResourceDescription resourceDescription = {}; + + // This is valid + if (buffer == VK_NULL_HANDLE) + return resourceDescription; + + resourceDescription.flags = FFX_API_RESOURCE_FLAGS_NONE; + resourceDescription.usage = additionalUsages; + resourceDescription.size = (uint32_t)createInfo.size; + resourceDescription.stride = 0; + resourceDescription.format = FFX_API_SURFACE_FORMAT_UNKNOWN; + + if ((createInfo.usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0) + resourceDescription.usage |= FFX_API_RESOURCE_USAGE_UAV; + if ((createInfo.usage & VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT) != 0) + resourceDescription.usage |= FFX_API_RESOURCE_USAGE_INDIRECT; + + // What should we initialize this to?? No case for this yet + resourceDescription.depth = 0; + resourceDescription.mipCount = 0; + + // Set the type + resourceDescription.type = FFX_API_RESOURCE_TYPE_BUFFER; + + return resourceDescription; +} + +static inline FfxApiResourceDescription ffxApiGetImageResourceDescriptionVK(const VkImage image, const VkImageCreateInfo createInfo, uint32_t additionalUsages) +{ + FfxApiResourceDescription resourceDescription = {}; + + // This is valid + if (image == VK_NULL_HANDLE) + return resourceDescription; + + // Set flags properly for resource registration + resourceDescription.flags = FFX_API_RESOURCE_FLAGS_NONE; + resourceDescription.usage = FFX_API_RESOURCE_USAGE_READ_ONLY; + + // Check for depth stencil use + if (ffxApiIsDepthFormat(createInfo.format)) + resourceDescription.usage |= FFX_API_RESOURCE_USAGE_DEPTHTARGET; + if (ffxApiIsStencilFormat(createInfo.format)) + resourceDescription.usage |= FFX_API_RESOURCE_USAGE_STENCILTARGET; + + // Unordered access use + if ((createInfo.usage & VK_IMAGE_USAGE_STORAGE_BIT) != 0) + resourceDescription.usage |= FFX_API_RESOURCE_USAGE_UAV; + + // Resource-specific supplemental use flags + resourceDescription.usage |= additionalUsages; + + resourceDescription.width = createInfo.extent.width; + resourceDescription.height = createInfo.extent.height; + resourceDescription.mipCount = createInfo.mipLevels; + resourceDescription.format = ffxApiGetSurfaceFormatVK(createInfo.format); + + // if the mutable flag is present, assume that the real format is sRGB + if ((createInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) + resourceDescription.format = ffxApiGetSurfaceFormatToGamma(resourceDescription.format); + + switch (createInfo.imageType) + { + case VK_IMAGE_TYPE_1D: + resourceDescription.type = FFX_API_RESOURCE_TYPE_TEXTURE1D; + break; + case VK_IMAGE_TYPE_2D: + resourceDescription.depth = createInfo.arrayLayers; + if ((additionalUsages & FFX_API_RESOURCE_USAGE_ARRAYVIEW) != 0) + resourceDescription.type = FFX_API_RESOURCE_TYPE_TEXTURE2D; + else if ((createInfo.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) != 0) + resourceDescription.type = FFX_API_RESOURCE_TYPE_TEXTURE_CUBE; + else + resourceDescription.type = FFX_API_RESOURCE_TYPE_TEXTURE2D; + break; + case VK_IMAGE_TYPE_3D: + resourceDescription.depth = createInfo.extent.depth; + resourceDescription.type = FFX_API_RESOURCE_TYPE_TEXTURE3D; + break; + default: + //FFX_ASSERT_MESSAGE(false, "FFXInterface: VK: Unsupported texture dimension requested. Please implement."); + break; + } + + return resourceDescription; +} + +static inline FfxApiResource ffxApiGetResourceVK(void* vkResource, FfxApiResourceDescription ffxResDescription, uint32_t state) +{ + FfxApiResource resource = {}; + resource.resource = vkResource; + resource.state = state; + resource.description = ffxResDescription; + + return resource; +} + +#endif diff --git a/third_party/fidelityfx/upscalers/include/ffx_upscale.h b/third_party/fidelityfx/upscalers/include/ffx_upscale.h new file mode 100644 index 000000000..9d4a3e5f6 --- /dev/null +++ b/third_party/fidelityfx/upscalers/include/ffx_upscale.h @@ -0,0 +1,230 @@ +// This file is part of the FidelityFX SDK. +// +// Copyright (C) 2026 Advanced Micro Devices, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and /or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once +#include "../../api/include/ffx_api.h" +#include "../../api/include/ffx_api_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define FFX_UPSCALER_VERSION_MAJOR 4 +#define FFX_UPSCALER_VERSION_MINOR 1 +#define FFX_UPSCALER_VERSION_PATCH 2 + +#define FFX_UPSCALER_MAKE_VERSION(major, minor, patch) (((major) << 22) | ((minor) << 12) | (patch)) +#define FFX_UPSCALER_VERSION FFX_UPSCALER_MAKE_VERSION(FFX_UPSCALER_VERSION_MAJOR, FFX_UPSCALER_VERSION_MINOR, FFX_UPSCALER_VERSION_PATCH) + +enum FfxApiUpscaleQualityMode +{ + FFX_UPSCALE_QUALITY_MODE_NATIVEAA = 0, ///< Perform upscaling with a per-dimension upscaling ratio of 1.0x. + FFX_UPSCALE_QUALITY_MODE_QUALITY = 1, ///< Perform upscaling with a per-dimension upscaling ratio of 1.5x. + FFX_UPSCALE_QUALITY_MODE_BALANCED = 2, ///< Perform upscaling with a per-dimension upscaling ratio of 1.7x. + FFX_UPSCALE_QUALITY_MODE_PERFORMANCE = 3, ///< Perform upscaling with a per-dimension upscaling ratio of 2.0x. + FFX_UPSCALE_QUALITY_MODE_ULTRA_PERFORMANCE = 4 ///< Perform upscaling with a per-dimension upscaling ratio of 3.0x. +}; + +enum FfxApiCreateContextUpscaleFlags +{ + FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE = (1 << 0), ///< A bit indicating if the input color data provided is using a high-dynamic range. + FFX_UPSCALE_ENABLE_DISPLAY_RESOLUTION_MOTION_VECTORS = (1 << 1), ///< A bit indicating if the motion vectors are rendered at display resolution. + FFX_UPSCALE_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION = (1 << 2), ///< A bit indicating that the motion vectors have the jittering pattern applied to them. + FFX_UPSCALE_ENABLE_DEPTH_INVERTED = (1 << 3), ///< A bit indicating that the input depth buffer data provided is inverted [1..0]. + FFX_UPSCALE_ENABLE_DEPTH_INFINITE = (1 << 4), ///< A bit indicating that the input depth buffer data provided is using an infinite far plane. + FFX_UPSCALE_ENABLE_AUTO_EXPOSURE = (1 << 5), ///< A bit indicating if automatic exposure should be applied to input color data. + FFX_UPSCALE_ENABLE_DYNAMIC_RESOLUTION = (1 << 6), ///< A bit indicating that the application uses dynamic resolution scaling. + FFX_UPSCALE_ENABLE_DEBUG_CHECKING = (1 << 7), ///< A bit indicating that the runtime should check some API values and report issues. + FFX_UPSCALE_ENABLE_NON_LINEAR_COLORSPACE = (1 << 8), ///< A bit indicating that the color resource contains perceptual (gamma corrected) colors + FFX_UPSCALE_ENABLE_DEBUG_VISUALIZATION = (1 << 9), ///< A bit indicating if debug visualization is allowed. (memory consumption could increase) +}; + +enum FfxApiDispatchFsrUpscaleFlags +{ + FFX_UPSCALE_FLAG_DRAW_DEBUG_VIEW = (1 << 0), ///< A bit indicating that the output resource will contain debug views with relevant information. + FFX_UPSCALE_FLAG_NON_LINEAR_COLOR_SRGB = (1 << 1), ///< A bit indicating that the input color resource contains perceptual sRGB colors + FFX_UPSCALE_FLAG_NON_LINEAR_COLOR_PQ = (1 << 2), ///< A bit indicating that the input color resource contains perceptual PQ colors +}; + +enum FfxApiDispatchUpscaleAutoreactiveFlags +{ + FFX_UPSCALE_AUTOREACTIVEFLAGS_APPLY_TONEMAP = (1 << 0), ///< Apply tonemapping function prior to calculating reactivity. + FFX_UPSCALE_AUTOREACTIVEFLAGS_APPLY_INVERSETONEMAP = (1 << 1), ///< Apply inverse tonemapping function prior to calculating reactivity. + FFX_UPSCALE_AUTOREACTIVEFLAGS_APPLY_THRESHOLD = (1 << 2), ///< Compare to a threshold value and only record reactivity above the threshold. + FFX_UPSCALE_AUTOREACTIVEFLAGS_USE_COMPONENTS_MAX = (1 << 3), ///< When enabled use the max. component value, otherwise use the length to calculate reactivity. +}; + +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x00) +struct ffxCreateContextDescUpscale +{ + ffxCreateContextDescHeader header; ///< Header descriptor, use type FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE. + uint32_t flags; ///< Zero or a combination of values from FfxApiCreateContextUpscaleFlags. + struct FfxApiDimensions2D maxRenderSize; ///< The maximum size that rendering will be performed at. + struct FfxApiDimensions2D maxUpscaleSize; ///< The size of the presentation resolution targeted by the upscaling process. + ffxApiMessage fpMessage; ///< A pointer to a function that can receive messages from the runtime. May be null. +}; + +#define FFX_API_DISPATCH_DESC_TYPE_UPSCALE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x01) +struct ffxDispatchDescUpscale +{ + ffxDispatchDescHeader header; ///< Header descriptor, use type FFX_API_DISPATCH_DESC_TYPE_UPSCALE. + void* commandList; ///< Command list to record upscaling rendering commands into. + struct FfxApiResource color; ///< Color buffer for the current frame (at render resolution). + struct FfxApiResource depth; ///< 32bit depth values for the current frame (at render resolution). + struct FfxApiResource motionVectors; ///< 2-dimensional motion vectors (at render resolution if FFX_FSR_ENABLE_DISPLAY_RESOLUTION_MOTION_VECTORS is not set). + struct FfxApiResource exposure; ///< Optional resource containing a 1x1 exposure value. + struct FfxApiResource reactive; ///< Optional resource containing alpha value of reactive objects in the scene. + struct FfxApiResource transparencyAndComposition; ///< Optional resource containing alpha value of special objects in the scene. + struct FfxApiResource output; ///< Output color buffer for the current frame (at presentation resolution). + struct FfxApiFloatCoords2D jitterOffset; ///< The subpixel jitter offset applied to the camera. + struct FfxApiFloatCoords2D motionVectorScale; ///< The scale factor to convert motion vectors to pixel space. Internally: pixelSpaceMV = motionVector * motionVectorScale. Set to renderSize if motion vectors are in UV space [0,1], or to (1.0, 1.0) if motion vectors are already in pixel space. When using display-resolution motion vectors, set to displaySize. + struct FfxApiDimensions2D renderSize; ///< The resolution that was used for rendering the input resources. + struct FfxApiDimensions2D upscaleSize; ///< The resolution that the upscaler will upscale to (optional, assumed maxUpscaleSize otherwise). + bool enableSharpening; ///< Enable an additional sharpening pass. + float sharpness; ///< The sharpness value between 0 and 1, where 0 is no additional sharpness and 1 is maximum additional sharpness. + float frameTimeDelta; ///< The time elapsed since the last frame (expressed in milliseconds). + float preExposure; ///< The pre exposure value (must be > 0.0f) + bool reset; ///< A boolean value which when set to true, indicates the camera has moved discontinuously. + float cameraNear; ///< The distance to the near plane of the camera. + float cameraFar; ///< The distance to the far plane of the camera. + float cameraFovAngleVertical; ///< The camera angle field of view in the vertical direction (expressed in radians). + float viewSpaceToMetersFactor; ///< The scale factor to convert view space units to meters + uint32_t flags; ///< Zero or a combination of values from FfxApiDispatchFsrUpscaleFlags. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GETUPSCALERATIOFROMQUALITYMODE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x02) +struct ffxQueryDescUpscaleGetUpscaleRatioFromQualityMode +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GETUPSCALERATIOFROMQUALITYMODE. + uint32_t qualityMode; ///< The desired quality mode for FSR upscaling. + float* pOutUpscaleRatio; ///< A pointer to a float which will hold the upscaling the per-dimension upscaling ratio. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GETRENDERRESOLUTIONFROMQUALITYMODE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x03) +struct ffxQueryDescUpscaleGetRenderResolutionFromQualityMode +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GETRENDERRESOLUTIONFROMQUALITYMODE. + uint32_t displayWidth; ///< The target display resolution width. + uint32_t displayHeight; ///< The target display resolution height. + uint32_t qualityMode; ///< The desired quality mode for FSR upscaling. + uint32_t* pOutRenderWidth; ///< A pointer to a uint32_t which will hold the calculated render resolution width. + uint32_t* pOutRenderHeight; ///< A pointer to a uint32_t which will hold the calculated render resolution height. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTERPHASECOUNT FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x04) +struct ffxQueryDescUpscaleGetJitterPhaseCount +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTERPHASECOUNT. + uint32_t renderWidth; ///< The render resolution width. + uint32_t displayWidth; ///< The output resolution width. + int32_t* pOutPhaseCount; ///< A pointer to a int32_t which will hold the jitter phase count for the scaling factor between renderWidth and displayWidth. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTEROFFSET FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x05) +struct ffxQueryDescUpscaleGetJitterOffset +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GETJITTEROFFSET. + int32_t index; ///< The index within the jitter sequence. + int32_t phaseCount; ///< The length of jitter phase. See ffxQueryDescFsrGetJitterPhaseCount. + float* pOutX; ///< A pointer to a float which will contain the subpixel jitter offset for the x dimension. + float* pOutY; ///< A pointer to a float which will contain the subpixel jitter offset for the y dimension. +}; + +#define FFX_API_DISPATCH_DESC_TYPE_UPSCALE_GENERATEREACTIVEMASK FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x06) +struct ffxDispatchDescUpscaleGenerateReactiveMask +{ + ffxDispatchDescHeader header; ///< Header descriptor, use type FFX_API_DISPATCH_DESC_TYPE_UPSCALE_GENERATEREACTIVEMASK. + void* commandList; ///< The FfxCommandList to record FSRUPSCALE rendering commands into. + struct FfxApiResource colorOpaqueOnly; ///< A FfxApiResource containing the opaque only color buffer for the current frame (at render resolution). + struct FfxApiResource colorPreUpscale; ///< A FfxApiResource containing the opaque+translucent color buffer for the current frame (at render resolution). + struct FfxApiResource outReactive; ///< A FfxApiResource containing the surface to generate the reactive mask into. + struct FfxApiDimensions2D renderSize; ///< The resolution that was used for rendering the input resources. + float scale; ///< A value to scale the output + float cutoffThreshold; ///< A threshold value to generate a binary reactive mask + float binaryValue; ///< A value to set for the binary reactive mask + uint32_t flags; ///< Flags to determine how to generate the reactive mask +}; + +#define FFX_API_CONFIGURE_DESC_TYPE_UPSCALE_KEYVALUE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x07) +struct ffxConfigureDescUpscaleKeyValue +{ + ffxConfigureDescHeader header; ///< Header descriptor, use type FFX_API_CONFIGURE_DESC_TYPE_UPSCALE_KEYVALUE. + uint64_t key; ///< Configuration key, member of the FfxApiConfigureUpscaleKey enumeration. + uint64_t u64; ///< Integer value or enum value to set. + void* ptr; ///< Pointer to set or pointer to value to set. +}; + +enum FfxApiConfigureUpscaleKey +{ + FFX_API_CONFIGURE_UPSCALE_KEY_FVELOCITYFACTOR = 0, ///< Override constant buffer fVelocityFactor. The float value is casted from void * ptr. Value of 0.0f can improve temporal stability of bright pixels. Default value is 1.0f. Value is clamped to [0.0f, 1.0f]. + FFX_API_CONFIGURE_UPSCALE_KEY_FREACTIVENESSSCALE = 1, ///< Override constant buffer fReactivenessScale. The float value is casted from void * ptr. Meant for development purpose to test if writing a larger value to reactive mask, reduces ghosting. Default value is 1.0f. Value is clamped to [0.0f, +infinity]. + FFX_API_CONFIGURE_UPSCALE_KEY_FSHADINGCHANGESCALE = 2, ///< Override fShadingChangeScale. Increasing this scales fsr3.1 computed shading change value at read to have higher reactiveness. Default value is 1.0f. Value is clamped to [0.0f, +infinity]. + FFX_API_CONFIGURE_UPSCALE_KEY_FACCUMULATIONADDEDPERFRAME = 3, ///< Override constant buffer fAccumulationAddedPerFrame. Corresponds to amount of accumulation added per frame at pixel coordinate where disocclusion occured or when reactive mask value is > 0.0f. Decreasing this and drawing the ghosting object (IE no mv) to reactive mask with value close to 1.0f can decrease temporal ghosting. Decreasing this value could result in more thin feature pixels flickering. Default value is 0.333. Value is clamped to [0.0f, 1.0f]. + FFX_API_CONFIGURE_UPSCALE_KEY_FMINDISOCCLUSIONACCUMULATION = 4, ///< Override constant buffer fMinDisocclusionAccumulation. Increasing this value may reduce white pixel temporal flickering around swaying thin objects that are disoccluding one another often. Too high value may increase ghosting. A sufficiently negative value means for pixel coordinate at frame N that is disoccluded, add fAccumulationAddedPerFrame starting at frame N+2. Default value is -0.333. Value is clamped to [-1.0f, 1.0f]. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x08) +struct ffxQueryDescUpscaleGetGPUMemoryUsage +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE. + struct FfxApiEffectMemoryUsage* gpuMemoryUsageUpscaler; ///< Output values by Query() call. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE_V2 FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x09) +struct ffxQueryDescUpscaleGetGPUMemoryUsageV2 +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GPU_MEMORY_USAGE_V2. + void* device; ///< For DX12: pointer to ID3D12Device. For VK, pointer to VkDeviceContext. App needs to fill out before Query() call. + struct FfxApiDimensions2D maxRenderSize; ///< App needs to fill out before Query() call. + struct FfxApiDimensions2D maxUpscaleSize; ///< App needs to fill out before Query() call. + uint32_t flags; ///< Zero or a combination of values from FfxApiCreateContextUpscaleFlags. App needs to fill out before Query() call. + struct FfxApiEffectMemoryUsage* gpuMemoryUsageUpscaler; ///< Output values by Query() call. +}; + +enum FfxApiQueryResourceIdentifiers +{ + FFX_API_QUERY_RESOURCE_INPUT_COLOR = (1 << 0), ///< Color buffer for the current frame (at render resolution). + FFX_API_QUERY_RESOURCE_INPUT_DEPTH = (1 << 1), ///< 32bit depth values for the current frame (at render resolution). + FFX_API_QUERY_RESOURCE_INPUT_MV = (1 << 2), ///< 2-dimensional motion vectors (at render resolution if FFX_FSR_ENABLE_DISPLAY_RESOLUTION_MOTION_VECTORS is not set). + FFX_API_QUERY_RESOURCE_INPUT_EXPOSURE = (1 << 3), ///< A 1x1 texture containing exposure value or the FFX_UPSCALE_ENABLE_AUTO_EXPOSURE set at context creation. + FFX_API_QUERY_RESOURCE_INPUT_REACTIVEMASK = (1 << 4), ///< An R8 UNORM texture at render resolution that controls the influence of pixel history on upscaling result. + FFX_API_QUERY_RESOURCE_INPUT_TRANSPARENCYCOMPOSITION = (1 << 5), ///< An R8 UNORM texture at render resolution that controls the blending of color in the upscaling result. +}; + +#define FFX_API_QUERY_DESC_TYPE_UPSCALE_GET_RESOURCE_REQUIREMENTS FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x0a) +struct ffxQueryDescUpscaleGetResourceRequirements +{ + ffxQueryDescHeader header; ///< Header descriptor, use type FFX_API_QUERY_DESC_TYPE_UPSCALE_GET_RESOURCE_REQUIREMENTS. + uint64_t required_resources; ///< resources 64b bitfield, that given current context state, are required for effect correctness. + uint64_t optional_resources; ///< resources 64b bitfield, that given current context state, will be consumed if provided, but are optional. +}; + +#define FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION FFX_API_MAKE_EFFECT_SUB_ID(FFX_API_EFFECT_ID_UPSCALE, 0x0b) +struct ffxCreateContextDescUpscaleVersion +{ + ffxCreateContextDescHeader header; ///< Header descriptor, use type FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION. + uint32_t version; ///< The version of the API the application was built against. This must be set to FFX_UPSCALER_VERSION. +}; + +#ifdef __cplusplus +} +#endif From 6af436cbd8bd054c0eb29f4c4910aff9474c7091 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 9 Nov 2026 22:11:52 +0000 Subject: [PATCH 2/9] Offer the untranslated Render resolution networks to the temporal upscaler On a GPU without cooperative matrices (AMD RDNA 2) the fixed shares' own upscale networks (67% 6676619641320ba7, 58%, 50%, 33%) are left out and upscale_stub stretches the scene linearly, so DLSS or FSR only ever saw the game's 3/4. The stub now offers the frame to the temporal upscaler first. Co-Authored-By: Claude Opus 5.5 --- gpu/vk/vk_draw.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/gpu/vk/vk_draw.cpp b/gpu/vk/vk_draw.cpp index 6be2a0c0b..f5eb2f75e 100644 --- a/gpu/vk/vk_draw.cpp +++ b/gpu/vk/vk_draw.cpp @@ -5836,6 +5836,8 @@ static bool movie_keep_all_networks() { static bool typed_upscale(VkCommandBuffer cmd, VkImageView input_view, VkImageView output_view, Texture* out, uint32_t level, uint64_t key, const char* keys, bool temporal); +static bool temporal_rejected_network(VkCommandBuffer cmd, uint64_t key, Texture* src, const nvn_tex* in, Texture* dst, + const nvn_tex* out); bool upscale_stub(VkCommandBuffer cmd, const NvnState& s, uint64_t key) { static const bool off = getenv("RX_VK_NO_UPSCALE_STUB") != nullptr; @@ -5880,6 +5882,10 @@ bool upscale_stub(VkCommandBuffer cmd, const NvnState& s, uint64_t key) { if (!src || !dst || !src->image || !dst->image || (src->aspect | dst->aspect) != VK_IMAGE_ASPECT_COLOR_BIT || src->bc || dst->bc || src->samples != VK_SAMPLE_COUNT_1_BIT || dst->samples != VK_SAMPLE_COUNT_1_BIT) return false; + // (hookshot) an upscale network that did not translate here (the Render resolution's fixed shares have one each, and on a + // GPU without cooperative matrices they are left out): the temporal upscaler is offered it first, as temporal_stand_in + // offers the networks that did translate. It takes only the scene into the picture; anything else goes on below + if (temporal_wanted() && temporal_rejected_network(cmd, key, src, &in, dst, &out)) return true; VkFormatProperties fs{}, fd{}; vkGetPhysicalDeviceFormatProperties(dev.physical, src->format, &fs); vkGetPhysicalDeviceFormatProperties(dev.physical, dst->format, &fd); @@ -6124,6 +6130,34 @@ bool temporal_stand_in(VkCommandBuffer cmd, const NvnState& s, const Program& p) vertex_set.on = false, last_ib.on = false; return done; } +/// (hookshot) The same for an upscale network that did not translate on this GPU (upscale_stub found its input, the largest +/// texture it samples, and its output, the largest image it writes): the Render resolution row's fixed shares (67% +/// 6676619641320ba7, 58%, 50%, 33%) are cooperative-matrix networks, left out without cooperative matrices (AMD RDNA 2), so +/// without this DLSS and FSR would only ever see the game's 3/4. True: the temporal upscaler wrote the output +static bool temporal_rejected_network(VkCommandBuffer cmd, uint64_t key, Texture* src, const nvn_tex* in, Texture* dst, + const nvn_tex* out) { + end_pass(cmd); + VkFormat in_format = VK_FORMAT_UNDEFINED, out_format = VK_FORMAT_UNDEFINED; + const VkImageView in_view = texture_view(src, in, nullptr, VK_IMAGE_VIEW_TYPE_2D, ViewUse::Sample, &in_format); + const VkImageView out_view = texture_view(dst, out, nullptr, VK_IMAGE_VIEW_TYPE_2D, ViewUse::Storage, &out_format, + VK_FORMAT_B10G11R11_UFLOAT_PACK32); + // (the gameplay networks' own formats; the movies' (RGB9E5 bits in a shared R11 image, RGBA8) are not the scene) + if (!in_view || !out_view || in_format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || out_format != VK_FORMAT_B10G11R11_UFLOAT_PACK32) + return false; + const bool done = temporal_upscale(cmd, key, false, src, in_view, dst, out_view, 0); + last_dyn_valid = false, last_desc_valid = false, last_vb_valid = false, last_pipeline = VK_NULL_HANDLE; + vertex_set.on = false, last_ib.on = false; + if (!done) return false; + full_barrier(cmd); + texture_written(dst); + static std::mutex mu; + static std::unordered_set seen; + std::lock_guard lk(mu); + if (seen.size() < 32 && seen.insert(key).second) + logf("vk: network %016llx does not translate here: the temporal upscaler draws its upscale (%ux%u -> %ux%u)", + (unsigned long long)key, src->width, src->height, dst->width, dst->height); + return true; +} bool taau_stand_in(VkCommandBuffer cmd, const NvnState& s, const Program& p) { if (!taau_key(p.key) || !dev.push_descriptor || !vkCmdPushDescriptorSetKHR || p.stages.empty()) return false; const Stage& st = p.stages[0]; From b0b547acaee247a26d6d4ca32befca67ee628089 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 9 Nov 2026 22:19:54 +0000 Subject: [PATCH 3/9] Take the fixed shares' RGB9E5 view of the scene as the temporal upscaler's input Co-Authored-By: Claude Opus 5.5 --- gpu/vk/vk_draw.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gpu/vk/vk_draw.cpp b/gpu/vk/vk_draw.cpp index f5eb2f75e..ff6e4e6d7 100644 --- a/gpu/vk/vk_draw.cpp +++ b/gpu/vk/vk_draw.cpp @@ -6141,8 +6141,11 @@ static bool temporal_rejected_network(VkCommandBuffer cmd, uint64_t key, Texture const VkImageView in_view = texture_view(src, in, nullptr, VK_IMAGE_VIEW_TYPE_2D, ViewUse::Sample, &in_format); const VkImageView out_view = texture_view(dst, out, nullptr, VK_IMAGE_VIEW_TYPE_2D, ViewUse::Storage, &out_format, VK_FORMAT_B10G11R11_UFLOAT_PACK32); - // (the gameplay networks' own formats; the movies' (RGB9E5 bits in a shared R11 image, RGBA8) are not the scene) - if (!in_view || !out_view || in_format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || out_format != VK_FORMAT_B10G11R11_UFLOAT_PACK32) + // (the gameplay networks' own formats: their input is the square root of the scene colour, which the fixed shares' networks + // sample as RGB9E5 from the B10G11R11 image it is kept in (shaders/taau_upscale.comp), the 3/4 one as B10G11R11. Decoded, + // both are what the temporal upscaler's prep pass takes. The movies' (RGBA8) are not the scene) + if (!in_view || !out_view || out_format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 || + (in_format != VK_FORMAT_B10G11R11_UFLOAT_PACK32 && in_format != VK_FORMAT_E5B9G9R9_UFLOAT_PACK32)) return false; const bool done = temporal_upscale(cmd, key, false, src, in_view, dst, out_view, 0); last_dyn_valid = false, last_desc_valid = false, last_vb_valid = false, last_pipeline = VK_NULL_HANDLE; From 1153f5b0deb7e6ac9bb636d42a2f50323de119cc Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 9 Nov 2026 22:40:18 +0000 Subject: [PATCH 4/9] test(upscale fallback): stand-ins for the temporal upscaler's offer in upscale_stub Co-Authored-By: Claude Opus 5.5 --- tests/test_upscale_fallback.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_upscale_fallback.py b/tests/test_upscale_fallback.py index 6a590f852..217013634 100644 --- a/tests/test_upscale_fallback.py +++ b/tests/test_upscale_fallback.py @@ -124,6 +124,11 @@ def block(text, marker): p->optimalTilingFeatures &= ~VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; } void end_pass(VkCommandBuffer) {} +// (the temporal upscaler, off here: upscale_stub offers it the frame first, vk_draw.cpp temporal_rejected_network) +bool temporal_wanted() { return false; } +static bool temporal_rejected_network(VkCommandBuffer, uint64_t, Texture *, const nvn_tex *, Texture *, const nvn_tex *) { + return false; +} void full_barrier(VkCommandBuffer) {} bool dump_on() { return false; } void logf(const char *, ...) {} From 6ef649fc0fe413908b3f2a9b744630b57c1b573b Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 9 Nov 2026 22:45:48 +0000 Subject: [PATCH 5/9] test(audio output menu): its options stand-in names the Upscaler row upscaler Co-Authored-By: Claude Opus 5.5 --- tests/test_audio_output_menu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_audio_output_menu.py b/tests/test_audio_output_menu.py index 21a525023..609ce71aa 100644 --- a/tests/test_audio_output_menu.py +++ b/tests/test_audio_output_menu.py @@ -39,7 +39,7 @@ static int au_device_channels(void) { device_asked++; return device_channels; } HOST struct Row { int tab; const char* name; int* value; }; -struct { int output_res, resolution, upscale, dlss, audio_output; } opts; +struct { int output_res, resolution, upscale, upscaler, audio_output; } opts; MENU int main(int argc, char **argv) { assert(argc == 7); From c6d85204f57fac7a553fd69e922351a894a716c9 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 9 Nov 2026 23:14:58 +0000 Subject: [PATCH 6/9] Review fixes: nothing of FSR's destroyed at exit, its inputs at the render size, its Linux side built in CI - vk_fsr4.cpp lib() and st(), vk_temporal.cpp st(): leaked statics. Hookshot exits through ExitProcess / exit() with the device alive; a destructor then called ffxDestroyContext after AMD's DLL detached (hckrman101's review). - FSR's inputs are as large as the largest render size its context has had, not the output's (about 15 MB instead of 130 MB at 4K and 33%). - linux-appimage.yml builds hookshot_vk.so once more with HOOKSHOT_FSR=1, so the #ifndef _WIN32 parts of vk_fsr4.cpp and vk_main.cpp are compiled in CI. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/linux-appimage.yml | 15 +++++++++++++++ gpu/vk/vk_fsr4.cpp | 19 ++++++++++++++----- gpu/vk/vk_temporal.cpp | 8 +++++--- nx2/make_game.py | 3 +++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/.github/workflows/linux-appimage.yml b/.github/workflows/linux-appimage.yml index f02060eca..262bc93c4 100644 --- a/.github/workflows/linux-appimage.yml +++ b/.github/workflows/linux-appimage.yml @@ -87,6 +87,21 @@ jobs: - name: AppImage run: scripts/linux/appimage.sh + # (AMD FSR 4's Linux side, a build option the AppImage leaves off (gpu/vk/vk_fsr4.cpp: dlopen, the older backend + # description, the unit exposure; the descriptor buffers in vk_main.cpp): built once more in a copy of the back end's + # work folder, where only vk_fsr4.cpp compiles again. The AppImage above is not changed) + - name: hookshot_vk.so with AMD FSR 4 (build check) + run: | + cp -r build-linux/vk-work "$RUNNER_TEMP/vk-fsr" + rm -f "$RUNNER_TEMP/vk-fsr/sa/build/hookshot_vk.so" # (the AppImage's own, without FSR: the test below is this build's) + D="$PWD/build-linux/deps" + OOT_WORK="$RUNNER_TEMP/vk-fsr" EMSDK="$D/emsdk" RX_CPM="$D/cpm" RX_MARCH=x86-64-v3 RX_VK_ONLY=1 RX_RELEASE=1 HOOKSHOT_FSR=1 \ + "$D/python/bin/python3" -I nx2/build.py --standalone --jobs "$(nproc)" | + tee "$RUNNER_TEMP/vk-fsr.log" + test "${PIPESTATUS[0]}" -eq 0 + grep -q "with AMD FSR 4" "$RUNNER_TEMP/vk-fsr.log" + test -f "$RUNNER_TEMP/vk-fsr/sa/build/hookshot_vk.so" + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: Hookshot-linux-x86_64 diff --git a/gpu/vk/vk_fsr4.cpp b/gpu/vk/vk_fsr4.cpp index 5ecd83bb6..2140be21a 100644 --- a/gpu/vk/vk_fsr4.cpp +++ b/gpu/vk/vk_fsr4.cpp @@ -83,8 +83,10 @@ struct Library { uint64_t version_id = 0; VkDeviceContext device_context{}; }; +// (lib() and st() are never destroyed: Hookshot leaves through ExitProcess / exit() with the device alive, and a destructor +// then would call into AMD's library after its DLL_PROCESS_DETACH, or under a render thread still recording with it) Library& lib() { - static Library l; + static Library& l = *new Library; return l; } @@ -108,12 +110,13 @@ struct Context { /// The engine's state: used with the temporal upscaler's lock held struct State { std::shared_ptr context; + uint32_t in_w = 0, in_h = 0; // (the inputs' size under this context: the largest render size it has had) std::chrono::steady_clock::time_point last{}; uint64_t evals = 0; int told_resets = 0; }; State& st() { - static State s; + static State& s = *new State; // (never destroyed: lib()'s note) return s; } @@ -168,6 +171,9 @@ bool load() { if (GetFileAttributesA(path.c_str()) == INVALID_FILE_ATTRIBUTES) continue; // (the upscaler DLL the loader picks lies beside it: the loader's folder first in the search) l.lib = LoadLibraryExA(path.c_str(), nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + // (there but not loaded: a DLL it needs missing, a bad signature, or a relative RX_VK_FSR_DIR, which the search + // flags refuse) + if (!l.lib) logf("vk: AMD FSR 4: %s not loaded (Windows error %lu)", path.c_str(), (unsigned long)GetLastError()); #else const std::string path = dir + "/" + kLoader; if (access(path.c_str(), R_OK) != 0) continue; @@ -348,17 +354,20 @@ void release() { s.context.reset(); } -int prepare(VkCommandBuffer, uint32_t, uint32_t, uint32_t ow, uint32_t oh, uint32_t* in_w, uint32_t* in_h, const char** why) { +int prepare(VkCommandBuffer, uint32_t rw, uint32_t rh, uint32_t ow, uint32_t oh, uint32_t* in_w, uint32_t* in_h, const char** why) { State& s = st(); int made = 1; if (!s.context || s.context->width != ow || s.context->height != oh) { release(); s.context = make_context(ow, oh); if (!s.context) return *why = "its upscaler was not made", -1; + s.in_w = s.in_h = 0; made = 2; } - // (the inputs at the context's largest render size: the render size moves under it without new images) - *in_w = ow, *in_h = oh; + // (the inputs as large as the largest render size this context has had, never past the output: a fixed share keeps + // images of its own size, and the game's dynamic resolution only makes them again when it goes past them) + s.in_w = std::min(std::max(s.in_w, rw), ow), s.in_h = std::min(std::max(s.in_h, rh), oh); + *in_w = s.in_w, *in_h = s.in_h; return made; } diff --git a/gpu/vk/vk_temporal.cpp b/gpu/vk/vk_temporal.cpp index 22ab4b779..8277720c7 100644 --- a/gpu/vk/vk_temporal.cpp +++ b/gpu/vk/vk_temporal.cpp @@ -74,8 +74,8 @@ int env_switch(const char* name) { // (-1: unset) int temporal_mode() { static const int env_dlss = env_switch("RX_VK_DLSS"), env_fsr = env_switch("RX_VK_FSR"); int v = opts.upscaler; - if (env_dlss > 0) v = env_dlss == 2 ? ur::DLAA : ur::DLSS; - else if (env_fsr > 0) v = env_fsr == 2 ? ur::FSR_AA : ur::FSR; + if (env_dlss > 0) v = env_dlss >= 2 ? ur::DLAA : ur::DLSS; // (2 and up: DLAA, as RX_VK_DLSS was clamped before) + else if (env_fsr > 0) v = env_fsr >= 2 ? ur::FSR_AA : ur::FSR; if (v < 0 || v >= ur::kUpscalers) return ur::GAME; if (ur::is_dlss(v) && dlss_why() != DLSS_OK) return ur::GAME; if (ur::is_fsr(v) && fsr4_why() != FSR_OK) return ur::GAME; @@ -180,7 +180,9 @@ struct State { std::atomic holding{false}; // (an engine's state, images or frees still to do: temporal_idle has something to let go) }; State& st() { - static State s; + // (never destroyed: Hookshot leaves through ExitProcess / exit() with the device alive, and `dead` may still hold an + // engine's state then (an FSR context), whose destructor would call into a library already gone) + static State& s = *new State; return s; } diff --git a/nx2/make_game.py b/nx2/make_game.py index 65d39dbeb..58c624d97 100644 --- a/nx2/make_game.py +++ b/nx2/make_game.py @@ -1019,6 +1019,9 @@ def main(a=None): if fsr_wanted(a, out): os.environ["HOOKSHOT_FSR"] = "1" open(os.path.join(out, FSR_MARK), "w").close() + if os.environ.get("HOOKSHOT_VK_PREBUILT"): # (the AppImage's back end, built without FSR: the jitter alone is put in) + print("--fsr: this build uses the prebuilt Vulkan back end (HOOKSHOT_VK_PREBUILT), which has no AMD FSR 4: the " + "Upscaler row will not offer it", flush=True) else: os.environ.pop("HOOKSHOT_FSR", None) if os.path.isfile(os.path.join(out, FSR_MARK)): From edf4055e78028d5dc2ef887838d823c957fa6da9 Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 9 Nov 2026 23:42:15 +0000 Subject: [PATCH 7/9] Keep the Upscaler row's field named opts.dlss Renaming it to opts.upscaler broke every test that builds its own Options stand-in (test_audio_output_menu, test_stick_dead_zone). The saved key was dlss all along; the field keeps that name, documented as the Upscaler row. Co-Authored-By: Claude Opus 5.5 --- gpu/vk/upscaler_rows.h | 2 +- gpu/vk/vk_menu.cpp | 14 +++++++------- gpu/vk/vk_menu.h | 6 +++--- gpu/vk/vk_temporal.cpp | 4 ++-- gpu/vk/vk_temporal.h | 2 +- tests/test_audio_output_menu.py | 2 +- tests/test_upscaler_rows.py | 8 ++++---- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/gpu/vk/upscaler_rows.h b/gpu/vk/upscaler_rows.h index 1eb09c742..2fe37cf92 100644 --- a/gpu/vk/upscaler_rows.h +++ b/gpu/vk/upscaler_rows.h @@ -4,7 +4,7 @@ namespace mkwvk::upscaler_rows { -/// The Upscaler row (opts.upscaler, saved as dlss; RX_VK_DLSS and RX_VK_FSR over it): the game's own network, NVIDIA DLSS, +/// The Upscaler row (opts.dlss, saved as dlss; RX_VK_DLSS and RX_VK_FSR over it): the game's own network, NVIDIA DLSS, /// NVIDIA DLAA, AMD FSR 4, AMD FSR 4's native anti-aliasing (FSR_AA). Each engine has two: one from a smaller 3D, and one /// with the 3D at full scale (anti-aliasing alone) enum { GAME = 0, DLSS = 1, DLAA = 2, FSR = 3, FSR_AA = 4, kUpscalers = 5 }; diff --git a/gpu/vk/vk_menu.cpp b/gpu/vk/vk_menu.cpp index 3e8e6edfb..a76d1beea 100644 --- a/gpu/vk/vk_menu.cpp +++ b/gpu/vk/vk_menu.cpp @@ -123,7 +123,7 @@ const char* hn_reflex(int v, int variant) { if (variant < 0) return reflex_available() ? t[std::clamp(v, 0, 2)] : none; return variant == 0 ? t[std::clamp(v, 0, 2)] : variant == 1 ? none : nullptr; } -// The Upscaler (opts.upscaler): what scales the 3D up to the output. DLSS and DLAA only where dlss_why() lets them run, FSR 4 +// The Upscaler (opts.dlss): what scales the 3D up to the output. DLSS and DLAA only where dlss_why() lets them run, FSR 4 // and its native AA only where fsr4_why() does; the menu shows (and steps from) what runs, temporal_mode(), and keeps the // saved choice for a PC where it can namespace ur = upscaler_rows; @@ -299,7 +299,7 @@ const char* hn_resolution(int v, int variant) { } /// A row with nothing else to choose here: shown without its arrows, Left and Right leave it (and the saved value) alone bool row_fixed(const int* value) { - return (value == &opts.reflex && !reflex_available()) || (value == &opts.upscaler && ur::upscaler_fixed(dlss_ok(), fsr_ok())) || + return (value == &opts.reflex && !reflex_available()) || (value == &opts.dlss && ur::upscaler_fixed(dlss_ok(), fsr_ok())) || (value == &opts.resolution && ur::resolution_fixed(upscaler())) || (value == &opts.upscale && !filter_needed()) || (value == &opts.dlss_model && ur::model_fixed(upscaler(), dlss_preset_env() != nullptr)); } @@ -509,7 +509,7 @@ Row rows[] = { {TAB_GRAPHICS, "Output resolution", &opts.output_res, 0, kLastOutput, 1, tx_output_res, "The game's whole frame, HUD included, before it is scaled to the window. Docked only. Applies after a restart.", hn_output_res, true}, - {TAB_GRAPHICS, "Upscaler", &opts.upscaler, 0, ur::kUpscalers - 1, 1, tx_upscaler, "What scales the 3D up to the Output resolution.", + {TAB_GRAPHICS, "Upscaler", &opts.dlss, 0, ur::kUpscalers - 1, 1, tx_upscaler, "What scales the 3D up to the Output resolution.", hn_upscaler}, {TAB_GRAPHICS, "DLSS model", &opts.dlss_model, 0, ur::kModels - 1, 1, tx_dlss_model, "Which of NVIDIA's DLSS networks draws the picture.", hn_dlss_model, false, true}, @@ -734,7 +734,7 @@ std::string restart_line(HDC dc, int width) { /// A row on its tab: the Cheats one only with cheats on, the Upscaler only in a build with DLSS or FSR bool row_shown(int i) { return (i != kCheatsRow || cheats_enabled) && - (rows[i].value != &opts.upscaler || ur::upscaler_shown(dlss_built() || fsr4_built())) && + (rows[i].value != &opts.dlss || ur::upscaler_shown(dlss_built() || fsr4_built())) && (rows[i].value != &opts.dlss_model || ur::model_shown(dlss_built())); } int tab_row_count(int t) { @@ -794,7 +794,7 @@ const char* ini_key(const Row& r) { // (rows renamed since their values were first saved keep their keys: an options file from before still loads) if (r.value == &opts.resolution) return "resolution"; if (r.value == &opts.upscale) return "upscaling"; - if (r.value == &opts.upscaler) return "dlss"; // (its name from before FSR, as RX_VK_DLSS) + if (r.value == &opts.dlss) return "dlss"; // (as opts.dlss and RX_VK_DLSS) static char k[32]; size_t n = 0; for (const char* p = r.name; *p && n + 1 < sizeof k; p++) k[n++] = *p == ' ' ? '_' : (char)tolower(*p); @@ -917,7 +917,7 @@ void set_row(Row& r, int v, int dir, bool keep = true) { // (nothing else to choose: no change, no save. A saved choice that cannot run here is kept) if (row_fixed(r.value)) return; // (the scaling rows: only what can run here; the Render resolution row through the Upscaler's own steps) - if (r.value == &opts.upscaler) v = ur::step_upscaler(upscaler(), dir, dlss_ok(), fsr_ok()); + if (r.value == &opts.dlss) v = ur::step_upscaler(upscaler(), dir, dlss_ok(), fsr_ok()); if (r.value == &opts.resolution) v = ur::step(upscaler(), *r.value, dir); if (r.value == &opts.output_res) { // (smallest to largest: kOutputOrder) v = step_output_choice(*r.value, dir); @@ -935,7 +935,7 @@ void set_row(Row& r, int v, int dir, bool keep = true) { } else { *r.value = v > r.hi ? r.lo : v < r.lo ? r.hi : v; // (another Upscaler: the Render resolution moves to the nearest of its steps, DLSS's modes or the game's) - if (r.value == &opts.upscaler) opts.resolution = ur::snap(upscaler(), opts.resolution); + if (r.value == &opts.dlss) opts.resolution = ur::snap(upscaler(), opts.resolution); if (!r.preset_exempt) { // (the controls, the window and the counter are not the preset's) opts.preset = 0; // Custom sync_preset_budgets(0); diff --git a/gpu/vk/vk_menu.h b/gpu/vk/vk_menu.h index ce5533659..6bf558fc4 100644 --- a/gpu/vk/vk_menu.h +++ b/gpu/vk/vk_menu.h @@ -14,9 +14,9 @@ struct Options { int output_res = 0; // the game's whole frame, docked: 0 its 2048x1152, 1 2560x1440, 2 3840x2160, 3 1920x1080; read once, at the start (host/nn_applet.c) int upscale = 0; // to the window (Window scaling filter, saved as upscaling): 0 off (the plain copy), 1 Catmull-Rom, 2 Lanczos int reflex = 0; // NVIDIA Reflex: 0 off, 1 on, 2 on + boost (vk_reflex.cpp; NVIDIA GPUs with VK_NV_low_latency2) - int upscaler = 0; // the Upscaler: 0 the game's own network, 1 NVIDIA DLSS, 2 NVIDIA DLAA (a build with NVIDIA's SDK only), - // 3 AMD FSR 4, 4 its native AA (a build with FSR only). upscaler_rows.h; vk_temporal.cpp. Saved as dlss - // (its name from before FSR: an options file from then still loads) + int dlss = 0; // the Upscaler (its name from before FSR: options files and the tests' stand-ins keep it): 0 the game's + // own network, 1 NVIDIA DLSS, 2 NVIDIA DLAA (a build with NVIDIA's SDK only), 3 AMD FSR 4, 4 its native + // AA (a build with FSR only). upscaler_rows.h; vk_temporal.cpp. Saved as dlss int dlss_model = 0; // DLSS's network, by its preset: 0 K (transformer), 1 M, 2 L (second-generation transformer), 3 NVIDIA's // default (upscaler_rows.h model_preset; vk_dlss.cpp). RX_VK_DLSS_PRESET over it int sharpness = 0; // 0 to 100 diff --git a/gpu/vk/vk_temporal.cpp b/gpu/vk/vk_temporal.cpp index 8277720c7..90e89bc94 100644 --- a/gpu/vk/vk_temporal.cpp +++ b/gpu/vk/vk_temporal.cpp @@ -25,7 +25,7 @@ // Anything unexpected (no camera this frame, other sizes, the engine not there or failing) leaves the network to run, the // reason in the log when it changes. // -// The Insert menu's Upscaler row (opts.upscaler, upscaler_rows.h) chooses the engine; RX_VK_DLSS=1 / 2 (DLSS, DLAA) or +// The Insert menu's Upscaler row (opts.dlss, upscaler_rows.h) chooses the engine; RX_VK_DLSS=1 / 2 (DLSS, DLAA) or // RX_VK_FSR=1 / 2 (FSR 4, its native anti-aliasing) over it, and =0 takes that engine out for the run. // RX_VK_DLSS_DEBUG=motion: the motion vectors shown instead of the picture (|x|, |y| over 16 px as red and green, the actors' // in it); =alternate: every other frame the game's own upscale (a test's reference beside the engine's). (The names are @@ -73,7 +73,7 @@ int env_switch(const char* name) { // (-1: unset) int temporal_mode() { static const int env_dlss = env_switch("RX_VK_DLSS"), env_fsr = env_switch("RX_VK_FSR"); - int v = opts.upscaler; + int v = opts.dlss; if (env_dlss > 0) v = env_dlss >= 2 ? ur::DLAA : ur::DLSS; // (2 and up: DLAA, as RX_VK_DLSS was clamped before) else if (env_fsr > 0) v = env_fsr >= 2 ? ur::FSR_AA : ur::FSR; if (v < 0 || v >= ur::kUpscalers) return ur::GAME; diff --git a/gpu/vk/vk_temporal.h b/gpu/vk/vk_temporal.h index e8984f1db..4857967d3 100644 --- a/gpu/vk/vk_temporal.h +++ b/gpu/vk/vk_temporal.h @@ -14,7 +14,7 @@ namespace mkwvk { struct Texture; -/// What the Upscaler row runs now (upscaler_rows.h: GAME, DLSS, DLAA, FSR, FSR_AA): the row's choice (opts.upscaler), or +/// What the Upscaler row runs now (upscaler_rows.h: GAME, DLSS, DLAA, FSR, FSR_AA): the row's choice (opts.dlss), or /// RX_VK_DLSS=1 / 2 or RX_VK_FSR=1 / 2 over it; GAME when the chosen engine cannot run here int temporal_mode(); bool temporal_wanted(); // (temporal_mode() != GAME) diff --git a/tests/test_audio_output_menu.py b/tests/test_audio_output_menu.py index 609ce71aa..21a525023 100644 --- a/tests/test_audio_output_menu.py +++ b/tests/test_audio_output_menu.py @@ -39,7 +39,7 @@ static int au_device_channels(void) { device_asked++; return device_channels; } HOST struct Row { int tab; const char* name; int* value; }; -struct { int output_res, resolution, upscale, upscaler, audio_output; } opts; +struct { int output_res, resolution, upscale, dlss, audio_output; } opts; MENU int main(int argc, char **argv) { assert(argc == 7); diff --git a/tests/test_upscaler_rows.py b/tests/test_upscaler_rows.py index 608029d77..89fe1abe9 100644 --- a/tests/test_upscaler_rows.py +++ b/tests/test_upscaler_rows.py @@ -1,4 +1,4 @@ -"""The Insert menu's scaling rows: the Upscaler (opts.upscaler: the game's own, NVIDIA DLSS and DLAA, AMD FSR 4 and its native +"""The Insert menu's scaling rows: the Upscaler (opts.dlss: the game's own, NVIDIA DLSS and DLAA, AMD FSR 4 and its native AA) and the Render resolution's steps for it (gpu/vk/upscaler_rows.h), their order on the Graphics tab, options files from before still loading, and their help (why DLSS or FSR is not offered, the Sharpness DLSS wants). No game or GPU required. @@ -29,7 +29,7 @@ def main(): menu = (ROOT / "gpu" / "vk" / "vk_menu.cpp").read_text(encoding="utf-8") # the picture's way, top to bottom; Reflex with the frame rows order = ['"Frame generation", &opts.framegen', '"Frame pacing", &opts.pace', '"NVIDIA Reflex", &opts.reflex', - '"Output resolution", &opts.output_res', '"Upscaler", &opts.upscaler', '"DLSS model", &opts.dlss_model', + '"Output resolution", &opts.output_res', '"Upscaler", &opts.dlss', '"DLSS model", &opts.dlss_model', '"Render resolution", &opts.resolution', '"Window scaling filter", &opts.upscale', '"Sharpness", &opts.sharpness'] at = [menu.find(row) for row in order] @@ -41,7 +41,7 @@ def main(): raise SystemExit("vk_menu.cpp: DLSS or FSR has a row of its own beside the Upscaler") # saved options: the renamed rows keep their keys (data, so read from the source) key = function(menu, "const char* ini_key(const Row& r) {") - for value, name in (("opts.resolution", "resolution"), ("opts.upscale", "upscaling"), ("opts.upscaler", "dlss")): + for value, name in (("opts.resolution", "resolution"), ("opts.upscale", "upscaling"), ("opts.dlss", "dlss")): if not re.search(r"&%s\)\s*return\s*\"%s\"" % (re.escape(value), name), key): raise SystemExit("ini_key: %s is not saved as %s (an options file from before would lose it)" % (value, name)) # the menu's rules are upscaler_rows.h's, checked by behaviour in test_upscaler_rows.cpp: here only that the menu uses them @@ -70,7 +70,7 @@ def main(): measure = function(menu, "int measure_help(int s) {") if "r.hint(v, k)" not in measure: raise SystemExit("measure_help leaves the values' lines out: the help's room would jump as a value changes") - for row, hint in (('"Upscaler", &opts.upscaler', "hn_upscaler"), ('"DLSS model", &opts.dlss_model', "hn_dlss_model"), ('"Render resolution", &opts.resolution', "hn_resolution"), + for row, hint in (('"Upscaler", &opts.dlss', "hn_upscaler"), ('"DLSS model", &opts.dlss_model', "hn_dlss_model"), ('"Render resolution", &opts.resolution', "hn_resolution"), ('"NVIDIA Reflex", &opts.reflex', "hn_reflex"), ('"Window scaling filter", &opts.upscale', "hn_filter")): entry = menu[menu.index(row):] if hint not in entry[:entry.index("},")]: From a3a8c00386cec5532ccfdd55ddd9a5bd69d5496d Mon Sep 17 00:00:00 2001 From: Jordan Date: Mon, 9 Nov 2026 23:44:23 +0000 Subject: [PATCH 8/9] ci(linux): run the FSR build check in bash (the container's sh has no PIPESTATUS) Co-Authored-By: Claude Opus 5.5 --- .github/workflows/linux-appimage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux-appimage.yml b/.github/workflows/linux-appimage.yml index 262bc93c4..3e68ef84d 100644 --- a/.github/workflows/linux-appimage.yml +++ b/.github/workflows/linux-appimage.yml @@ -91,6 +91,7 @@ jobs: # description, the unit exposure; the descriptor buffers in vk_main.cpp): built once more in a copy of the back end's # work folder, where only vk_fsr4.cpp compiles again. The AppImage above is not changed) - name: hookshot_vk.so with AMD FSR 4 (build check) + shell: bash # (this container's default is sh, which has no PIPESTATUS) run: | cp -r build-linux/vk-work "$RUNNER_TEMP/vk-fsr" rm -f "$RUNNER_TEMP/vk-fsr/sa/build/hookshot_vk.so" # (the AppImage's own, without FSR: the test below is this build's) From edb090da3423a838060d83688b72fa3999699f89 Mon Sep 17 00:00:00 2001 From: Jordan Date: Tue, 10 Nov 2026 03:07:58 +0000 Subject: [PATCH 9/9] readme: AMD's FSR files beside the exe or in the game folder; RX_VK_FSR_DIR not from hookshot_env.txt Upstream's env-file hardening (#1398, #1422) leaves path switches out of the player's hookshot_env.txt, RX_VK_DLSS_DIR aside. RX_VK_FSR_DIR is a test switch, so it stays out; the readme now says where it can be set, and names the game folder, which the loader also searches. Co-Authored-By: Claude Opus 5.5 --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 6553bfdff..f953cd9c7 100644 --- a/readme.md +++ b/readme.md @@ -543,7 +543,8 @@ the menu (`dlss=3` or `4` in `hookshot_options.ini`, `RX_VK_FSR=1` or `2` for on - **AMD's files:** Hookshot includes only AMD's MIT-licensed API headers (`third_party/fidelityfx`). Put `amd_fidelityfx_loader_vk.dll` and `amd_fidelityfx_upscaler_vk.dll` from the [FidelityFX SDK](https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK) 2.4 release (`Kits/FidelityFX/signedbin`) - beside the game's exe (`bin\hookshot.exe`), or in a folder named by `RX_VK_FSR_DIR`. Without them the Upscaler row + beside the game's exe (`bin\hookshot.exe`) or in the game folder. (`RX_VK_FSR_DIR` names another folder, for testing: + set it in the environment or a `HOS_ENV_FILE` file; `hookshot_env.txt` takes no paths.) Without them the Upscaler row says so and offers the rest. On Linux, a Vulkan FSR 4 library (`amd_fidelityfx_upscaler_vk.so`, such as fsr4-vk's) takes their place.