Files
NX2navi/runtime/vk_dlssg.h
chrg6 dc3f424fde Let Reflex hold the frame limit with DLSS Frame Generation, and the game's pacer keep only its ceiling while it does.
With DLSS-G, Reflex's sleep held each frame's start with no limit while the game's pacer still held the limit on the
present thread and at the window image's acquire (A-16, P-16): the pacer's wait came after the picture was drawn, so
the pad was read up to a period before the picture left, on top of Reflex's own wait for the GPU.
vk_dlssg.c vk_dlssg_pace, on the presenting guest thread in nvnQueuePresentTexture, asks dlssg_pace.h which limit Reflex
should hold and stores it; vk_dlssg_frame on the present thread hands a changed limit to Streamline before its present
(reflex_apply, under the present lock, which keeps Streamline's options calls on one thread at a time), as does each
new swapchain, which has neither mode nor limit. GfxBackend.frame_paced now returns the period the pacer still holds:
vk_backend.c vk_be_frame_paced gives the game's ceiling (frame_step_ceiling_ns, 240) while Reflex holds the limit,
0 where a driver's low latency holds it (vk_lowlat_pace, unchanged), else the period; during a movie the back ends get
0 and the pacer keeps the movie's period and catch-up slots as before. The pacer takes the limit back before the first
host present, after 200 ms without one, with a DLL older than nx2sl_reflex2 (one log line at start), for a limit
Streamline refused, and for the run when the frames show Reflex does not hold it; the "frame rate:" lines name who
holds it, the "dlssg: present N" line counts the frame starts Reflex held, and Reflex's sleep now counts as pacing
(perf_end_any, on whichever thread acquires). No wait is added on a drawing thread: the limiter's wait moves from the
acquire's sleep_until and the present thread's slot to Reflex's sleep a few lines later in the same acquire, and the
present thread's options call returns at once. docs/architecture.md names Reflex as a limiter. Not reproduced before
the change (no RTX 40 or 50 here); vk_dlssg.c, vk_backend.c and nvn_api_obj.c compile clean with the dev build's
flags, and dlssg_pace_check, fg_reset_check and frame_step_check pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 20:12:49 -05:00

55 lines
4.4 KiB
C

// DLSS Frame Generation through NVIDIA Streamline (vk_dlssg.c, nx2navi_dlssg.dll).
#pragma once
#include <stdint.h>
#include <vulkan/vulkan.h>
int vk_dlssg_wanted(void); // NX2NAVI_FRAMEGEN = dlss, with any upscaler or none (read once)
int vk_dlssg_early(void); // before the instance exists: loads the DLL and Streamline; 1 when its proxies are there
// vk_device.c: the instance and device through Streamline's proxies when vk_dlssg_early succeeded, else the loader's
VkResult vk_dlssg_create_instance(const VkInstanceCreateInfo *ci, VkInstance *out);
VkResult vk_dlssg_enumerate(VkInstance inst, uint32_t *n, VkPhysicalDevice *pd); // through the interposer too
PFN_vkVoidFunction vk_dlssg_instance_proc(VkInstance inst, const char *name); // the interposer's, or NULL
VkResult vk_dlssg_create_device(VkInstance inst, VkPhysicalDevice pd, const VkDeviceCreateInfo *ci, VkDevice *out);
void vk_dlssg_device_ready(VkPhysicalDevice physical, VkDevice dev); // after the device: checks DLSS-G and takes the swapchain hooks
PFN_vkGetDeviceProcAddr vk_dlssg_dpa(void); // Streamline's device entry points, or NULL when it is not in use
int vk_dlssg_active(void);
// gfx_present.c's swapchain calls while active
VkResult vk_dlssg_create_swapchain(VkDevice d, const VkSwapchainCreateInfoKHR *ci, VkSwapchainKHR *sc);
void vk_dlssg_destroy_swapchain(VkDevice d, VkSwapchainKHR sc);
VkResult vk_dlssg_get_images(VkDevice d, VkSwapchainKHR sc, uint32_t *n, VkImage *img);
VkResult vk_dlssg_acquire(VkDevice d, VkSwapchainKHR sc, uint64_t timeout, VkSemaphore sem, uint32_t *idx);
VkResult vk_dlssg_present(VkQueue q, const VkPresentInfoKHR *pi);
void vk_dlssg_frame(VkCommandBuffer cb, VkExtent2D ext); // each present, before its command buffer is submitted
// Frames are numbered by the game's presents (frame n ends with the n-th). Reflex's frame timing while active:
void vk_dlssg_frame_start(void); // the game starts a frame (nvn_api_obj.c nvnWindowAcquireTexture): Reflex's sleep
// The game presents (nvn_api_obj.c nvnQueuePresentTexture, through vk_backend.c vk_be_frame_paced) with the pacer's
// period, 0 during a movie: 1 when Reflex holds the frame limit where the frame starts (dlssg_pace.h), so the pacer
// keeps only the game's ceiling. Atomics only, no wait; 0 while DLSS-G does not run.
int vk_dlssg_pace(uint64_t period_ns);
void vk_dlssg_game_present(void); // the game presents it (vk_backend.c vk_be_present): counts it
// its submissions went out (vk_backend.c: vk_be_present, or replay_main with asynchronous replay): the render-submit-end
// marker; frame is gfx_counters.frames of its present, the same count
void vk_dlssg_submitted(uint64_t frame);
void vk_dlssg_present_frame(uint64_t frame); // the present thread's next blit and present are of this frame
uint64_t vk_dlssg_shown(void); // the frames the display got, generated ones included (present thread); 0 when off
void vk_dlssg_release(void);
// The multiplier: the frames the display gets per rendered frame, 2, 3 or 4 (one to three generated), set by
// NX2NAVI_FRAMEGEN_MULTIPLIER and applied at once.
// What a setting value gives where the GPU and driver allow up to max (2 to 4): the value when it is 2, 3 or 4 ("3x"
// too) and at most max, else the nearest that is; unset or empty, 2. *why (may be NULL): the reason the value was not
// taken as it is, for the log, or NULL.
int vk_dlssg_multiplier_of(const char *value, int max, const char **why);
// The highest multiplier while DLSS-G runs: 2 to 4 from Streamline's numFramesToGenerateMax; 0 while DLSS-G does not
// run, or before Streamline reported its limit (the first frames; one frame is generated until then). *old_dll (may be
// NULL): 1 when the limit of 2 is nx2navi_dlssg.dll's, a build older than its nx2sl_frames_max, not the GPU's.
int vk_dlssg_multiplier_max(int *old_dll);
// The frames the display gets per game frame now: the multiplier while generation runs, 1 while it is off or paused
// (the menu). frame_rate.c paces the game at the frame limit over it. Any thread.
int vk_dlssg_multiplier(void);
// Why DLSS-G runs or not: DLSSG_NOT_WANTED (NX2NAVI_FRAMEGEN was not dlss at start, or no DLSS or FSR 3.1 upscaling),
// DLSSG_NO_FILES (nx2navi_dlssg.dll, Streamline or its swapchain hooks missing or failing), DLSSG_UNAVAILABLE (Streamline
// loaded but DLSS-G or a required plugin function was unavailable).
enum { DLSSG_NOT_WANTED, DLSSG_RUNS, DLSSG_NO_FILES, DLSSG_UNAVAILABLE };
int vk_dlssg_status(void);