Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/linux-appimage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ 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)
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)
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
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/vulkan-dlss.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
# 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:
push:
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'
Expand All @@ -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'
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only compiles the Windows side. All the #ifndef _WIN32 code in vk_fsr4.cpp (dlopen, the _DEPRECATED backend chain, unit_exposure) and the descriptor-buffer feature in vk_main.cpp never gets compiled in CI, and the PR says Linux wasn't tested. Because it's header-only, a cheap step in an existing Linux job (for example HOOKSHOT_FSR=1 on the AppImage/source-checks vk build, or a single -fsyntax-only of vk_fsr4.cpp with -DHOOKSHOT_FSR -Ihost/posix) would at least keep it building.

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
Expand Down
5 changes: 5 additions & 0 deletions builder/HookshotBuilder/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@
Content="NVIDIA DLSS (downloads NVIDIA's SDK under NVIDIA's license)"/>
<TextBlock Style="{StaticResource Note}" FontSize="12" Margin="26,4,0,0" TextWrapping="Wrap"
Text="For NVIDIA RTX graphics cards. NVIDIA's files stay on this PC: never share a game folder built with them."/>
<!-- AMD FSR 4: off unless ticked. Nothing is downloaded: AMD's DLLs are put beside the game by hand (the readme) -->
<CheckBox x:Name="FsrTick" Style="{StaticResource Tick}" IsChecked="False" Margin="0,10,0,0"
Content="AMD FSR 4 (AMD's files are not included: see the readme)"/>
<TextBlock Style="{StaticResource Note}" FontSize="12" Margin="26,4,0,0" TextWrapping="Wrap"
Text="For AMD Radeon graphics cards. It is chosen in the game's Insert menu, as the Upscaler."/>
</StackPanel>

</Grid>
Expand Down
5 changes: 4 additions & 1 deletion builder/HookshotBuilder/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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;
Expand All @@ -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));
}
Expand Down Expand Up @@ -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);
}

Expand Down
6 changes: 6 additions & 0 deletions gpu/PROVENANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 3 additions & 1 deletion gpu/vk/status_line.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
67 changes: 41 additions & 26 deletions gpu/vk/upscaler_rows.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.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 };
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";
Expand All @@ -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++)
Expand All @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Loading
Loading