mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Add the reflex_limit setting (NX2NAVI_REFLEX_LIMIT, default 1, live), which gives the frame limit back to the game's pacer with DLSS Frame Generation.
Reflex's hold of the frame limit with DLSS-G is unmeasured here (no RTX 40 or 50), so a player needs a way back to the pacing of 0.10.1 without an older DLL. vk_dlssg.c limit_allowed reads the setting through cfg_bool, again whenever a setting changed (cfg_version), as vk_lowlat.c reads low_latency; with 0 the presenting guest thread asks Reflex for no limit, the present thread hands Streamline 0, and the "frame rate:" line names NX2NAVI_REFLEX_LIMIT=0 as the reason the game's own limiter holds it. The row sits beside FG_MULT in metas[], beside REFLEX in known[] and beside LOW_LATENCY in sections[] (display), and nx2navi.ini.example explains it under framegen_multiplier. cfg_rows_check reports 0 problems; vk_dlssg.c and cfg.c compile clean with the dev build's flags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -334,7 +334,7 @@ keeps per-core data without locks and relies on that, so the host models it (`ru
|
||||
(`frame_rate.c` `frame_step_ceiling_ns`). Since the SDK does not say whether the driver's interval counts the frames
|
||||
the game draws or every frame on screen, the frames are checked: about the multiplier periods apart switches Reflex
|
||||
to the period over the multiplier once, and the same again or frames too soon give the limit to the pacer for the
|
||||
run.
|
||||
run. `NX2NAVI_REFLEX_LIMIT=0` (live) leaves the limit to the pacer, as before Reflex's frame limit was used.
|
||||
The device gets the drivers' extensions only in a run that starts with the setting on (`vk_device.c`
|
||||
`create_device`): turning it on takes a restart, and with it off the device is made as it was before the mode.
|
||||
- ASTC textures are uploaded as they are where the device samples ASTC, and decoded by a compute shader everywhere
|
||||
|
||||
@@ -782,6 +782,14 @@
|
||||
# Each step adds a little latency, and a display shows no more than its refresh rate. Changes while the game runs
|
||||
# take effect at once.
|
||||
# framegen_multiplier = 2
|
||||
# With framegen = dlss, Reflex holds fps_limit where each frame starts, before the controller is read, and the game's
|
||||
# own limiter keeps only its ceiling of 240 frames a second: a finished picture no longer waits for its turn, which
|
||||
# takes up to a frame of the game's off the delay from the controller to the screen. The game's own limiter takes the
|
||||
# limit back while a movie plays, while the window is minimised, and for the rest of the run when the frames show that
|
||||
# Reflex does not hold it; the log's "frame rate:" lines say which one holds it. 0 leaves the limit to the game's own
|
||||
# limiter, as before 0.10.2. Needs the nx2navi_dlssg.dll of 0.10.2 or later (an older one leaves the limit to the
|
||||
# game). Changes while the game runs take effect at once.
|
||||
# reflex_limit = 1
|
||||
# Low latency without frame generation: 0 off, 1 on, 2 on with boost (NVIDIA's: the GPU keeps a high clock; on AMD 2
|
||||
# is 1). The graphics driver holds each frame's start, so the game reads the controller as late as the picture allows
|
||||
# and no finished picture waits for its turn: NVIDIA's low-latency mode (VK_NV_low_latency2) or AMD's Anti-Lag
|
||||
|
||||
@@ -67,7 +67,7 @@ static const char *const known[] = {
|
||||
"POOL_FAST", "POOL_GROUP", "POOL_GROUP_CHECK", "POOL_PRESNAP", "POOL_PRESNAP_RUNS",
|
||||
"SUBMIT_FINISH_GPU", "SVC_WAITS", "SNAP_WIDEN", "SUBMIT_FINISH_RESULTS", "SUBMIT_FINISH_KINDS", "FOLIAGE", "MIRROR_STAGE", "FLUSH_SKIP_CLEAN", "MIRROR_HOT", "MESH_EMULATION", "MESH_EMU_MB", "MESH_EMU_TASKS", "MESH_EMU_BATCH", "GUEST_OPT", "LOG_TEX_NEW", "DUMP_KEY", "VEC_INDEX_FIX", "PASS_TIMES",
|
||||
"PROFILE_MAIN", "PROFILE_RENDER", "PROMPTS", "PUSH_SKIP", "PUSH_STAGE", "RDOC", "RDOC_DIR", "RDOC_DLL", "REALTIME", "REC_END", "REEXEC",
|
||||
"REFLEX", "LOW_LATENCY", "RENDER_SCALE", "REPLAY_ASYNC", "REPLAY_PIPE", "REPLAY_RUNS", "REPLAY_SMALL", "TAIL_EARLY", "REPLAY_WORKERS", "RTSS", "RTSS_ENUM", "SCENE_SHARE",
|
||||
"REFLEX", "REFLEX_LIMIT", "LOW_LATENCY", "RENDER_SCALE", "REPLAY_ASYNC", "REPLAY_PIPE", "REPLAY_RUNS", "REPLAY_SMALL", "TAIL_EARLY", "REPLAY_WORKERS", "RTSS", "RTSS_ENUM", "SCENE_SHARE",
|
||||
"PRESNAP_PLACE",
|
||||
"REPLAY_CHECK", "RESOLVE_CHECK", "RESTART_WAIT", "ROMFS", "ROMFS_INDEX", "ROMFS_MMAP", "ROMFS_MODS", "ROMFS_PATCHES",
|
||||
"ROMFS_PROOF_DIR", "ROMFS_VERIFY", "RSTICK_DEADZONE", "RUMBLE", "RUMBLE_CURVE", "RUMBLE_HD", "RUMBLE_HD_MS", "RUMBLE_STRENGTH", "RUMBLE_TRIGGERS", "SHADER_CACHE",
|
||||
@@ -127,7 +127,7 @@ static const struct { const char *key, *section; } sections[] = {
|
||||
{"HDR_UI_NITS", "display"}, {"SHADOW_RES", "display"},
|
||||
{"DLAA", "display"}, {"FSR4", "display"}, {"FSR4_FG", "display"}, {"DLSS", "display"}, {"DLSS_PRESET", "display"}, {"DLSS_IDLE_FREE", "display"}, {"DLSS_RANGE", "display"}, {"DLSS_PREBLOOM", "display"}, {"DLSS_CHAR_MASK", "display"}, {"FXAA", "display"}, {"AA", "display"},
|
||||
{"FRAMEGEN", "display"}, {"FRAMEGEN_MULTIPLIER", "display"}, {"FG_MULT", "display"}, {"SKIN_MOTION", "display"},
|
||||
{"LOW_LATENCY", "display"},
|
||||
{"LOW_LATENCY", "display"}, {"REFLEX_LIMIT", "display"},
|
||||
{"DEVICE_PROFILE", "display"}, {"ASSUME_FOCUS", "display"},
|
||||
{"DISCORD_RPC", "discord"}, {"DISCORD_APP_ID", "discord"}, {"DISCORD_IMAGE", "discord"},
|
||||
{"SHADOW_SCALE", "display"}, {"SHADOW_RANGE", "display"}, {"CONTACT_SHADOWS", "display"},
|
||||
@@ -221,6 +221,9 @@ static const CfgMeta metas[] = {
|
||||
{"FRAMEGEN_MULTIPLIER", "2", "2|3|4", LIVE}, // vk_dlssg.c: frames on screen per rendered frame with DLSS
|
||||
// the same where DLSS's frame generation runs through NGX itself (gfx_present.c ng_multiplier): 2x to 6x or dynamic
|
||||
{"FG_MULT", "2", "2|3|4|5|6|dynamic", LIVE},
|
||||
// vk_dlssg.c limit_allowed: with DLSS Frame Generation, Reflex holds the frame limit where each frame starts; 0 leaves
|
||||
// it to the game's own limiter
|
||||
{"REFLEX_LIMIT", "1", "0|1", LIVE},
|
||||
{"DLSS_CHAR_MASK", "65", "0|35|65|100", LIVE}, {"FXAA", "1", "0|1", LIVE},
|
||||
// vk_skin_motion.inc skin_enabled: motion vectors of their own for every skinned character, not the player alone
|
||||
{"SKIN_MOTION", "1", "0|1", RESTART},
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
// game's own pacer then keeps only the game's ceiling (vk_dlssg_pace, nvn_api_obj.c nvnQueuePresentTexture): the pacer's
|
||||
// wait came after the picture was drawn, so the pad was read up to a period before the picture left, and Reflex's sleep
|
||||
// at the frame's start already waited for the GPU. The pacer keeps the limit while a movie plays, before the first host
|
||||
// present, while none went out for DLSSG_HOLD_IDLE_NS, with a DLL older than nx2sl_reflex2 or a limit Streamline
|
||||
// refused, and for the rest of the run once the frames show Reflex does not hold it (dlssg_pace.h). The guest thread
|
||||
// that presents decides and the present thread hands the limit to Streamline before its present (vk_dlssg_frame, under
|
||||
// the present lock, which keeps Streamline's options calls on one thread at a time); the pacer stands aside only once
|
||||
// Streamline took that limit.
|
||||
// present, while none went out for DLSSG_HOLD_IDLE_NS, with NX2NAVI_REFLEX_LIMIT=0 (live), a DLL older than
|
||||
// nx2sl_reflex2 or a limit Streamline refused, and for the rest of the run once the frames show Reflex does not hold it
|
||||
// (dlssg_pace.h). The guest thread that presents decides and the present thread hands the limit to Streamline before
|
||||
// its present (vk_dlssg_frame, under the present lock, which keeps Streamline's options calls on one thread at a time);
|
||||
// the pacer stands aside only once Streamline took that limit.
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
@@ -115,9 +115,15 @@ static void reflex_apply(uint32_t us) {
|
||||
__atomic_store_n(&S.limit_set, ok ? us : 0, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
// Reflex may hold the frame limit: a DLL with nx2sl_reflex2 whose limit Streamline did not refuse.
|
||||
// Reflex may hold the frame limit: NX2NAVI_REFLEX_LIMIT (default 1, live; 0 leaves the limit to the game's own limiter,
|
||||
// as before Reflex's frame limit was used), and a DLL with nx2sl_reflex2 whose limit Streamline did not refuse. The
|
||||
// setting is parsed again when a setting changed (cfg_version). Only the presenting guest thread calls it.
|
||||
static int limit_allowed(void) {
|
||||
return S.reflex2 && !__atomic_load_n(&S.limit_refused, __ATOMIC_ACQUIRE);
|
||||
static unsigned seen;
|
||||
static int read, on;
|
||||
unsigned v = cfg_version();
|
||||
if (!read || v != seen) on = cfg_bool("NX2NAVI_REFLEX_LIMIT"), seen = v, read = 1;
|
||||
return on && S.reflex2 && !__atomic_load_n(&S.limit_refused, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
// With any upscaler or none: vk_dlaa.c makes the inputs either way (vk_dlaa_frame says which, at the start and at each
|
||||
@@ -380,7 +386,8 @@ int vk_dlssg_pace(uint64_t period_ns) {
|
||||
if (held) pace_say(1, want, NULL);
|
||||
else if (!want) // (a limit Streamline has yet to take is no change: the present thread hands it over next)
|
||||
pace_say(0, 0, !allowed ? (!S.reflex2 ? "nx2navi_dlssg.dll is older than Reflex's frame limit"
|
||||
: "Streamline refused the limit")
|
||||
: __atomic_load_n(&S.limit_refused, __ATOMIC_ACQUIRE) ? "Streamline refused the limit"
|
||||
: "NX2NAVI_REFLEX_LIMIT=0")
|
||||
: S.pace.bad ? "Reflex did not hold the limit"
|
||||
: !period_ns ? "a movie plays, or no limit"
|
||||
: hosted ? "no host present for 200 ms"
|
||||
|
||||
Reference in New Issue
Block a user