diff --git a/runtime/dlssg/nx2_sl.cpp b/runtime/dlssg/nx2_sl.cpp index fc6caa2a..4756886d 100644 --- a/runtime/dlssg/nx2_sl.cpp +++ b/runtime/dlssg/nx2_sl.cpp @@ -42,9 +42,11 @@ PFun_slDLSSGGetState *p_dlssg_state; PFun_slPCLSetMarker *p_marker; PFun_slReflexSetOptions *p_reflex; PFun_slReflexSleep *p_sleep; +PFun_slReflexGetState *p_reflex_state; PFN_vkGetInstanceProcAddr p_gipa; PFN_vkGetDeviceProcAddr p_gdpa; int g_available = -1; +int g_low_latency = -1; // Reflex's low-latency sleep on this GPU and driver (ReflexState), -1 not asked int g_frames = -1; // the frames to generate as last set (0 off), -1 before the first nx2sl_frame std::atomic g_max{0}; // numFramesToGenerateMax as Streamline last reported it, 0 before it did uint64_t g_presented, g_shown; @@ -67,6 +69,16 @@ void sl_log(sl::LogType type, const char *msg) { template T *proc(const char *name) { return reinterpret_cast(GetProcAddress(g_mod, name)); } +// Whether Reflex's sleep runs on this GPU and driver: Streamline takes a frame limit without it and then sets nothing +// (its slReflexSetOptions hands the limit to the driver only with the low-latency sleep available). Asked until +// Streamline answers, from nx2sl_available on the device's thread and from nx2sl_reflex2 under the runtime's present +// lock, never at once: slReflexGetState is not thread safe. +void reflex_state() { + static sl::ReflexState rs; + if (g_low_latency < 0 && p_reflex_state && p_reflex_state(rs) == sl::Result::eOk) + g_low_latency = rs.lowLatencyAvailable ? 1 : 0; +} + // The token of frame n. Tokens come from a small ring Streamline reuses, so each call takes its own right before use. sl::FrameToken *token(uint32_t frame) { sl::FrameToken *t = nullptr; @@ -146,6 +158,8 @@ extern "C" NX2SL_API int nx2sl_available(void) { if (p_feature(sl::kFeaturePCL, "slPCLSetMarker", f) == sl::Result::eOk) p_marker = (PFun_slPCLSetMarker *)f; if (p_feature(sl::kFeatureReflex, "slReflexSetOptions", f) == sl::Result::eOk) p_reflex = (PFun_slReflexSetOptions *)f; if (p_feature(sl::kFeatureReflex, "slReflexSleep", f) == sl::Result::eOk) p_sleep = (PFun_slReflexSleep *)f; + if (p_feature(sl::kFeatureReflex, "slReflexGetState", f) == sl::Result::eOk) p_reflex_state = (PFun_slReflexGetState *)f; + reflex_state(); if (!p_dlssg_options || !p_marker) g_available = 0; // How many frames this GPU and driver generate per rendered frame: known once the device exists (else after the // first presents, nx2sl_present_marker) @@ -168,17 +182,30 @@ extern "C" NX2SL_API void nx2sl_log_unavailable(VkPhysicalDevice physical) { say("dlssg: Streamline's support check for frame generation: %s (%d)", sl::getResultAsStr(r), (int)r); } -// Streamline sets Reflex's mode on the swapchain it was told of last (VK_NV_low_latency2) and drops it while there is -// none, so it is set again after each swapchain. -extern "C" NX2SL_API void nx2sl_reflex(int mode) { - if (g_available != 1 || !p_reflex) return; +// Streamline sets Reflex's mode and frame limit on the swapchain it was told of last (VK_NV_low_latency2, the sleep +// mode's minimum interval) and drops them while there is none, so both are set again after each swapchain. Every call +// carries both: a call without the limit clears it. 1 when Streamline took them, unless Streamline said Reflex's sleep +// does not run here (then nothing would hold the limit). Where it has not said, the runtime's own check of the frames +// (dlssg_pace.h) finds a limit nothing holds. +extern "C" NX2SL_API int nx2sl_reflex2(int mode, uint32_t frame_limit_us) { + if (g_available != 1 || !p_reflex) return 0; + reflex_state(); sl::ReflexOptions o{}; o.mode = mode == 2 ? sl::ReflexMode::eLowLatencyWithBoost : mode ? sl::ReflexMode::eLowLatency : sl::ReflexMode::eOff; + o.frameLimitUs = frame_limit_us; sl::Result r = p_reflex(o); static int n; - if (n++ < 8) say("dlssg: Reflex low-latency mode %s (%d)", mode == 2 ? "on with boost" : mode ? "on" : "off", (int)r); + if (n++ < 8 || (r != sl::Result::eOk && n < 40)) { // the first calls, and later ones Streamline refused + char limit[48] = "no frame limit"; + if (frame_limit_us) snprintf(limit, sizeof limit, "frame limit %.3f ms", frame_limit_us / 1000.0); + say("dlssg: Reflex low-latency mode %s, %s (%d)", mode == 2 ? "on with boost" : mode ? "on" : "off", limit, (int)r); + } + return r == sl::Result::eOk && (!frame_limit_us || g_low_latency != 0); } +// The runtime before nx2sl_reflex2: the mode alone, no frame limit. +extern "C" NX2SL_API void nx2sl_reflex(int mode) { nx2sl_reflex2(mode, 0); } + // The game's simulation and its recording of the frame's commands are one span here: the guest's threads cannot be // told apart, so the simulation markers sit at the frame's start and the render submission runs until the frame's // submissions went out (nx2sl_submit_end). diff --git a/runtime/dlssg/nx2_sl.h b/runtime/dlssg/nx2_sl.h index fcbf4eff..caf8dfcf 100644 --- a/runtime/dlssg/nx2_sl.h +++ b/runtime/dlssg/nx2_sl.h @@ -50,6 +50,11 @@ typedef void (*PFN_nx2sl_log_unavailable)(VkPhysicalDevice physical); // Reflex's low-latency mode, 0 off, 1 on, 2 on with boost: after the device, and after each swapchain the game creates // through the interposer (the mode is the swapchain's) typedef void (*PFN_nx2sl_reflex)(int mode); +// The same with Reflex's frame limit, frame_limit_us microseconds from one frame's start to the next (0 none), which +// the driver's sleep then holds (vk_dlssg.c vk_dlssg_pace). Each call sets both, so the runtime calls only this one when +// the DLL has it. 1 when Streamline took them and, for a limit, Reflex's sleep runs on this GPU and driver. Optional: +// an older DLL lacks it, and the game's own limiter then keeps the frame limit. +typedef int (*PFN_nx2sl_reflex2)(int mode, uint32_t frame_limit_us); // Frames are numbered by the game's presents: frame n ends with its n-th present (vk_dlssg.c vk_dlssg_game_present). // Where the game starts a frame: with hold, Reflex's sleep, which holds the start until the time the driver computes // from the measured render and queue times; then the frame's simulation and render-submit-start markers.