Skip to content

feat(dlss): DLSS Frame Generation through NGX, evenly paced at 3x and up - #2118

Merged
hckrman101 merged 9 commits into
NotRexed:mainfrom
pmwork-personal:feat/dlss-frame-gen-ngx
Nov 11, 2026
Merged

hckrman101 merged 9 commits into
NotRexed:mainfrom
pmwork-personal:feat/dlss-frame-gen-ngx

Conversation

@pmwork-personal

@pmwork-personal pmwork-personal commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

On an RTX 5090 at 5120x2160 the built-in frame generation couldn't follow motion. Its block search covers ±32 px per game frame, so a camera pan's vectors fell to zero and every made frame was a cross-fade of two game frames: smoother counters, same stepping motion. This PR makes those frames with NVIDIA's DLSS Frame Generation through NGX, from the temporal upscaler's own depth and motion. It also fixes two pacing problems that made 3x feel uneven even once the frames were right.

What changed (five commits, each on its own)

  1. fix(vk): present 3x and 4x sets ahead too. Frame generation 2x: frames reach the screen 5.7 / 11 ms apart instead of 8.3 (VRR flicker), with cause and a patch #1702's present-ahead (a set's copies go to the GPU together, and only the presents wait for their times) ran for 2x only. At 3x and 4x each made frame's copy still queued behind the game's next frame and reached the screen unevenly. It now runs for any paced set the swapchain can hold, and at 3x/4x the swapchain asks for one spare image per frame (5 and 6 images on NVIDIA).
  2. feat(dlss): DLSS-G through NGX (vk_dlss.cpp, beside DLSS on the same NGX session, no Streamline):
    • Inputs. The chain's depth and motion (actor motion included) are copied at its evaluation, with the camera. The scene's projection is now kept beside its view-projection (camera block 0x70).
    • Output. At the present, DLSS-G writes the frame generation row's made frames into the renderer's own set, which the display thread shows and paces as before.
    • Fallback. A set it doesn't make is made by the existing methods, and the log says every 600 presents how many sets DLSS-G made and why it didn't make the rest.
    • Motion units. The motion goes to NGX in render pixels (MvecScale 1), the same as to DLSS. The header's "[-1, 1]" is Streamline's convention. With 1/render size, DLSS-G saw no motion and made cross-fades again; I captured frames to confirm, and it was 0.2/255 off a pure blend at t = 1/3 and 2/3.
    • Exit. The feature is let go with the queue idle.
    • Runtime. nvngx_dlssg.dll is looked for where DLSS's runtime is.
    • Menu. A Frame generation by row: Auto (DLSS-G where it runs, otherwise the built-in one) or Built-in. Both frame generation rows now sit under the Upscaler, as asked in #dev-work. The row changes at once, with no restart. RX_VK_DLSS_FG=0/1 overrides it for a run.
  3. fix(vk): V-Sync Auto presents FIFO with DLSS Frame Generation at 2x and up. Auto was mailbox, which on NVIDIA is Fast Sync: below the refresh rate (~135 shown on 165 Hz) frames land on the refresh grid, so evenly timed presents still judder. With DLSS-G making the sets, Auto now asks for FIFO, which a VRR display shows as presented. The built-in frame generation and other GPUs keep Auto as before (from review, item 5). On and Off are unchanged. The swapchain is rebuilt when this changes, as it already is for V-Sync.
  4. feat(vk): NVIDIA Reflex with frame generation (added after review). Reflex was off whenever frame generation was on, so its held-back frame added its whole delay (nullifyshot measured +12 to +18 ms at 4x on an RTX 5080). The game's frame calls stay on: the sleep, the simulation markers and render submission. Each set keeps the game frame it was made at, and only that frame is presented with its present id and present markers; made frames get neither. Present ids only go up. Without frame generation nothing changes. On the 5090 at 3x, Reflex's own timings now appear under DLSS-G: simulation start to present end ~41 ms, against ~16 ms with frame generation off (details in the comments).
  5. fix(dlss): DLSS-G only where it runs (from review). NGX isn't asked for frame generation's device extensions, or started at a present, on a device without NVIDIA's extensions. V-Sync Auto's FIFO is for DLSS-G only. Frame generation at Off or Auto lets DLSS-G's feature go. A barrier follows a refused frame before the built-in passes write the same images.

How it was tested

  • RTX 5090, driver 621.14, Windows 11, 5120x2160 @ 165 Hz with G-SYNC, DLAA, frame generation 3x, Kokiri Forest:
  • tests/run_all.py (Windows, at 90c4683): 340 passed, 28 skipped, 3 failed. The same 3 also fail on untouched main at this branch's base here:
    • test_recorder_sync's exit case;
    • test_precompile_compute_guard;
    • test_finish_output (a NameError in make_game.py; it passes on current main).
  • Updated tests:
    • test_present_ahead.py: 3x/4x with and without enough spare images.
    • test_vsync_row.py: the present-mode choice with DLSS-G and with the built-in frame generation.
    • test_upscaler_rows.py: the new row order.
    • present_source_crop_test.cpp and test_present_semaphores.py: stubs for the new calls.
    • test_recorder_sync.py: knows NGX's DLSS-G create/evaluate calls as direct recordings with their B_DIRECT boundary.

Not tested / known

🤖 Generated with Claude Code

pmwork-personal and others added 3 commits November 10, 2026 22:17
…ge for each frame

Present ahead (NotRexed#1702) submits a paced set's copies into the window together and keeps only the presents on
their times; it ran for 2x only, so at 3x and 4x each made frame's copy still queued behind the game's next
frame on the same queue and reached the screen unevenly (frame generation at 3x felt less even than
Streamline's at the same rates). It now runs for any paced set the swapchain can hold (a spare image per
frame), and the swapchain asks for that many over the surface's least when the menu is at 3x or 4x at the
start (NVIDIA: 5 and 6 images). A set the chain cannot hold is shown one at a time as before.

test_present_ahead.py: 3x and 4x with enough spare images, not with fewer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er's depth and motion

Frame generation at 5120x2160 made its frames from the finished picture alone: the block search covers
+-32 px a game frame, so a camera pan's motion fell to zero and every made frame was a cross-fade of the
two game frames (motion stepped at the game's rate). On RTX 40 and newer this makes them with NVIDIA's
DLSS Frame Generation through NGX (no Streamline), from the temporal Upscaler's own depth and motion
(actor motion included) and its camera.

- vk_dlss.cpp: DLSS-G beside DLSS, on the same NGX session. The chain's depth and motion are copied at its
  evaluation (dlss_fg_inputs) with the camera (the scene's projection is now kept beside its view-projection,
  camera block 0x70); at the present the made frames of the Frame generation row's set are evaluated into
  the renderer's own set (dlss_fg_frames), which the display thread shows and paces as before. A set it
  does not make (no Upscaler this frame, a gap, more frames than the GPU makes, a refusal) is made by the
  renderer's own methods as before, and the log counts which and why every 600 presents.
- The motion goes to NGX in render pixels (MvecScale 1), as to DLSS. With 1 / render size (the [-1, 1]
  nvsdk_ngx_defs_dlssg.h describes, which is Streamline's convention) DLSS-G saw no motion and every made
  frame was a cross-fade again: captured at 5120x2160, t = 1/3 and 2/3 and 0.2 of 255 off a pure blend;
  with 1 the same scenes came out as moved pictures between the game's.
- Insert menu: a "Frame generation by" row, Auto (DLSS-G where it runs, else the built-in one) or
  Built-in, and the Frame generation and Frame pacing rows moved under the Upscaler's (asked for in #dev-work). Its device
  extensions are asked of NGX whenever DLSS may run and the chain's images are always copyable, so the row
  changes at once; turned off, the feature is let go. RX_VK_DLSS_FG=0 / 1 over the row for a run.
- At the exit the feature is let go with the queue idle (as PR 1944's Streamline path needed).
- nvngx_dlssg.dll is looked for where DLSS's runtime is (the game folder's ngx first).

Played on an RTX 5090 (driver 621.14, Windows 11), 5120x2160 DLAA, frame generation 3x: DLSS-G made every
set in gameplay (600 / 600), ~135 frames shown from ~45 game fps; clean exits.

Tests: present_source_crop_test.cpp stubs dlss_fg_wanted / dlss_fg_frames; test_recorder_sync.py knows
NGX's frame generation calls (NGX_VK_*_DLSSG) as recordings with their B_DIRECT boundary;
test_upscaler_rows.py checks the new order (frame generation between the Render resolution and the window
rows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With V-Sync Auto the swapchain is mailbox where the driver has it: NVIDIA's Fast Sync, which puts frames on
the refresh grid whenever they come slower than the display. With frame generation at 3x (~135 shown on a
165 Hz screen) the display thread's evenly timed presents still reached the screen unevenly. Auto now asks
for FIFO when frame generation is at 2x and up, which a VRR display shows as presented; On and Off are as
before. The swapchain is made again when frame generation's multiplier changes, as for V-Sync itself
(swapchain_framegen), so its present mode and its spare images (present ahead) follow the row.

Tests: test_vsync_row.py's present-mode choice with frame generation (Auto FIFO with or without mailbox,
On and Off as before); test_present_semaphores.py stubs swapchain_framegen.

Played on an RTX 5090, 165 Hz G-SYNC, 5120x2160, DLSS Frame Generation 3x: even pacing (as with Streamline's
DLSS-G in NotRexed#1944), and the game ~45 fps instead of ~42.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Arkex-ai

Arkex-ai commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Tested exact head ec27f1f on an RTX 5070 Ti / Windows 11, 3440x1440 output, DLSS Quality 2294x960, game cap 60, FG 2x, pacing on. Fresh guest generation and matching API 14 host/backend build. NGX loaded the pinned official SR runtime and the DLSS-G runtime supplied with Streamline 2.14.16 from an isolated ngx directory; no Streamline interposer was loaded for this path.

NGX created its feature and logged repeated 600/600 sets made by DLSS-G, with zero refused and no fallback in those windows. Three controls used the same host/backend, copied checkpoint and active window. The controller reported 3440x1440 at 159 Hz; all three swapchains logged FIFO (V-Sync Auto). PresentMon 2.6 recorded actual display timestamps, separately from the native-present trace; no pixel capture during timing.

Phase Off displayed/s Built-in 2x NGX 2x
Gameplay before pause 60.03 119.88 120.05
Inventory 60.03 119.88 120.05
First second after return 59.47 119.46 118.71
Stabilized 59.98 120.23 120.14

Native rates stayed around 60 in all modes. Displayed P95 in the first return second: 18.78 ms off, 12.51 ms built-in, 12.52 ms NGX. There was no missing display feedback in those measurement windows. Normal exit from inventory returned 0 with NGX active, and both controls also exited normally. Presented hearts/instructions and inventory text were legible in the frames inspected after timing.

This confirms the NGX path runs on this RTX 50 setup. It is one run per mode in the initial interior scene, not a latency, motion-quality, 3x/4x, fixed-refresh, or campaign test. With the game capped at 60, both FG methods reach about 120 displayed/s here; these measurements do not establish an overall winner. PresentMon marked frames Application, so individual generated-frame identity comes from neither that label nor the UI screenshots; the generator attribution above comes from the NGX set log.

@nullifyshot

Copy link
Copy Markdown
Contributor

I tested this on an RTX 5080 (1920x1080 at 360 Hz, G-SYNC, driver V-Sync on, in-game V-Sync Off) at 4x, measuring input latency with DLSS-G against frame generation off. DLSS-G worked well: it made every set in gameplay ("600 made by DLSS-G" in each window). It adds a fair amount of latency, though, and part of that is because NVIDIA Reflex is off while it runs.

How I measured. The game pressed its own buttons through HOS_INPUT_LIVE. A local log line (not part of this PR) recorded when each press was due, on the QPC clock. Desktop Duplication timed the first composed frame that showed the reaction, using LastPresentTime on the same clock. That covers the game's input read through to the frame on the desktop. It leaves out the controller and the monitor. I used 20 presses per action, with Link idle in the same spot each time.

Action DLSS-G 4x Frame generation off Difference
Camera flick (right stick) 37.8 ms 25.7 ms +12.1 ms
Sword (Y) 62.5 ms 45.1 ms +17.4 ms
Walk (left stick) 64.3 ms 46.6 ms +17.7 ms

These are medians. The 10–90% range was about ±5 ms around each, and the two modes don't overlap. The game's own frame rate was about 77 fps with DLSS-G and about 99 fps with frame generation off.

Reflex is off whenever frame generation is on, DLSS-G included.

  • The code: on() in gpu/vk/vk_reflex.cpp requires !framegen_on(). That check gates the sleep, every marker and the timing log. Anti-Lag 2 has the same check. This PR doesn't touch either file, and vk_dlss.cpp never calls into Reflex.
  • In game: with Reflex set to On + Boost and RX_VK_REFLEX_LOG=1, a DLSS-G session wrote no Reflex timing report in over 10,000 frames. The log writes one every 300 frames while Reflex runs.
  • One quirk: NVIDIA Reflex: on + boost is still logged once after each swapchain rebuild. It looks like the sleep mode is re-applied when the latency state is reset. So the driver may keep Boost even though nothing is paced.

The frame-generation-off run had Reflex On + Boost and the DLSS-G run had none, so the gap above includes both frame generation's held-back frame and Reflex's pacing. NVIDIA's own integrations (Streamline) require Reflex while DLSS-G is on, to win back exactly this latency, so it seems worth having here too.

A possible fix. As far as I can tell, Reflex is off under frame generation because its per-frame calls assume one present per game frame. Its calls fall into two groups, and only the second actually conflicts:

  • Game-frame calls, all on the presentation thread in do_present:

    • SIMULATION_END and RENDERSUBMIT_START (reflex_present_begins, vk_replay.cpp:2940)
    • the latency sleep after it, followed by SIMULATION_START and INPUT_SAMPLE (ReflexSleep, vk_replay.cpp:2941)
    • RENDERSUBMIT_END (reflex_submit_ends, vk_replay.cpp:3678)

    These still describe the game's real frames with frame generation on, and the sleep that paces them is where the latency gets won back.

  • Present calls: the present id and PRESENT_START/PRESENT_END (reflex_present_id, vk_replay.cpp:3704, and reflex_presented, :3724). These run in the present path the display thread also uses for made frames, so they would mark made frames as if they were game frames. This is the part that clashes with frame generation.

So on() could be split into two checks:

  • A game-frame check: the row is on, the device is ready and the swapchain has latency mode. It no longer depends on framegen_on(), and it gates the sleep and the simulation and render-submit markers.
  • A present check: the same, plus "this present carries the game's real frame". It's always true without frame generation. With frame generation it holds only for the present of the set's real frame, and made frames get no present id or present markers. It gates reflex_present_id and reflex_presented.

RX_VK_REFLEX_LOG=1 would show it working: timing reports with frame generation on. Rerunning the measurement above would show what it wins back. Anti-Lag 2 could probably get the same split. I haven't tried any of this, so take it as a direction, not a tested patch.

…d, made frames without present ids

Reflex was off whenever frame generation was on (on() required !framegen_on()), so its held-back frame
added its whole delay: on an RTX 5080 at 4x DLSS-G measured +12 to +18 ms from input to the screen against
frame generation off with Reflex on (PR 2118's review, breakthetargets, who also laid out this split).
NVIDIA's own integrations keep Reflex on with DLSS-G for this reason.

The calls that describe the game's frame stay on with frame generation: the latency sleep, the simulation
and input markers after it, the simulation end and submission start at do_present, and the submission's end
(reflex_game_submitted, once the set's recording is submitted). The present calls run in the present path
the display thread also uses for made frames, so they now know which frame a present shows:
- framegen_record keeps the game frame the set was made at (reflex_frame) with the set;
- the display thread sets reflex_present_frame per present: the set's game frame gets that frame's present
  id and its PRESENT_START/END markers, made frames get neither (a deferred present keeps it in its
  PendingPresent until present_commit);
- present ids only go up: one that would not (frame generation turned on or off with sets in flight) is
  left off; a new swapchain starts again;
- the display thread's copies are not the game's render submission.
Without frame generation nothing changes: reflex_present_frame is kReflexThisFrame, the frame being made.
The NVIDIA Reflex row's help says it now works with frame generation.

Tests: test_reflex_swapchain_lifetime.py: a wait that turns frame generation on still marks the frame; with
frame generation the game frame's markers, a made frame without present id or markers, the set's game frame
with its own id, an id that does not go up left off. present_source_crop_test.cpp stubs reflex_frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pmwork-personal

Copy link
Copy Markdown
Contributor Author

Thanks @Arkex-ai for the 5070 Ti run, and @breakthetargets for the latency numbers and the Reflex write-up. I pushed your split as a new commit here (feat(vk): NVIDIA Reflex with frame generation):

  • Kept with frame generation: the calls that describe the game's frame. That's the latency sleep, the simulation/input markers after it, simulation end + render-submit start at do_present, and render-submit end once the set's recording is submitted (reflex_game_submitted).
  • Present calls: each set keeps the game frame it was made at (reflex_frame()). On the display thread only the set's real frame is presented with that present id and PRESENT_START/END. Made frames get neither. A deferred (present-ahead) present carries it in its PendingPresent.
  • Present ids only go up. One that wouldn't (frame generation toggled with sets in flight) is left off, and a new swapchain starts again.
  • Without frame generation nothing changes. test_reflex_swapchain_lifetime.py covers the new cases.

On the 5090 (5120x2160, DLAA, Reflex On + Boost, same spot), Reflex's own timings (RX_VK_REFLEX_LOG=1) now show up under DLSS-G. Before, it wrote none:

game fps sim start → present end
frame generation off ~61 15–16.5 ms
DLSS-G 3x ~54 41–42 ms

That's Reflex's view, so it can't show what turning Reflex on under frame generation wins back: with Reflex off there's no timing to compare against. @breakthetargets, could you rerun your input-to-screen measurement on the new head? That's the comparison that counts.

Most of the remaining gap is the interpolation itself: the real frame is shown after its set's made frames, about ⅔ of a game frame later at 3x.

@nullifyshot

Copy link
Copy Markdown
Contributor

Thanks for the quick follow-up. I reran the same test on 3a39e3f, with the same PC, settings, spot and method as my comment above. This time RX_VK_REFLEX_LOG=1 was set in both modes.

Reflex now runs with DLSS-G. The DLSS-G run wrote 59 Reflex timing reports while DLSS-G made every set. Before this commit it wrote none.

Action DLSS-G 4x + Reflex (3a39e3f) Frame generation off, Reflex on Gap Gap before 3a39e3f
Camera flick 38.3 ms 25.3 ms +13.0 ms +12.1 ms
Sword (Y) 62.9 ms 47.6 ms +15.3 ms +17.4 ms
Walk 61.6 ms 48.9 ms +12.7 ms +17.7 ms

These are medians of 20 presses each. The walk gap shrank by about 5 ms and the sword gap by about 2 ms, but part of that may be run-to-run noise. Overall, the latency barely changed. Reflex's own timings show why:

Simulation start → present end Simulation Game's own frames/s
Frame generation off 10.1 ms 9.5 ms ~97
DLSS-G 4x 20.9 ms 12.0 ms ~77
  • Off: a frame reaches its present about 0.6 ms after its simulation ends. This scene isn't GPU-bound, so there's no queue for Reflex's sleep to drain, with or without frame generation.
  • DLSS-G: the extra ~11 ms is the game frame held back while the set's made frames are shown first. That's built into interpolation, and Reflex can't remove it. Each game frame also takes longer (12.0 vs 9.5 ms of simulation, ~77 vs ~97 fps). If the presentation thread is waiting on frame generation's work, that may be worth a look too.

So the change looks right. It should pay off in GPU-bound scenes, where frames do queue up: higher resolution, heavier settings or a slower card.

One thing looks off: the GPU-end timing with frame generation. With frame generation on, Reflex's "to the GPU's end" reads 243–442 ms, next to about 21 ms from simulation start to present end. With frame generation off it reads a sensible 12.1 ms. It looks like the driver is pinning the GPU's end of a frame on later work.

A possible fix (untested, just a direction). Hookshot never chains VkLatencySubmissionPresentIdNV to its queue submits. That's VK_NV_low_latency2's way of telling the driver which frame a submission's GPU work belongs to. Without frame generation the driver seems to infer it correctly. With frame generation, the display thread's copy and DLSS-G submits sit between the game's frames, and it probably gets them wrong. What I'd try is tagging every submit through one helper, much like reflex_present_id, for example reflex_submit_id(const void* next):

  • The game's rendering: submit_recording, including the batched path through queue_flush_locked, and the parallel recording jobs' submit (vk_replay.cpp around line 1555). Chain VkLatencySubmissionPresentIdNV with the frame being made (r.id while frame_marked, between its simulation start and its render-submit end).
  • present_image's submit (around line 3682): use the same rule as the present id. That's r.id without frame generation, the set's game frame (reflex_present_frame) for the present that carries it, and no tag for made frames. That way the made frames' copy and DLSS-G work isn't counted as the next game frame's rendering.

If this is the cause, RX_VK_REFLEX_LOG=1 should show "to the GPU's end" back below "simulation start to present end" with frame generation on, as it is with frame generation off. The driver would then also be pacing on correct GPU timings. I haven't tried this, so it's a lead to check, not a confirmed diagnosis.

…let go at Off or Auto

From the review:
- NGX is not asked for frame generation's device extensions on a device
  without NVIDIA's NVX extensions (AMD, Intel), and fg_start does not start
  NGX where dlss_why() says DLSS does not run.
- V-Sync Auto's FIFO under frame generation is limited to DLSS-G making the
  sets (swapchain_dlssg), where it was measured; the renderer's own methods
  and other GPUs keep Auto's mailbox or immediate. The swapchain is made
  again when that changes.
- A frame NGX refuses after the first: a barrier before the renderer's own
  passes write the same images.
- Frame generation at Off or Auto lets DLSS-G's feature go (dlss_fg_idle),
  as a change of the Frame generation by row does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pmwork-personal

Copy link
Copy Markdown
Contributor Author

Thanks for the review. The four inline points are fixed in a new commit, 90c4683 fix(dlss): DLSS-G only where it runs (replies in each thread). The Reflex submit tagging (VkLatencySubmissionPresentIdNV) is now in the description as a follow-up.

From #1831's side:

  • Method choice as a pure header with a unit test: I can move the Frame generation by choice into a small header with a test here, if that helps the review.
  • DLSS-G without a temporal upscaler: here its inputs come from the temporal chain, because that's where the actor motion is. DLAA (the Upscaler's Native) covers the no-upscaling case. Decoupling it could be a follow-up.
  • Single/pair/no-weight actor-motion layouts: I'll open them as their own PR next, with credit.

Agreed that the choice between this and #1831 is @NotRexed's.

pmwork-personal and others added 3 commits November 11, 2026 11:17
…back to its mode

From review: after create_failures >= 3 dlss_fg_frames refuses every set,
but dlss_fg_runs still said yes, so V-Sync Auto stayed FIFO while the
built-in method made the sets. Giving up now sets start_failed, which
dlss_fg_runs reads; present_image makes the swapchain again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pmwork-personal

Copy link
Copy Markdown
Contributor Author

Thanks for the approval. Main is merged in again (#2042 touched the Reflex row), and the nit is fixed in 7c13821. I'll take the method choice as a small header with a unit test in the follow-up, together with DLSS-G without a temporal upscaler, and the single/pair/no-weight actor-motion layouts as their own PR, with credit to FIocker.

…-ngx

# Conflicts:
#	gpu/vk/vk_framegen.h
#	gpu/vk/vk_internal.h
#	gpu/vk/vk_main.cpp
#	gpu/vk/vk_replay.cpp
@hckrman101
hckrman101 merged commit c51df0b into NotRexed:main Nov 11, 2026
11 of 12 checks passed
unixman69 added a commit to unixman69/Hookshot that referenced this pull request Nov 11, 2026
NotRexed#2118's DLSS Frame Generation (NGX) copies the chain's depth each frame and
told NGX it was not reversed. With FSR 3.1 on the chain the depth is 1 - d
(dlss_prep.comp bit 3), so generated frames under FSR 3.1 would have had
their occlusion backwards. The frame's depth_reversed now goes with the
copied inputs (Fg::depth_reversed) and into
NVSDK_NGX_DLSSG_Opt_Eval_Params::depthInverted. DLSS and FSR 4 are
unchanged (their depth is not reversed).

test_upscaler_rows checks both lines; it fails with the hard-coded false.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
notquiteog added a commit to notquiteog/Hookshot that referenced this pull request Nov 11, 2026
… the fork's side

Upstream's 73 commits since the last merge, with its NVIDIA DLSS Frame
Generation through NGX, taken as it is: the fork's own vk_dlssg.cpp and
dlssg_math.h are gone. Upstream measured in the game that NGX takes motion
in render pixels, which the fork's version had wrong from the headers.

Frame generation: upstream's Frame generation by row (Auto, Built-in)
keeps its key and values, and the fork adds AMD FSR 4 (vk_ffx.cpp) and
AMD FSR 3.1 after them where they run. Neither is ever Auto's: both are
given the picture with the HUD in it. A set a method does not make whole
is the renderer's own.

Neural rendering: runs once for each of the game's frames, before frame
generation, on the picture's part of the window image alone (it was run
over all 2560x1440 of a 2048x1152 picture), and is left out of a native
HDR picture. Two rows the RenoDX DLSS add-on has are new: Passes (1 to
10, a feature or a Streamline viewport a pass, local tone on the first
alone) and Resolution scale (50 to 100%: the model on a smaller copy,
its detail added back at full size, shaders/nr_detail.comp).

Every fork backend that hands the command buffer to someone else's code
goes through the recorder (B_DIRECT before, note_work after), and
tests/test_recorder_sync.py checks each with a control.

Run in the game on an RTX 5060 Ti, Linux (docs/upscaling.md): each
upscaler that exists there, DLSS Frame Generation at 2x to 4x, Built-in,
AMD FSR 3.1's interpolation, and the open neural rendering network with
and without frame generation and at 50% scale. Windows-only code (AMD
FSR 4, Streamline) and NVIDIA's neural rendering model, which has no
Linux runtime, are compiled for Windows and not run.

Tests: 413 passed, 12 skipped (Windows-only or missing tools).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
liska1304 added a commit to liska1304/Hookshot that referenced this pull request Nov 12, 2026
Frame generation no longer turns Anti-Lag 2 off. As NVIDIA Reflex's
markers with frame generation (NotRexed#2118), each set keeps the game frame it
was made at (antilag_frame), and only the set's game frame gives that
frame's present stage (antilag_present_frame, kept by a deferred present
until its commit); the made frames' presents give none. Without frame
generation nothing changes. The row's hint takes the Reflex row's
wording and translations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants