Files
NX2navi/runtime/vk_lowlat.c

537 lines
30 KiB
C

// Low latency without frame generation (NX2NAVI_LOW_LATENCY: 0 off, 1 on, 2 on with boost; default 0). In a run that
// starts with it on, a change takes effect at once; a run that starts with it off makes its device without the
// drivers' extensions (vk_device.c create_device), so turning it on then takes a restart.
//
// A frame's delay from the pad to the screen has two parts the runtime can shorten. The first is the time the frame
// waits after it is drawn: the frame limit used to hold a finished picture back until its slot (vk_backend.c
// present_main), so the pad was read up to a whole period before the picture left. The second is the queue in front of
// the GPU: when the GPU is the slower side, the game draws frames faster than they are shown and each waits its turn.
// Both go when the wait moves to the frame's start, before the pad is read: the driver knows how long the GPU took and
// when the last picture left, and holds the next frame's start until its picture can go out as soon as it is drawn.
//
// Two drivers offer that, and the device enables the one it lists (vk_device.c create_device):
// - NVIDIA, VK_NV_low_latency2. The window's swapchain is made with VkSwapchainLatencyCreateInfoNV (gfx_present.c
// create_swapchain) and the sleep mode is set on it: low latency, boost for the value 2, and the frame limit as the
// minimum interval. Each frame asks the driver for its sleep (vkLatencySleepNV, which signals a timeline semaphore
// when the frame may start) and marks its stages with the frame's number: simulation start, input sample,
// simulation end, render submit start and end, present start and end. The frame's queue submissions and its
// present carry the same number (VkLatencySubmissionPresentIdNV, VkPresentIdKHR).
// - AMD, VK_AMD_anti_lag. vkAntiLagUpdateAMD is called with the input stage where the frame starts (the driver waits
// inside the call) and with the present stage before the frame's present, both with the frame's number and the frame
// limit in frames a second.
// Any other driver has neither: the setting does nothing there and the menu's row says so.
//
// Threads. The game's frame is made on guest threads and shown by the present thread, so the calls are split:
// - guest thread, where the frame starts (vk_lowlat_frame_start): the first pad read (ov_input.c npad_states) or
// window image acquire (nvn_api_obj.c nvnWindowAcquireTexture) after a present, whichever the game makes first. The
// sleep, then the simulation-start marker; the pad read sets the input marker. The thread gives its virtual core
// up for the sleep, as around every host wait.
// - guest thread, the frame's first nvnQueueSubmitCommands (vk_lowlat_submit_start): simulation end, render submit
// start. The game's own threads cannot be told apart, so the simulation ends where the first commands are handed over.
// - the thread that replays (the guest's, or the replay thread with NX2NAVI_REPLAY_ASYNC), at the frame's present
// (vk_lowlat_submitted): render submit end. Its queue submissions are tagged in vk_sync.c queue_submit.
// - present thread (gfx_present.c flip_locked): the present stage or the present markers around the host present.
// The frame's number is the count of the game's presents, as vk_backend.c numbers the frame it hands to the present
// thread (PresentJob.frame): a guest thread starts frame n while n - 1 presents were made, and the replay and the
// present thread get n with the frame's work. A frame the present leaves out (vk_backend.c frame_hold) simply has no
// present stage.
//
// One limiter. With the mode on, the driver's sleep holds the frames to the frame limit and the runtime's own pacer
// holds nothing: nvnQueuePresentTexture asks vk_lowlat_pace, and on 1 gives its present the time now and frees the
// window image at once. Both together would wait twice, and the pacer's wait is the one after the picture is drawn,
// which is the delay the mode is there to remove. The pacer keeps the limit where the driver cannot: while a movie
// plays (nvn_api_obj.c pace keeps the movie's slots through a late frame, which a driver's limiter does not), while no
// host present goes out (a minimized window: the driver would have nothing to time its sleep by, so the sleep is left
// out too), and before the first host present. RivaTuner Statistics Server is kept out as with frame generation
// (frame_rate.c frame_step_cap_ns): it would hold the finished picture as the pacer did. NX2NAVI_PACE_SMOOTH and
// NX2NAVI_FRAME_HOLD_MS act on the present thread as before.
//
// Frame generation. DLSS Frame Generation through the vendor's interposer brings its own low latency
// (vk_dlssg.c, NX2NAVI_REFLEX): a device its proxies make does not get these extensions and this path stays off for
// the run. AMD's frame generation puts its own swapchain object in the window swapchain's place, which NVIDIA's calls
// cannot be given, so NVIDIA's path is off while it is there and on again with the plain swapchain; AMD's path has no
// swapchain in its calls and runs beside it. NVIDIA's frame generation with the runtime's own presenter (gfx_present.c
// ng_run) presents several times a rendered frame, which the driver would take for frames: off while it presents.
// Each is decided where the state changes: at device creation, at each swapchain creation, and at the present that
// starts or stops the presenter.
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "cfg.h"
#include "host.h"
#include "nvn_api.h"
#include "platform/platform.h"
#include "vk.h"
#include "vk_dlssg.h"
#include "vk_fg.h"
#include "vk_lowlat.h"
enum { LL_NONE, LL_NV, LL_AMD };
#define RING 64 // frames between a frame's start and its present are a handful at most
#define HOLD_IDLE_NS 200000000ull // no host present for this long: the driver's sleep is left out
#define SLEEP_MAX_NS 500000000ull // the longest wait for the driver to end a sleep
#define SLEEP_FAILS 3 // that many waits in a row running out: the mode is given up for the run
#define REPORTS 16 // frames of the driver's report read for a statistics line
#define PACE_WINDOW 60 // frames over which the driver's hold of the frame limit is checked
#define MODE_UNSET 0xffffffffu
#define LOAD(p) __atomic_load_n(p, __ATOMIC_ACQUIRE)
#define STORE(p, v) __atomic_store_n(p, v, __ATOMIC_RELEASE)
static struct {
int kind; // LL_*: fixed by vk_lowlat_init
int why; // vk_lowlat_status while kind is LL_NONE
int present_ids; // the presentId feature is on: the present carries the frame's number
VkDevice dev;
PFN_vkSetLatencySleepModeNV set_mode;
PFN_vkLatencySleepNV sleep;
PFN_vkSetLatencyMarkerNV marker;
PFN_vkGetLatencyTimingsNV timings;
PFN_vkAntiLagUpdateAMD antilag;
VkSemaphore sem; // NVIDIA: the timeline the driver signals to end a sleep
// NVIDIA's calls name the swapchain and nothing says they may run at once on two threads: all of them, and the
// swapchain's replacement, hold m. None of them waits (the sleep's wait is on the semaphore, outside m). AMD's
// stage calls hold it shared and its switch-off exclusively (amd_stage).
PlatMutex m;
VkSwapchainKHR sc; // m: the swapchain made with the latency mode, or none
uint32_t mode; // m to write, atomic to read: the sleep mode as last set on sc (mode_want), or MODE_UNSET
uint64_t sleep_value; // m: the semaphore value of the newest sleep
// atomics
int attached; // sc is set
int on; // the direct path is in effect
int said; // on, as last logged
int failed; // the driver refused the mode or did not end its sleeps: off for the run
int fg_block; // vk_lowlat_fg_presents
int pace_bad; // the driver's sleep did not hold the frame limit: the pacer holds it (vk_lowlat_pace)
int cfg_read, level; // the setting as last parsed (level_now)
unsigned cfg_seen;
uint32_t interval_us; // the period the driver holds frames to, 0 while the pacer does (vk_lowlat_pace)
uint32_t fails; // sleeps in a row whose wait ran out
uint64_t game; // the game's presents so far
uint64_t started; // the newest frame that started
uint64_t input_id; // the newest frame whose pad read was marked
uint64_t submit_id; // the newest frame whose first submission was marked
uint64_t cur; // the frame of the present thread's next blit
uint64_t hosted_ns; // kern_now_ns after the newest host present of a frame this path followed
uint64_t ring[RING]; // [n % RING] == n: frame n started with the path on, so its later stages are marked
uint64_t staged[RING]; // AMD, the same: frame n's input stage was called, so its present stage is too
// statistics since the last line (vk_lowlat_present_end)
uint64_t n_sleep, sleep_ns, n_input, n_unheld, n_timeout;
} L = {.why = VK_LOWLAT_NO_DRIVER, .mode = MODE_UNSET};
// NX2NAVI_LOW_LATENCY as a number, parsed again when a setting changed.
static int level_now(void) {
unsigned v = cfg_version();
if (!LOAD(&L.cfg_read) || v != LOAD(&L.cfg_seen)) {
int n = cfg_int("NX2NAVI_LOW_LATENCY");
STORE(&L.level, n < 0 ? 0 : n > 2 ? 2 : n);
STORE(&L.cfg_seen, v);
STORE(&L.cfg_read, 1);
}
return LOAD(&L.level);
}
static int level_eff(void) { return LOAD(&L.failed) ? 0 : level_now(); }
static int frame_on(uint64_t id) { return id && LOAD(&L.ring[id % RING]) == id; }
static void say(int on, const char *why) {
static int32_t lines;
if (__atomic_exchange_n(&L.said, on, __ATOMIC_ACQ_REL) == on || plat_inc32(&lines) > 40) return;
if (on)
rt_log("lowlat: low latency on%s: the driver holds each frame's start, and the frame limit with it",
L.kind == LL_NV && level_now() == 2 ? " with boost" : "");
else rt_log("lowlat: low latency off (%s): the game's own limiter paces the frames", why);
}
static void give_up(const char *what, int r) {
if (__atomic_exchange_n(&L.failed, 1, __ATOMIC_ACQ_REL)) return;
rt_log("lowlat: %s (%d): low latency is off for the rest of the run", what, r);
}
// ---- NVIDIA
// The sleep mode in one word: bits 0-1 the level (0 off, 1 low latency, 2 with boost), the rest the minimum interval
// in microseconds. 0 while the setting is off or the runtime's frame generation presenter presents.
static uint32_t mode_want(void) {
int lv = LOAD(&L.fg_block) ? 0 : level_eff();
return lv ? (uint32_t)lv | LOAD(&L.interval_us) << 2 : 0;
}
// m held, sc set. vkSetLatencySleepModeNV: sc is a live swapchain of dev (it is replaced only under m), the structure
// has its type; the call returns at once.
static void nv_mode_locked(uint32_t want) {
if (want == L.mode) return;
VkLatencySleepModeInfoNV mi = {.sType = VK_STRUCTURE_TYPE_LATENCY_SLEEP_MODE_INFO_NV,
.lowLatencyMode = (want & 3) != 0, .lowLatencyBoost = (want & 3) == 2,
.minimumIntervalUs = want >> 2};
VkResult r = L.set_mode(L.dev, L.sc, &mi);
if (r != VK_SUCCESS) {
give_up("the driver refused the low-latency sleep mode", (int)r);
want = 0; // taken as off: nothing sleeps on it
}
STORE(&L.mode, want);
STORE(&L.on, (want & 3) != 0);
say((want & 3) != 0, LOAD(&L.failed) ? "the driver refused it" : LOAD(&L.fg_block) ? "frame generation presents the frames"
: "the setting");
}
static void nv_marker(VkLatencyMarkerNV marker, uint64_t id) {
VkSetLatencyMarkerInfoNV mi = {.sType = VK_STRUCTURE_TYPE_SET_LATENCY_MARKER_INFO_NV, .presentID = id, .marker = marker};
plat_mutex_lock(&L.m);
if (L.sc) L.marker(L.dev, L.sc, &mi);
plat_mutex_unlock(&L.m);
}
// The driver's sleep for the frame about to start: vkLatencySleepNV returns at once and signals the timeline semaphore
// with the value given when the frame may start. Each sleep takes the next value, so a wait that ran out leaves a value
// behind that a later signal passes. 1 when the driver ended the sleep.
static int nv_sleep(void) {
VkLatencySleepInfoNV si = {.sType = VK_STRUCTURE_TYPE_LATENCY_SLEEP_INFO_NV, .signalSemaphore = L.sem};
VkResult r = VK_NOT_READY;
plat_mutex_lock(&L.m);
if (L.sc && L.mode != MODE_UNSET && (L.mode & 3)) {
si.value = ++L.sleep_value;
r = L.sleep(L.dev, L.sc, &si);
}
plat_mutex_unlock(&L.m);
if (r == VK_NOT_READY) return 0; // the swapchain went, or the mode was switched off, since the frame started
if (r != VK_SUCCESS) {
vk_device_lost_check(r, "the low-latency sleep");
give_up("the driver refused the low-latency sleep", (int)r);
return 0;
}
VkSemaphoreWaitInfo wi = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, .semaphoreCount = 1, .pSemaphores = &L.sem,
.pValues = &si.value};
r = vkWaitSemaphores(L.dev, &wi, SLEEP_MAX_NS);
vk_device_lost_check(r, "the wait for the low-latency sleep");
if (r == VK_SUCCESS) {
STORE(&L.fails, 0);
return 1;
}
__atomic_add_fetch(&L.n_timeout, 1, __ATOMIC_RELAXED);
if (__atomic_add_fetch(&L.fails, 1, __ATOMIC_ACQ_REL) >= SLEEP_FAILS)
give_up("the driver did not end its low-latency sleep three times in a row", (int)r);
return 0;
}
// ---- AMD
// vkAntiLagUpdateAMD with a stage of frame id: the antiLag feature is enabled (vk_device.c create_device keeps
// has_anti_lag only with it), both structures have their types, the mode and the stage are values of their enums.
// The stage calls hold m shared, and make none once the path is off: the input stage waits inside the call at a
// frame's start while the present thread makes the present stage of the frame before, which must not wait for it
// (the command has no parameter the caller has to synchronize, so the two may run at once). The switch-off (refresh)
// holds m exclusively: it waits for a stage call in flight, so a stage call with the mode on cannot come after it and
// leave the driver on.
static void amd_stage(VkAntiLagStageAMD stage, uint64_t id) {
uint32_t us = LOAD(&L.interval_us);
VkAntiLagPresentationInfoAMD pi = {.sType = VK_STRUCTURE_TYPE_ANTI_LAG_PRESENTATION_INFO_AMD, .stage = stage,
.frameIndex = id};
VkAntiLagDataAMD d = {.sType = VK_STRUCTURE_TYPE_ANTI_LAG_DATA_AMD, .mode = VK_ANTI_LAG_MODE_ON_AMD,
.maxFPS = us ? (1000000u + us / 2) / us : 0, .pPresentationInfo = &pi};
plat_mutex_lock_shared(&L.m);
if (LOAD(&L.on)) L.antilag(L.dev, &d);
plat_mutex_unlock_shared(&L.m);
}
// ---- both
// The guest thread at a frame's start: brings the driver's mode in step with the setting, the frame limit and what
// presents the frames. 1 while the direct path is in effect.
static int refresh(void) {
if (L.kind == LL_AMD) {
int want = level_eff() > 0, was = __atomic_exchange_n(&L.on, want, __ATOMIC_ACQ_REL);
if (was && !want) { // switched off: told once, without a stage, after any stage call in flight (amd_stage)
VkAntiLagDataAMD d = {.sType = VK_STRUCTURE_TYPE_ANTI_LAG_DATA_AMD, .mode = VK_ANTI_LAG_MODE_OFF_AMD};
plat_mutex_lock(&L.m);
if (!LOAD(&L.on)) L.antilag(L.dev, &d); // not when another thread switched it on again meanwhile
plat_mutex_unlock(&L.m);
}
say(want, LOAD(&L.failed) ? "the driver refused it" : "the setting");
return want;
}
if (!LOAD(&L.attached)) return 0; // no swapchain with the mode: on is 0 (vk_lowlat_swapchain)
uint32_t want = mode_want();
if (want != LOAD(&L.mode)) {
plat_mutex_lock(&L.m);
if (L.sc) nv_mode_locked(want);
plat_mutex_unlock(&L.m);
}
return LOAD(&L.on);
}
static void frame_begin(uint64_t id, int at_input) {
uint64_t t0 = kern_now_ns(), hosted = LOAD(&L.hosted_ns);
STORE(&L.ring[id % RING], id);
if (hosted && t0 - hosted < HOLD_IDLE_NS) {
int away = kern_core_away(); // 0 where the caller gave the core up already (the acquire)
uint64_t p0 = perf_begin();
if (L.kind == LL_NV) nv_sleep();
else STORE(&L.staged[id % RING], id), amd_stage(VK_ANTI_LAG_STAGE_INPUT_AMD, id);
perf_end_any(PERF_PACE, p0); // the acquiring thread may not be the one that presents
kern_core_back(&away);
__atomic_add_fetch(&L.sleep_ns, kern_now_ns() - t0, __ATOMIC_RELAXED);
__atomic_add_fetch(&L.n_sleep, 1, __ATOMIC_RELAXED);
if (at_input) __atomic_add_fetch(&L.n_input, 1, __ATOMIC_RELAXED);
} else {
__atomic_add_fetch(&L.n_unheld, 1, __ATOMIC_RELAXED);
}
if (L.kind == LL_NV) nv_marker(VK_LATENCY_MARKER_SIMULATION_START_NV, id);
}
void vk_lowlat_frame_start(int at_input) {
if (L.kind == LL_NONE) return;
if (!refresh() || vk_device_is_lost()) return;
uint64_t id = LOAD(&L.game) + 1, prev = LOAD(&L.started);
// once a frame, whichever guest thread comes first: the game reads several pads and may acquire again
if (prev != id && __atomic_compare_exchange_n(&L.started, &prev, id, 0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED))
frame_begin(id, at_input);
if (!at_input || L.kind != LL_NV) return;
prev = LOAD(&L.input_id);
if (prev != id && __atomic_compare_exchange_n(&L.input_id, &prev, id, 0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED) && frame_on(id))
nv_marker(VK_LATENCY_MARKER_INPUT_SAMPLE_NV, id);
}
void vk_lowlat_submit_start(void) {
if (L.kind != LL_NV || !LOAD(&L.on)) return;
uint64_t id = LOAD(&L.game) + 1, prev = LOAD(&L.submit_id);
if (prev == id || !frame_on(id)) return;
if (!__atomic_compare_exchange_n(&L.submit_id, &prev, id, 0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) return;
nv_marker(VK_LATENCY_MARKER_SIMULATION_END_NV, id);
nv_marker(VK_LATENCY_MARKER_RENDERSUBMIT_START_NV, id);
}
void vk_lowlat_game_present(void) { __atomic_add_fetch(&L.game, 1, __ATOMIC_RELEASE); }
void vk_lowlat_submitted(uint64_t frame) {
if (L.kind == LL_NV && frame_on(frame)) nv_marker(VK_LATENCY_MARKER_RENDERSUBMIT_END_NV, frame);
}
// The pacer gives the frame limit up only while the driver keeps it, and that is checked: of every PACE_WINDOW frames
// the driver was to hold, at most half may come sooner than three quarters of the period after the frame before. More
// means nothing holds the game (a driver that ignores the interval, or a game whose pad read and image acquire are not
// on the thread that makes the frame, so the sleep held another thread), and a game that runs free can outrun what
// its own code survives (frame_rate.c LIMIT_FREE_MAX_HZ). The pacer then holds the limit for the rest of the run and
// the driver's sleep goes on without an interval.
int vk_lowlat_pace(uint64_t period_ns) {
static uint64_t last_ns, last_period; // the presenting guest thread's: the newest present the driver was to hold
static int window, fast;
if (!LOAD(&L.on)) return 0;
uint64_t now = kern_now_ns(), hosted = LOAD(&L.hosted_ns);
if (last_period && last_period == period_ns && !LOAD(&L.pace_bad)) {
if (now - last_ns < period_ns / 4 * 3) fast++;
if (++window >= PACE_WINDOW) {
if (fast > PACE_WINDOW / 2) {
STORE(&L.pace_bad, 1);
rt_log("lowlat: the driver's sleep does not hold the frame limit (%d of %d frames came sooner than three "
"quarters of %.2f ms): the game's own limiter holds it for the rest of the run", fast, PACE_WINDOW,
(double)period_ns / 1e6);
}
window = fast = 0;
}
}
uint64_t us = period_ns && !LOAD(&L.pace_bad) && hosted && now - hosted < HOLD_IDLE_NS ? (period_ns + 500) / 1000 : 0;
if (us > 0x3fffffffu) us = 0x3fffffffu;
STORE(&L.interval_us, (uint32_t)us);
last_ns = now, last_period = us ? period_ns : 0;
return us != 0;
}
// VkLatencySubmissionPresentIdNV extends VkSubmitInfo2 where the extension is enabled, which kind LL_NV says.
static void tag_fill(VkLatencySubmissionPresentIdNV *tag, uint64_t id) {
*tag = (VkLatencySubmissionPresentIdNV){.sType = VK_STRUCTURE_TYPE_LATENCY_SUBMISSION_PRESENT_ID_NV, .presentID = id};
}
// The frame being replayed is the one after the newest present the replaying thread counted (vk_backend.c: the guest
// thread in vk_be_present, the replay thread at its present item).
int vk_lowlat_submit_tag(VkLatencySubmissionPresentIdNV *tag) {
if (L.kind != LL_NV || !LOAD(&L.on)) return 0;
uint64_t id = __atomic_load_n(&gfx_counters.frames, __ATOMIC_RELAXED) + 1;
if (!frame_on(id)) return 0;
tag_fill(tag, id);
return 1;
}
void vk_lowlat_present_frame(uint64_t frame) { STORE(&L.cur, frame); }
uint64_t vk_lowlat_blit_frame(VkLatencySubmissionPresentIdNV *tag, int *tagged) {
*tagged = 0;
if (L.kind == LL_NONE) return 0;
uint64_t id = __atomic_exchange_n(&L.cur, 0, __ATOMIC_ACQ_REL);
if (!frame_on(id)) return 0;
if (L.kind == LL_NV) tag_fill(tag, id), *tagged = 1;
return id;
}
// The caller holds the present lock, under which the swapchain is replaced too: sc, when set, is the swapchain this
// present goes to. VkPresentIdKHR: one swapchain as in the present, the presentId feature on (present_ids), and the
// frame numbers of a swapchain's presents only grow (a frame is presented once, in order; a new swapchain starts over).
const void *vk_lowlat_present_begin(uint64_t frame, VkLowlatPresent *p) {
p->id = 0;
if (L.kind == LL_NONE || !frame_on(frame)) return NULL;
p->id = frame;
if (L.kind == LL_AMD) {
if (LOAD(&L.on) && LOAD(&L.staged[frame % RING]) == frame) amd_stage(VK_ANTI_LAG_STAGE_PRESENT_AMD, frame);
return NULL;
}
VkSetLatencyMarkerInfoNV mi = {.sType = VK_STRUCTURE_TYPE_SET_LATENCY_MARKER_INFO_NV, .presentID = frame,
.marker = VK_LATENCY_MARKER_PRESENT_START_NV};
plat_mutex_lock(&L.m);
int have = L.sc != VK_NULL_HANDLE;
if (have) L.marker(L.dev, L.sc, &mi);
plat_mutex_unlock(&L.m);
if (!have || !L.present_ids) return NULL;
p->info = (VkPresentIdKHR){.sType = VK_STRUCTURE_TYPE_PRESENT_ID_KHR, .swapchainCount = 1, .pPresentIds = &p->id};
return &p->info;
}
// The driver's own account of the newest frames (vkGetLatencyTimingsNV; every report has its type, the count says how
// many the array holds and comes back as how many were written), averaged into text for the statistics line.
static void nv_report(char *out, size_t cap) {
static VkLatencyTimingsFrameReportNV rep[REPORTS]; // the present thread's
memset(rep, 0, sizeof rep);
for (int i = 0; i < REPORTS; i++) rep[i].sType = VK_STRUCTURE_TYPE_LATENCY_TIMINGS_FRAME_REPORT_NV;
VkGetLatencyMarkerInfoNV gi = {.sType = VK_STRUCTURE_TYPE_GET_LATENCY_MARKER_INFO_NV, .timingCount = REPORTS, .pTimings = rep};
plat_mutex_lock(&L.m);
if (L.sc) L.timings(L.dev, L.sc, &gi);
else gi.timingCount = 0;
plat_mutex_unlock(&L.m);
uint32_t n = gi.timingCount > REPORTS ? REPORTS : gi.timingCount, used = 0;
double total = 0, sim = 0, sub = 0, pres = 0, drv = 0, osq = 0, gpu = 0;
#define SPAN(a, b) ((b) > (a) ? (double)((b) - (a)) / 1000.0 : 0.0)
for (uint32_t i = 0; i < n; i++) {
const VkLatencyTimingsFrameReportNV *t = &rep[i];
uint64_t from = t->inputSampleTimeUs ? t->inputSampleTimeUs : t->simStartTimeUs;
if (!from || t->gpuRenderEndTimeUs <= from) continue; // a frame the driver has not finished timing
used++;
total += SPAN(from, t->gpuRenderEndTimeUs), sim += SPAN(t->simStartTimeUs, t->simEndTimeUs);
sub += SPAN(t->renderSubmitStartTimeUs, t->renderSubmitEndTimeUs), pres += SPAN(t->presentStartTimeUs, t->presentEndTimeUs);
drv += SPAN(t->driverStartTimeUs, t->driverEndTimeUs), osq += SPAN(t->osRenderQueueStartTimeUs, t->osRenderQueueEndTimeUs);
gpu += SPAN(t->gpuRenderStartTimeUs, t->gpuRenderEndTimeUs);
}
#undef SPAN
if (!used) {
snprintf(out, cap, "; the driver reported no frame timings (%u reports)", n);
return;
}
snprintf(out, cap, "; the driver's report over %u frames: pad read to GPU done %.2f ms (simulation %.2f, render "
"submit %.2f, present call %.2f, driver %.2f, OS queue %.2f, GPU %.2f)", used, total / used, sim / used,
sub / used, pres / used, drv / used, osq / used, gpu / used);
}
// One line after the first 600 frames the path followed and every 3600 after that (every 600 with NX2NAVI_TRACE=perf):
// how many frame starts the driver held and for how long, where they were held, and the driver's report.
static void stats(uint64_t frame) {
static uint64_t shown, next = 600; // the present thread's
if (++shown < next) return;
next = shown + (rt_trace & RT_TRACE_PERF ? 600 : 3600);
uint64_t n = __atomic_exchange_n(&L.n_sleep, 0, __ATOMIC_RELAXED), ns = __atomic_exchange_n(&L.sleep_ns, 0, __ATOMIC_RELAXED);
uint64_t in = __atomic_exchange_n(&L.n_input, 0, __ATOMIC_RELAXED), un = __atomic_exchange_n(&L.n_unheld, 0, __ATOMIC_RELAXED);
uint64_t late = __atomic_exchange_n(&L.n_timeout, 0, __ATOMIC_RELAXED);
uint32_t us = LOAD(&L.interval_us);
char limit[96], drv[320] = "";
if (us) snprintf(limit, sizeof limit, "the driver holds frames to %.3f ms", us / 1000.0);
else snprintf(limit, sizeof limit, "the game's own limiter holds the frame limit");
if (L.kind == LL_NV) nv_report(drv, sizeof drv);
rt_log("lowlat: frame %llu: %s; %llu frame starts held %.2f ms on average, %llu at the pad read and %llu at the "
"image acquire; %llu not held, %llu sleeps not ended in time%s", (unsigned long long)frame, limit,
(unsigned long long)n, n ? (double)ns / (double)n / 1e6 : 0.0, (unsigned long long)in,
(unsigned long long)(n - in), (unsigned long long)un, (unsigned long long)late, drv);
}
void vk_lowlat_present_end(const VkLowlatPresent *p) {
if (!p->id) return;
if (L.kind == LL_NV) nv_marker(VK_LATENCY_MARKER_PRESENT_END_NV, p->id);
STORE(&L.hosted_ns, kern_now_ns());
stats(p->id);
}
// ---- the swapchain and the state
int vk_lowlat_swapchain_wanted(void) { return L.kind == LL_NV && level_eff() > 0; }
void vk_lowlat_swapchain(VkSwapchainKHR sc, int made) {
if (L.kind != LL_NV) return;
plat_mutex_lock(&L.m);
L.sc = sc;
STORE(&L.mode, MODE_UNSET); // a new swapchain has no mode yet; none has none
STORE(&L.on, 0);
if (sc) nv_mode_locked(mode_want());
STORE(&L.attached, sc != VK_NULL_HANDLE);
plat_mutex_unlock(&L.m);
// a swapchain was made and it is not one for this path, with the setting on: a frame generation's (a resize in
// between says nothing: the next swapchain is there a moment later)
if (made && !sc && level_eff() > 0) say(0, "a frame generation's swapchain is in the window swapchain's place");
}
void vk_lowlat_swapchain_refused(int result) { give_up("no swapchain could be made with the latency mode", result); }
void vk_lowlat_fg_presents(int on) {
if (L.kind == LL_NV) STORE(&L.fg_block, on != 0); // taken up at the next frame's start (refresh)
}
int vk_lowlat_on(void) { return LOAD(&L.on); }
int vk_lowlat_status(void) {
if (L.kind == LL_NONE) return L.why;
if (LOAD(&L.failed)) return VK_LOWLAT_FAILED;
if (L.kind == LL_AMD) return VK_LOWLAT_AMD;
return vk_fg_active() || LOAD(&L.fg_block) ? VK_LOWLAT_FG : VK_LOWLAT_NV;
}
void vk_lowlat_init(VkCtx *c) {
int level = level_now();
L.dev = c->device;
L.kind = LL_NONE, L.why = VK_LOWLAT_NO_DRIVER, L.present_ids = 0; // a probe makes the device more than once
STORE(&L.failed, 0);
STORE(&L.pace_bad, 0);
if (vk_dlssg_dpa()) {
L.why = VK_LOWLAT_DLSSG;
rt_log("lowlat: DLSS Frame Generation's interposer owns low latency in this run (NX2NAVI_REFLEX); the direct "
"path is off (NX2NAVI_LOW_LATENCY=%d)", level);
return;
}
if (c->has_low_latency2) {
L.set_mode = (PFN_vkSetLatencySleepModeNV)vk_dpa(c->device, "vkSetLatencySleepModeNV");
L.sleep = (PFN_vkLatencySleepNV)vk_dpa(c->device, "vkLatencySleepNV");
L.marker = (PFN_vkSetLatencyMarkerNV)vk_dpa(c->device, "vkSetLatencyMarkerNV");
L.timings = (PFN_vkGetLatencyTimingsNV)vk_dpa(c->device, "vkGetLatencyTimingsNV");
// the sleep's semaphore has to be a timeline semaphore (the timelineSemaphore feature is one the device needs)
VkSemaphoreTypeCreateInfo st = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO,
.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE, .initialValue = 0};
VkSemaphoreCreateInfo si = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = &st};
if (L.set_mode && L.sleep && L.marker && L.timings && vkCreateSemaphore(c->device, &si, NULL, &L.sem) == VK_SUCCESS) {
L.present_ids = c->has_present_id;
L.kind = LL_NV;
rt_log("lowlat: NVIDIA's low latency through VK_NV_low_latency2 (presents %s the frame's number): %s "
"(NX2NAVI_LOW_LATENCY=%d)", L.present_ids ? "carry" : "do not carry",
level == 2 ? "on with boost" : level ? "on" : "off", level);
return;
}
rt_log("lowlat: VK_NV_low_latency2 is enabled but its entry points or the sleep's semaphore are missing");
} else if (c->has_anti_lag) {
L.antilag = (PFN_vkAntiLagUpdateAMD)vk_dpa(c->device, "vkAntiLagUpdateAMD");
if (L.antilag) {
L.kind = LL_AMD;
rt_log("lowlat: AMD's Anti-Lag through VK_AMD_anti_lag: %s (NX2NAVI_LOW_LATENCY=%d)", level ? "on" : "off", level);
return;
}
rt_log("lowlat: VK_AMD_anti_lag is enabled but its entry point is missing");
}
if (c->lists_low_latency) {
L.why = VK_LOWLAT_RESTART;
rt_log("lowlat: the driver has a low-latency mode; this run started with NX2NAVI_LOW_LATENCY off and made its "
"device without it, so the setting takes effect at the next start");
return;
}
rt_log("lowlat: this driver has neither VK_NV_low_latency2 nor VK_AMD_anti_lag: no low-latency mode%s",
level ? " (NX2NAVI_LOW_LATENCY does nothing here)" : "");
}
void vk_lowlat_release(void) {
L.kind = LL_NONE;
STORE(&L.on, 0);
STORE(&L.attached, 0);
L.sc = VK_NULL_HANDLE;
if (L.sem) vkDestroySemaphore(L.dev, L.sem, NULL);
L.sem = VK_NULL_HANDLE;
}