mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Generate frames with DLSS on Linux through NGX itself: 2x to 6x or dynamic, switched while the game runs.
DLSS Frame Generation went through Streamline, which only the Windows build has, so framegen = dlss did nothing on Linux. There it now calls NGX's frame generation feature directly (vk_dlaa.c vk_ngxfg_*), with the depth, motion vectors, jitter, camera and HUD-less picture the Streamline path gets. With it on, each frame is composed into an image of its own instead of a swapchain image, the generated frames are made at the end of the frame's command buffer, and a presenter thread shows them and then the rendered frame, each copied into a swapchain image it acquires itself, spaced over the time between rendered frames (gfx_present.c ng_*). fg_mult is the multiplier: 2 to 6, or dynamic for only the frames the display's refresh rate needs. NVIDIA's libnvidia-ngx-dlssg.so.<version> goes beside the game; it is not in the repository or any kit. Pull request #124 by spidercraft. Its base was the present path of 0.5, so it is ported by hand: - taken as written: the NGX calls and the presenter (the block is lifted from the pull request's diff); - wired again into present_image, create_swapchain, the output rate and the HUD-less picture as they are now, with every new branch behind vk_ngxfg_wanted, which is 0 in the Windows build: the code is compiled there and never reached; - added: frame generation's feature and parameters are released before NGX shuts down, the presenter's images, semaphores and command pool go when the device is released and are made again for a new one, and a failed submit on this path settles its fence without an acquire semaphore; - the menu: the Frame generation row follows the setting at once on Linux and says "after restart" only when the game started without DLSS upscaling; the multiplier row lists what the GPU allows and Dynamic; - the overlay row of the pull request it builds on (#123) is not part of this. It touches vk_dlaa.c, which another session is changing: one field in N, the FG state after it, one FN line, the instance extension query, the capability read in vk_dlaa_init, the release, and a block appended at the end. Known gap, as in the pull request: on this path fps_limit counts the frames the game draws. The Windows files compile without new warnings; nothing was run, and the Linux path is not compiled here. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
@@ -125,6 +125,13 @@ or to use another DLSS version, put the library there yourself: NVIDIA publishes
|
||||
`v<version>`, and `tools/dlss_pin.py` names the version the kit carries. Later builds keep a library they did not
|
||||
place. Without it DLSS does nothing on Linux.
|
||||
|
||||
DLSS Frame Generation (RTX 40 and 50 cards) runs through NGX itself on Linux, without Streamline. It needs NVIDIA's
|
||||
`libnvidia-ngx-dlssg.so.<version>` beside `ocarina` (the same repository, `lib/Linux_x86_64/rel/`; no kit carries it)
|
||||
and DLSS upscaling on when the game starts. *Frame generation* in the Screen menu then offers NVIDIA DLSS, which
|
||||
switches at once, with a *DLSS multiplier* of 2x to 6x (above 2x on RTX 50 cards) or Dynamic, which generates only the
|
||||
frames needed to reach the display's refresh rate (`framegen = dlss` and `fg_mult` in `nx2navi.ini`). The log says
|
||||
`ngxfg: DLSS Frame Generation available` when NGX found the library.
|
||||
|
||||
`tools/make_appimage.py` packs this kit into one `NX2navi-Linux-<version>-x86_64.AppImage` with its own Python, for
|
||||
players without a source checkout: it checks for the packages above, installs zbic with pip on its first run, picks
|
||||
`--jobs` from the memory, builds into `~/Games/Ocarina` (or the folder given after the dump) and writes `play.sh`
|
||||
|
||||
@@ -387,6 +387,12 @@
|
||||
# With fps_limit unset it is the display's rate.
|
||||
# fsr3 takes effect at once, dlss (on or off) at the next start.
|
||||
# framegen = off
|
||||
# On Linux dlss runs through NVIDIA's NGX itself, without Streamline: it needs dlaa = 1 with DLSS when the game starts
|
||||
# and NVIDIA's libnvidia-ngx-dlssg.so.<version> (from NVIDIA's DLSS SDK repository) beside the game, then switches at
|
||||
# once, and its multiplier is fg_mult: 2 to 6 (above 2 needs an RTX 50 GPU) or dynamic, which generates only the frames
|
||||
# needed to reach the display's refresh rate at the rate the game draws. There fps_limit counts the frames the game
|
||||
# draws, and framegen_multiplier is not used.
|
||||
# fg_mult = 2
|
||||
# DLSS frame generation's multiplier: the frames on screen for each frame the game draws, 2, 3 or 4 (one, two or three
|
||||
# generated). 2 needs an RTX 40 GPU or newer, 3 and 4 an RTX 50 and a current driver. A value above what the GPU and
|
||||
# driver allow (Streamline reports it) is lowered to that, with a line in the log; the menu offers only what they allow.
|
||||
|
||||
@@ -42,7 +42,7 @@ static const char *const known[] = {
|
||||
"COLOR_PRESET", "COLOR_SATURATION", "CONTACT_DEBUG", "CONTACT_SHADOWS", "CORE_DIAG", "CRIT", "CRIT_OUT",
|
||||
"DESC_TEMPLATE", "DEVICE_PROFILE", "DISCORD_APP_ID", "DISCORD_IMAGE", "DISCORD_RPC", "DLAA", "DLAA_CAM_KEEP", "DLSS_IDLE_FREE", "DLAA_JITTER", "DLAA_MV", "DLAA_MASKDEBUG", "DLAA_MVDEBUG", "DLAA_OBJMV", "DLAA_SURVEY", "DLAA_TIMES", "DLSS", "DLSS_CHAR_MASK", "DLSS_PRESET", "DLSS_REACTIVE", "DUMP", "DUMP_BUF", "DUMP_DIR",
|
||||
"DUMP_LOG", "DUMP_RAW", "DUMP_SHADERS", "DUMP_FILE", "DS3", "LOG_CS_BINDS", "LOG_TEX_FLAGS", "LOG_MESH", "MESH", "POOL", "NATIVE_TENSOR", "LOG_RCOND", "LOG_RT_SIZES", "DUMP_WHITE", "DUMP_AFTER", "DUMP_MOVIE", "SUBMIT_FINISH", "ACQUIRE_WAIT", "CRASH_FILTER_GUARD", "CS_FALLBACK", "EXEFS", "FEEDBACK", "FIX_C0", "FIX_ZERO", "FLIGHT", "FLIGHT_AT", "FLIGHT_DIR", "FLIGHT_FRAMES",
|
||||
"FLIGHT_SPIKE_MS", "FLIGHT_THUMBS", "FLUSH_SPAN", "FLUSH_UPLOAD", "FOG_CULL", "FOLIAGE_SHADOW_CULL", "FOLIAGE_SHADOW_VRS", "FOV", "FOV_PATCH", "FG_HUDLESS_SHOW", "FPS60", "FPS_LIMIT", "FRAMEGEN", "FRAMEGEN_MULTIPLIER", "FREEZE_IDLE", "FREE_CORES",
|
||||
"FLIGHT_SPIKE_MS", "FLIGHT_THUMBS", "FLUSH_SPAN", "FLUSH_UPLOAD", "FOG_CULL", "FOLIAGE_SHADOW_CULL", "FOLIAGE_SHADOW_VRS", "FOV", "FOV_PATCH", "FG_HUDLESS_SHOW", "FG_MULT", "FPS60", "FPS_LIMIT", "FRAMEGEN", "FRAMEGEN_MULTIPLIER", "FREEZE_IDLE", "FREE_CORES",
|
||||
"FULLSCREEN", "FXAA", "GAME_ASPECT", "GAME_RES", "GPA", "GPA_EMPTY", "GPA_TWIN", "GPA_TWIN_MAX_KB", "GPL", "GPL_DRAW_BUILD", "GPU", "GPU_INDEX", "GPU_PASSES", "GPU_NAME", "HDR", "HDR_BLOWOUT", "HDR_CONTRAST",
|
||||
"HDR_EXPOSURE", "HDR_FLARE", "HDR_GAMMA", "HDR_GAME_NITS", "HDR_GRADE", "HDR_HIGHLIGHT_SAT", "HDR_HIGHLIGHTS", "HDR_HUE",
|
||||
"HDR_LIVE",
|
||||
@@ -88,7 +88,7 @@ static const struct { const char *key, *section; } sections[] = {
|
||||
{"HDR_HIGHLIGHT_SAT", "display"}, {"HDR_BLOWOUT", "display"}, {"HDR_FLARE", "display"}, {"HDR_HUE", "display"},
|
||||
{"HDR_GRADE", "display"}, {"SHADOW_RES", "display"},
|
||||
{"DLAA", "display"}, {"DLSS", "display"}, {"DLSS_PRESET", "display"}, {"DLSS_IDLE_FREE", "display"}, {"DLSS_CHAR_MASK", "display"}, {"FXAA", "display"},
|
||||
{"FRAMEGEN", "display"}, {"FRAMEGEN_MULTIPLIER", "display"},
|
||||
{"FRAMEGEN", "display"}, {"FRAMEGEN_MULTIPLIER", "display"}, {"FG_MULT", "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"},
|
||||
@@ -145,6 +145,8 @@ static const CfgMeta metas[] = {
|
||||
{"DLSS_PRESET", "default", "default|k|j", LIVE}, {"DLSS_IDLE_FREE", "1", "0|1|2", RESTART}, {"TAA", "dlss", "dlss|fsr3", LIVE}, // vk_dlaa.c fsr3
|
||||
{"FRAMEGEN", "off", "off|fsr3|dlss", LIVE}, // vk_fg.cpp, vk_dlssg.c
|
||||
{"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},
|
||||
{"DLSS_CHAR_MASK", "65", "0|35|65|100", LIVE}, {"FXAA", "1", "0|1", LIVE},
|
||||
{"GAME_ASPECT", "16:9", "16:9|window|21:9|32:9", RESTART}, // widescreen.c
|
||||
{"GAME_RES", "1440", "1080|1440|1800|2160|2880|window", RESTART}, // widescreen.c: the picture's height
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#define FRAMES 2
|
||||
#define MAX_IMAGES 16
|
||||
#define NG_MAX 5 // generated frames per rendered one at most (DLSS Frame Generation through NGX, 6x)
|
||||
#define VSYNC_NS 1000000000ull
|
||||
#define VSYNC_HZ 60
|
||||
|
||||
@@ -349,8 +350,10 @@ static void drop_swapchain_locked(VkCtx *c) {
|
||||
// read: a resize that arrives while the swapchain is being made (AMD's frame generation swapchain takes tens of ms)
|
||||
// sets it again, where clearing it at the end lost that resize and kept a swapchain of the old size, whose acquires
|
||||
// then timed out frame after frame behind AMD's presenter (the picture stopped).
|
||||
static void ng_drain(void);
|
||||
static int create_swapchain(void) {
|
||||
flip_locked(); // the acquired image goes out before its swapchain is replaced
|
||||
ng_drain(); // and what the NGX frame generation presenter still holds (nothing unless it runs)
|
||||
plat_xchg32(&g_dirty, 0);
|
||||
VkCtx *c = vk_ctx();
|
||||
VkSurfaceCapabilitiesKHR caps;
|
||||
@@ -374,9 +377,12 @@ static int create_swapchain(void) {
|
||||
if (fmts[i].format == VK_FORMAT_B8G8R8A8_UNORM && fmts[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { sf = fmts[i]; found = 1; }
|
||||
for (uint32_t i = 0; i < nf && !found; i++)
|
||||
if (fmts[i].format == VK_FORMAT_R8G8B8A8_UNORM && fmts[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { sf = fmts[i]; found = 1; }
|
||||
uint32_t count = caps.minImageCount + 1;
|
||||
// NGX frame generation's presenter holds the generated frames' images too, and the rendered frame waits for it:
|
||||
// room for both
|
||||
const uint32_t ngx = vk_ngxfg_wanted() ? 1 : 0, most = ngx ? 12 : 8;
|
||||
uint32_t count = caps.minImageCount + 1 + (ngx ? 2u + NG_MAX : 0u);
|
||||
if (caps.maxImageCount && count > caps.maxImageCount) count = caps.maxImageCount;
|
||||
if (count > 8) count = 8;
|
||||
if (count > most) count = most;
|
||||
|
||||
vk_queue_lock();
|
||||
vkDeviceWaitIdle(c->device);
|
||||
@@ -846,9 +852,12 @@ static int put_picture(VkCommandBuffer cb, unsigned slot, VkImage src, VkFormat
|
||||
// vk_fg_presents, vk_dlssg.c vk_dlssg_shown) over half a second, sampled at each of the game's presents
|
||||
// (gfx_output_fps).
|
||||
static volatile double g_out_fps;
|
||||
static uint64_t ng_presented(void); // frames the NGX frame generation presenter showed (0: it does not run)
|
||||
static int ng_running(void); // that presenter is in use
|
||||
static void output_rate(void) {
|
||||
static uint64_t t0, n0;
|
||||
uint64_t n = vk_dlssg_active() ? vk_dlssg_shown() : vk_fg_presents(g_sc), t = plat_now_ns();
|
||||
uint64_t ng_n = ng_presented();
|
||||
uint64_t n = ng_n ? ng_n : vk_dlssg_active() ? vk_dlssg_shown() : vk_fg_presents(g_sc), t = plat_now_ns();
|
||||
if (!n) { g_out_fps = 0, t0 = 0; return; }
|
||||
if (!t0 || n < n0) { t0 = t, n0 = n; return; }
|
||||
if (t - t0 >= 500000000ull) g_out_fps = (double)(n - n0) * 1e9 / (double)(t - t0), t0 = t, n0 = n;
|
||||
@@ -866,7 +875,7 @@ static VkImage hudless_record(VkCommandBuffer cb, unsigned slot, int w, int h, c
|
||||
VkImageView pv;
|
||||
uint32_t pw, ph;
|
||||
VkFormat pf;
|
||||
if (!vk_fg_active() || !vk_dlaa_fg_hudless(&pic, &pv, &pw, &ph, &pf) || (int)pw != w || (int)ph != h)
|
||||
if (!(vk_fg_active() || ng_running()) || !vk_dlaa_fg_hudless(&pic, &pv, &pw, &ph, &pf) || (int)pw != w || (int)ph != h)
|
||||
return VK_NULL_HANDLE;
|
||||
VkCtx *c = vk_ctx();
|
||||
if (g_hl && (g_hl_w != g_sc_ext.width || g_hl_h != g_sc_ext.height || g_hl_fmt != g_sc_fmt)) {
|
||||
@@ -911,6 +920,406 @@ static VkImage hudless_record(VkCommandBuffer cb, unsigned slot, int w, int h, c
|
||||
return g_hl;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------- DLSS Frame Generation through NGX (vk_ngxfg_*)
|
||||
// NX2NAVI_FRAMEGEN = dlss where Streamline does not run (every build but the Windows one; vk_ngxfg_wanted is 0 there
|
||||
// and none of this is reached). Each frame is composed into an image of its own (ng.bb) instead of a swapchain image.
|
||||
// At the end of the frame's command buffer DLSS Frame Generation makes the generated frames from it
|
||||
// (vk_ngxfg_evaluate, the multiplier less one) and the picture is copied aside. The presenter thread then shows them
|
||||
// in order, each copied into a swapchain image it acquires itself, and the rendered frame last, spaced evenly over the
|
||||
// time between two rendered frames, as NVIDIA's frame generation guide paces them. Two sets of images take turns: a
|
||||
// set is written again two frames later, after the presenter's copies from it are done (the timeline ng.copied).
|
||||
// Every frame goes through the presenter while it runs, so the timelines advance in order.
|
||||
// Three things froze the picture while this was written, and the code keeps away from each:
|
||||
// - a frame's own command buffer never waits for a swapchain acquire: on Wayland an acquire's semaphore can signal
|
||||
// only after a later present, and every later present would come from that command buffer;
|
||||
// - a timeline wait is never submitted ahead of its signal on the same queue (ng_wait_signaled);
|
||||
// - an acquire never blocks while it holds the queue lock the presenter presents with (ng_acquire).
|
||||
typedef struct { VkImage img; VmaAllocation mem; VkImageView view; } NgImg;
|
||||
static struct {
|
||||
int tried, on, fresh; // tried: 0 nothing made, 1 the objects below exist, 2 it cannot run here
|
||||
int thread_up; // the presenter thread runs (started once, kept over a release)
|
||||
uint32_t w, h;
|
||||
VkFormat fmt;
|
||||
NgImg bb, out[2][NG_MAX], real[2];
|
||||
VkSemaphore rendered, copied; // timelines: frame n's command buffer submitted/done, the presenter done with frame n
|
||||
uint64_t n; // frames handed to the presenter
|
||||
VkCommandPool pool;
|
||||
VkCommandBuffer cb[2][NG_MAX + 1]; // per shown frame: the generated ones, then the rendered one
|
||||
VkSemaphore acq[2][NG_MAX + 1];
|
||||
PlatMutex m;
|
||||
PlatCond cv;
|
||||
int busy, pending;
|
||||
struct { uint32_t idx; int count, set, shown; uint64_t n, t; VkSwapchainKHR sc; } job;
|
||||
uint64_t signaled; // the last value of ng.copied the presenter submitted a signal for (ng.m)
|
||||
uint64_t last_t, interval; // average time between rendered frames (ns)
|
||||
volatile uint64_t presents; // frames presented while it runs, generated ones included
|
||||
} ng = {.m = PLAT_MUTEX_INIT};
|
||||
|
||||
// An acquire that never blocks while it holds the queue lock: the swapchain is shared by the game thread and the
|
||||
// presenter (both acquire, the presenter presents), and an image is freed only by a present, which needs that lock.
|
||||
// A blocking acquire under the lock waited for the present it held off: the picture froze (1 s acquire timeouts).
|
||||
static VkResult ng_acquire(VkSwapchainKHR sc, uint64_t timeout, VkSemaphore sem, uint32_t *idx) {
|
||||
VkDevice dev = vk_ctx()->device;
|
||||
uint64_t end = plat_now_ns() + timeout;
|
||||
for (;;) {
|
||||
vk_queue_lock();
|
||||
VkResult r = vkAcquireNextImageKHR(dev, sc, 0, sem, VK_NULL_HANDLE, idx);
|
||||
vk_queue_unlock();
|
||||
if (r != VK_NOT_READY && r != VK_TIMEOUT) return r;
|
||||
if (plat_now_ns() >= end) return VK_TIMEOUT;
|
||||
plat_sleep_ns(200000);
|
||||
}
|
||||
}
|
||||
|
||||
static void ng_drain(void) { // waits until the presenter has shown everything handed to it
|
||||
if (!ng.tried) return;
|
||||
plat_mutex_lock(&ng.m);
|
||||
while (ng.pending || ng.busy) plat_cond_wait(&ng.cv, &ng.m);
|
||||
plat_mutex_unlock(&ng.m);
|
||||
}
|
||||
|
||||
static void ng_sleep_until(uint64_t t) {
|
||||
uint64_t now = plat_now_ns();
|
||||
if (t > now && t - now < 100000000ull) plat_sleep_ns(t - now);
|
||||
}
|
||||
|
||||
static void ng_present(VkSwapchainKHR sc, uint32_t idx) {
|
||||
VkPresentInfoKHR pi = {.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1,
|
||||
.pWaitSemaphores = &g_render_done[idx], .swapchainCount = 1, .pSwapchains = &sc,
|
||||
.pImageIndices = &idx};
|
||||
vk_queue_lock();
|
||||
VkResult r = vkQueuePresentKHR(vk_ctx()->gfx_queue, &pi);
|
||||
vk_queue_unlock();
|
||||
vk_device_fault_report(r);
|
||||
if (r == VK_ERROR_OUT_OF_DATE_KHR || r == VK_SUBOPTIMAL_KHR) plat_xchg32(&g_dirty, 1);
|
||||
__atomic_add_fetch(&ng.presents, 1, __ATOMIC_RELAXED);
|
||||
}
|
||||
|
||||
static void ng_signal_copied(uint64_t v) { // after everything submitted before it (frame v's copies included)
|
||||
VkSemaphoreSubmitInfo sig = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = ng.copied, .value = v,
|
||||
.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT};
|
||||
VkSubmitInfo2 si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, .signalSemaphoreInfoCount = 1, .pSignalSemaphoreInfos = &sig};
|
||||
vk_queue_lock();
|
||||
vkQueueSubmit2(vk_ctx()->gfx_queue, 1, &si, VK_NULL_HANDLE);
|
||||
vk_queue_unlock();
|
||||
plat_mutex_lock(&ng.m);
|
||||
ng.signaled = v;
|
||||
plat_cond_broadcast(&ng.cv);
|
||||
plat_mutex_unlock(&ng.m);
|
||||
}
|
||||
|
||||
// A frame's submission waits for ng.copied >= n - 2 on the GPU. That signal must be submitted first: one queue runs
|
||||
// its submissions in order, and a wait queued ahead of its signal stopped the queue for good (the picture froze).
|
||||
static void ng_wait_signaled(uint64_t v) {
|
||||
plat_mutex_lock(&ng.m);
|
||||
while (ng.signaled < v) plat_cond_wait(&ng.cv, &ng.m);
|
||||
plat_mutex_unlock(&ng.m);
|
||||
}
|
||||
|
||||
// Acquires a swapchain image, copies img (GENERAL, the swapchain's size and format) into it once frame n's command
|
||||
// buffer is done (ng.rendered), and presents it. Only the presenter acquires while it runs: the frame's own command
|
||||
// buffer never waits for an acquire, whose image the compositor may give back only after a later present.
|
||||
static int ng_show(VkImage img, int set, int slot, uint64_t n) {
|
||||
VkCtx *c = vk_ctx();
|
||||
uint32_t j = 0;
|
||||
VkResult r = ng_acquire(ng.job.sc, 1000000000ull, ng.acq[set][slot], &j);
|
||||
if (r != VK_SUCCESS && r != VK_SUBOPTIMAL_KHR) {
|
||||
static int told;
|
||||
if (told++ < 5) rt_log("ngxfg: presenter acquire failed (%d)", (int)r);
|
||||
if (r == VK_ERROR_OUT_OF_DATE_KHR || r == VK_TIMEOUT) plat_xchg32(&g_dirty, 1);
|
||||
return 0;
|
||||
}
|
||||
VkCommandBuffer cb = ng.cb[set][slot];
|
||||
vkResetCommandBuffer(cb, 0);
|
||||
VkCommandBufferBeginInfo bi = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
||||
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
|
||||
vkBeginCommandBuffer(cb, &bi);
|
||||
sync_barrier(cb, g_sc_img[j], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
|
||||
0, VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
VkImageCopy cp = {.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||
.extent = {ng.w, ng.h, 1}};
|
||||
vkCmdCopyImage(cb, img, VK_IMAGE_LAYOUT_GENERAL, g_sc_img[j], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &cp);
|
||||
sync_barrier(cb, g_sc_img[j], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
|
||||
VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_NONE, 0);
|
||||
vkEndCommandBuffer(cb);
|
||||
VkSemaphoreSubmitInfo wait[2] = {
|
||||
{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = ng.acq[set][slot], .stageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT},
|
||||
{.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = ng.rendered, .value = n,
|
||||
.stageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT}};
|
||||
VkSemaphoreSubmitInfo sig = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = g_render_done[j],
|
||||
.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT};
|
||||
VkCommandBufferSubmitInfo cbi = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, .commandBuffer = cb};
|
||||
VkSubmitInfo2 si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, .waitSemaphoreInfoCount = 2, .pWaitSemaphoreInfos = wait,
|
||||
.commandBufferInfoCount = 1, .pCommandBufferInfos = &cbi, .signalSemaphoreInfoCount = 1,
|
||||
.pSignalSemaphoreInfos = &sig};
|
||||
vk_queue_lock();
|
||||
r = vkQueueSubmit2(c->gfx_queue, 1, &si, VK_NULL_HANDLE);
|
||||
vk_queue_unlock();
|
||||
if (r != VK_SUCCESS) { vk_device_fault_report(r); return 0; }
|
||||
ng_present(ng.job.sc, j);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void ng_run(void) {
|
||||
VkCtx *c = vk_ctx();
|
||||
uint64_t t = ng.job.t, n = ng.job.n;
|
||||
int set = ng.job.set, count = ng.job.count;
|
||||
ng.job.shown = 0;
|
||||
if (ng.last_t && t > ng.last_t) {
|
||||
uint64_t d = t - ng.last_t;
|
||||
if (d > 2000000ull && d < 100000000ull) ng.interval = ng.interval ? (ng.interval * 7 + d) / 8 : d;
|
||||
}
|
||||
ng.last_t = t;
|
||||
uint64_t step = ng.interval / (uint64_t)(count + 1);
|
||||
if (n > 2) { // the command buffers and acquire semaphores of this set: their last use (frame n - 2) is over
|
||||
uint64_t v = n - 2;
|
||||
VkSemaphoreWaitInfo wi = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO, .semaphoreCount = 1, .pSemaphores = &ng.copied,
|
||||
.pValues = &v};
|
||||
vkWaitSemaphores(c->device, &wi, 2000000000ull);
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i) ng_sleep_until(t + (uint64_t)i * step); // the first one at once: the least latency
|
||||
if (!ng_show(ng.out[set][i].img, set, i, n)) break;
|
||||
ng.job.shown = i + 1;
|
||||
}
|
||||
static uint64_t jobs, gen_ok, gen_want;
|
||||
jobs++, gen_want += (uint64_t)count;
|
||||
if (count) ng_sleep_until(t + (uint64_t)count * step);
|
||||
ng_show(ng.real[set].img, set, NG_MAX, n); // the rendered frame, last
|
||||
ng_signal_copied(n); // after the copies out of this set
|
||||
gen_ok += (uint64_t)ng.job.shown;
|
||||
if (jobs == 60 || jobs % 1200 == 0)
|
||||
rt_log("ngxfg: presenter: %llu rendered frames, %llu of %llu generated frames shown, %llu presents; %.2f ms between rendered frames",
|
||||
(unsigned long long)jobs, (unsigned long long)gen_ok, (unsigned long long)gen_want,
|
||||
(unsigned long long)__atomic_load_n(&ng.presents, __ATOMIC_RELAXED), (double)ng.interval / 1e6);
|
||||
}
|
||||
|
||||
static void ng_thread(void *arg) {
|
||||
(void)arg;
|
||||
for (;;) {
|
||||
plat_mutex_lock(&ng.m);
|
||||
while (!ng.pending) {
|
||||
if (!plat_cond_wait_ms(&ng.cv, &ng.m, 1500) && ng.on && ng.n) { // nothing for 1.5 s: where the GPU stands
|
||||
uint64_t rv = 0, cv = 0;
|
||||
vkGetSemaphoreCounterValue(vk_ctx()->device, ng.rendered, &rv);
|
||||
vkGetSemaphoreCounterValue(vk_ctx()->device, ng.copied, &cv);
|
||||
static int told;
|
||||
if (told++ < 3)
|
||||
rt_log("ngxfg: watchdog: no frame for 1.5 s; frames handed %llu, copied signal submitted %llu; GPU rendered %llu copied %llu",
|
||||
(unsigned long long)ng.n, (unsigned long long)ng.signaled, (unsigned long long)rv, (unsigned long long)cv);
|
||||
}
|
||||
}
|
||||
ng.pending = 0, ng.busy = 1;
|
||||
plat_cond_broadcast(&ng.cv);
|
||||
plat_mutex_unlock(&ng.m);
|
||||
ng_run();
|
||||
plat_mutex_lock(&ng.m);
|
||||
ng.busy = 0;
|
||||
plat_cond_broadcast(&ng.cv);
|
||||
plat_mutex_unlock(&ng.m);
|
||||
}
|
||||
}
|
||||
|
||||
static void ng_free_image(NgImg *im) {
|
||||
VkCtx *c = vk_ctx();
|
||||
if (im->view) vkDestroyImageView(c->device, im->view, NULL);
|
||||
if (im->img) vmaDestroyImage(c->vma, im->img, im->mem);
|
||||
memset(im, 0, sizeof *im);
|
||||
}
|
||||
|
||||
static int ng_image(NgImg *im, uint32_t w, uint32_t h, VkFormat f) {
|
||||
VkCtx *c = vk_ctx();
|
||||
VkImageCreateInfo ii = {.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = f,
|
||||
.extent = {w, h, 1}, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT,
|
||||
.tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
|
||||
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED};
|
||||
VmaAllocationCreateInfo ai = {.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE};
|
||||
if (vmaCreateImage(c->vma, &ii, &ai, &im->img, &im->mem, NULL) != VK_SUCCESS) return im->img = VK_NULL_HANDLE, 0;
|
||||
VkImageViewCreateInfo vi = {.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = im->img, .viewType = VK_IMAGE_VIEW_TYPE_2D,
|
||||
.format = f, .subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
||||
return vkCreateImageView(c->device, &vi, NULL, &im->view) == VK_SUCCESS;
|
||||
}
|
||||
|
||||
// The presenter and the images for the current swapchain (g_pl held). 0: frames go out the plain way.
|
||||
static int ng_ready(void) {
|
||||
if (!vk_ngxfg_wanted()) return 0; // the Windows build, or a run that started without DLSS upscaling
|
||||
int want = vk_ngxfg_on() && !g_sc_hdr && !vk_fg_active() && !vk_dlssg_active();
|
||||
if (!vk_fg_wanted()) vk_dlaa_hudless_wanted = want; // the HUD-less picture DLSS-G tells the HUD apart by
|
||||
if (!want) { // off (the menu switched it off live): what the presenter holds goes out first
|
||||
if (ng.on) ng_drain(), ng.on = 0, rt_log("ngxfg: frame generation off");
|
||||
return 0;
|
||||
}
|
||||
if (ng.on && ng.w == g_sc_ext.width && ng.h == g_sc_ext.height && ng.fmt == g_sc_fmt) return 1;
|
||||
VkCtx *c = vk_ctx();
|
||||
if (!ng.tried) {
|
||||
ng.tried = 1;
|
||||
VkFormatProperties fp;
|
||||
vkGetPhysicalDeviceFormatProperties(c->phys, g_sc_fmt, &fp);
|
||||
if (!(fp.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT)) {
|
||||
rt_log("ngxfg: the swapchain format %d is no storage format here: frame generation off", (int)g_sc_fmt);
|
||||
return ng.on = 0, ng.tried = 2, 0;
|
||||
}
|
||||
if (!ng.thread_up) plat_cond_init(&ng.cv);
|
||||
VkSemaphoreTypeCreateInfo tl = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO, .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE};
|
||||
VkSemaphoreCreateInfo tci = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = &tl};
|
||||
vkCreateSemaphore(c->device, &tci, NULL, &ng.rendered);
|
||||
vkCreateSemaphore(c->device, &tci, NULL, &ng.copied);
|
||||
VkSemaphoreCreateInfo semi = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||
VkCommandPoolCreateInfo pci = {.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = c->gfx_family};
|
||||
vkCreateCommandPool(c->device, &pci, NULL, &ng.pool);
|
||||
for (int s = 0; s < 2; s++) {
|
||||
VkCommandBufferAllocateInfo ai = {.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = ng.pool,
|
||||
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = NG_MAX + 1};
|
||||
vkAllocateCommandBuffers(c->device, &ai, ng.cb[s]);
|
||||
for (int i = 0; i <= NG_MAX; i++) vkCreateSemaphore(c->device, &semi, NULL, &ng.acq[s][i]);
|
||||
}
|
||||
if (!ng.thread_up && !plat_thread_spawn(ng_thread, NULL, "fg present", 0)) {
|
||||
rt_log("ngxfg: the presenter thread did not start: frame generation off");
|
||||
return ng.on = 0, ng.tried = 2, 0;
|
||||
}
|
||||
ng.thread_up = 1;
|
||||
}
|
||||
if (ng.tried == 2) return 0;
|
||||
ng_drain();
|
||||
vk_queue_lock();
|
||||
vkDeviceWaitIdle(c->device);
|
||||
vk_queue_unlock();
|
||||
ng_free_image(&ng.bb);
|
||||
for (int s = 0; s < 2; s++) {
|
||||
ng_free_image(&ng.real[s]);
|
||||
for (int i = 0; i < NG_MAX; i++) ng_free_image(&ng.out[s][i]);
|
||||
}
|
||||
uint32_t w = g_sc_ext.width, h = g_sc_ext.height;
|
||||
int ok = ng_image(&ng.bb, w, h, g_sc_fmt);
|
||||
for (int s = 0; s < 2 && ok; s++) {
|
||||
ok = ng_image(&ng.real[s], w, h, g_sc_fmt);
|
||||
for (int i = 0; i < NG_MAX && ok; i++) ok = ng_image(&ng.out[s][i], w, h, g_sc_fmt);
|
||||
}
|
||||
if (!ok) {
|
||||
rt_log("ngxfg: frame generation images (%ux%u) could not be made: frame generation off", w, h);
|
||||
return ng.on = 0;
|
||||
}
|
||||
ng.w = w, ng.h = h, ng.fmt = g_sc_fmt, ng.fresh = 1, ng.on = 1;
|
||||
vk_ngxfg_reset();
|
||||
rt_log("ngxfg: presenter ready at %ux%u (up to %dx)", w, h, vk_ngxfg_max() + 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// NX2NAVI_FG_MULT: 2 to 6, or dynamic: only the frames needed to reach the display's refresh rate at the rendered frame
|
||||
// rate (240 Hz at 60 fps: 4x), between 2x and what the GPU allows, as NVIDIA describes its dynamic multi frame
|
||||
// generation; a new multiplier must hold for 30 frames before it is taken, so it does not flicker.
|
||||
static int ng_multiplier(int max) {
|
||||
const char *v = cfg_value("NX2NAVI_FG_MULT");
|
||||
int m;
|
||||
if (!strcmp(v, "dynamic")) {
|
||||
static int cur = 2, cand, held;
|
||||
double hz = gfx_window_refresh_hz(), ms = (double)ng.interval / 1e6;
|
||||
int fit = hz > 0 && ms > 0 ? (int)(hz * ms / 1000.0 + 0.1) : 2; // shown frames per rendered one that fit
|
||||
if (fit < 2) fit = 2;
|
||||
if (fit > max + 1) fit = max + 1;
|
||||
if (fit != cur) {
|
||||
held = fit == cand ? held + 1 : 0, cand = fit;
|
||||
if (held >= 30) cur = fit, held = 0;
|
||||
} else held = 0;
|
||||
m = cur;
|
||||
} else {
|
||||
m = atoi(v);
|
||||
}
|
||||
if (m < 2) m = 2;
|
||||
if (m > max + 1) m = max + 1;
|
||||
return m;
|
||||
}
|
||||
|
||||
// Records the copy of the frame's picture and DLSS-G's frames (the end of present_image's command buffer, dst in
|
||||
// layout *last). Returns the number of generated frames, -1 when the frame goes out the plain way.
|
||||
static int ng_record(VkCommandBuffer cb, VkImage dst, VkImageLayout *last, VkPipelineStageFlags2 *stage, VkAccessFlags2 *write,
|
||||
VkImage hudless, int *set, int gen) {
|
||||
if (!ng.on) return -1;
|
||||
if (ng.fresh) { // every image to GENERAL, once
|
||||
NgImg *all[1 + 2 + 2 * NG_MAX];
|
||||
int k = 0;
|
||||
all[k++] = &ng.bb;
|
||||
for (int s = 0; s < 2; s++) {
|
||||
all[k++] = &ng.real[s];
|
||||
for (int i = 0; i < NG_MAX; i++) all[k++] = &ng.out[s][i];
|
||||
}
|
||||
for (int i = 0; i < k; i++)
|
||||
sync_barrier(cb, all[i]->img, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_NONE, 0,
|
||||
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT);
|
||||
ng.fresh = 0;
|
||||
}
|
||||
*set = (int)((ng.n + 1) & 1);
|
||||
// the frame's picture is in dst (ng.bb): to GENERAL for DLSS-G and the copy into this set's rendered-frame image
|
||||
sync_barrier(cb, dst, *last, VK_IMAGE_LAYOUT_GENERAL, *stage, *write, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_READ_BIT);
|
||||
*last = VK_IMAGE_LAYOUT_GENERAL, *stage = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, *write = 0;
|
||||
int made = 0;
|
||||
if (!gen || menu_is_open()) {
|
||||
vk_ngxfg_reset(); // generation pauses while the menu is open
|
||||
} else {
|
||||
int max = vk_ngxfg_max(), want = ng_multiplier(max) - 1;
|
||||
VkNgxImage bb = {ng.bb.img, ng.bb.view, ng.fmt, ng.w, ng.h};
|
||||
VkNgxImage hl = {hudless, g_hl_view, g_hl_fmt, g_hl_w, g_hl_h};
|
||||
VkNgxImage out[NG_MAX], real = {ng.real[*set].img, ng.real[*set].view, ng.fmt, ng.w, ng.h};
|
||||
for (int i = 0; i < NG_MAX; i++) out[i] = (VkNgxImage){ng.out[*set][i].img, ng.out[*set][i].view, ng.fmt, ng.w, ng.h};
|
||||
int hl_ok = hudless && g_hl_w == ng.w && g_hl_h == ng.h && g_hl_fmt == ng.fmt;
|
||||
made = vk_ngxfg_evaluate(cb, &bb, hl_ok ? &hl : NULL, out, want, &real);
|
||||
}
|
||||
// the rendered frame for the presenter (ng.bb is drawn again by the next frame)
|
||||
sync_barrier(cb, ng.real[*set].img, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
|
||||
VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
VkImageCopy cp = {.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||
.extent = {ng.w, ng.h, 1}};
|
||||
vkCmdCopyImage(cb, dst, VK_IMAGE_LAYOUT_GENERAL, ng.real[*set].img, VK_IMAGE_LAYOUT_GENERAL, 1, &cp);
|
||||
sync_barrier(cb, ng.real[*set].img, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
|
||||
VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, VK_ACCESS_2_MEMORY_READ_BIT);
|
||||
*stage = VK_PIPELINE_STAGE_2_TRANSFER_BIT, *write = 0;
|
||||
return made;
|
||||
}
|
||||
|
||||
// gfx_window_release_vk, the device idle: the presenter shows what it holds, then its images, semaphores and command
|
||||
// buffers go with the device. The thread stays and waits; a later device makes the objects again (ng_ready).
|
||||
static void ng_release(void) {
|
||||
if (!ng.tried) return;
|
||||
ng_drain();
|
||||
VkCtx *c = vk_ctx();
|
||||
vk_queue_lock(), vkDeviceWaitIdle(c->device), vk_queue_unlock(); // what the presenter submitted while it drained
|
||||
ng_free_image(&ng.bb);
|
||||
for (int s = 0; s < 2; s++) {
|
||||
ng_free_image(&ng.real[s]);
|
||||
for (int i = 0; i < NG_MAX; i++) ng_free_image(&ng.out[s][i]);
|
||||
}
|
||||
if (ng.rendered) vkDestroySemaphore(c->device, ng.rendered, NULL);
|
||||
if (ng.copied) vkDestroySemaphore(c->device, ng.copied, NULL);
|
||||
for (int s = 0; s < 2; s++)
|
||||
for (int i = 0; i <= NG_MAX; i++)
|
||||
if (ng.acq[s][i]) vkDestroySemaphore(c->device, ng.acq[s][i], NULL), ng.acq[s][i] = VK_NULL_HANDLE;
|
||||
if (ng.pool) vkDestroyCommandPool(c->device, ng.pool, NULL); // its command buffers with it
|
||||
ng.rendered = ng.copied = VK_NULL_HANDLE, ng.pool = VK_NULL_HANDLE;
|
||||
plat_mutex_lock(&ng.m);
|
||||
ng.on = ng.tried = 0, ng.n = ng.signaled = 0; // new timelines start at 0
|
||||
plat_mutex_unlock(&ng.m);
|
||||
}
|
||||
|
||||
static uint64_t ng_presented(void) { return ng.on ? __atomic_load_n(&ng.presents, __ATOMIC_RELAXED) : 0; }
|
||||
static int ng_running(void) { return ng.on; }
|
||||
|
||||
// Hands the submitted frame to the presenter (it shows count generated frames, then swapchain image idx).
|
||||
static void ng_hand(uint32_t idx, int count, int set, uint64_t n) {
|
||||
plat_mutex_lock(&ng.m);
|
||||
while (ng.pending) plat_cond_wait(&ng.cv, &ng.m);
|
||||
ng.job.idx = idx, ng.job.count = count < 0 ? 0 : count, ng.job.set = set, ng.job.n = n, ng.job.t = plat_now_ns();
|
||||
ng.job.sc = g_sc;
|
||||
ng.pending = 1;
|
||||
plat_cond_broadcast(&ng.cv);
|
||||
plat_mutex_unlock(&ng.m);
|
||||
}
|
||||
|
||||
static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w, int h, const int crop[4], int defer) {
|
||||
int general = layout == VK_IMAGE_LAYOUT_GENERAL;
|
||||
{ // the presented image's format, when it changes (an HDR picture comes in another one)
|
||||
@@ -932,7 +1341,10 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
goto out;
|
||||
}
|
||||
uint32_t idx = 0;
|
||||
VkResult r = vk_dlssg_active() ? vk_dlssg_acquire(c->device, g_sc, 1000000000ull, g_acquire[slot], &idx)
|
||||
// NGX frame generation: the frame is composed into ng.bb and the presenter shows it (ng_show); no acquire here
|
||||
const int ngp = ng_ready();
|
||||
VkResult r = ngp ? VK_SUCCESS
|
||||
: vk_dlssg_active() ? vk_dlssg_acquire(c->device, g_sc, 1000000000ull, g_acquire[slot], &idx)
|
||||
: vk_fg_active() ? vk_fg_acquire(g_sc, 1000000000ull, g_acquire[slot], &idx)
|
||||
: vkAcquireNextImageKHR(c->device, g_sc, 1000000000ull, g_acquire[slot], VK_NULL_HANDLE, &idx);
|
||||
if (r == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||
@@ -980,7 +1392,8 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
g_sc_hdr ? VK_FORMAT_R8G8B8A8_UNORM : g_sc_fmt, g_sc_ext.width,
|
||||
g_sc_ext.height, cw, ch, g_scaler};
|
||||
// without a source image (gfx_present_boot): a black frame with the boot screen
|
||||
int overlay = g_sc_view[idx] &&
|
||||
VkImageView dst_view = ngp ? ng.bb.view : g_sc_view[idx];
|
||||
int overlay = dst_view &&
|
||||
(src ? menu_overlay_frame(__atomic_load_n(&gfx_counters.frames, __ATOMIC_RELAXED), &mt, &menu_shot)
|
||||
: menu_boot_frame(&mt));
|
||||
// NX2NAVI_SHOT_EVERY=<s> with NX2NAVI_SHOT_DIR=<dir>: the presented frame as shot_<n>.png every s seconds
|
||||
@@ -1013,7 +1426,7 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
vkBeginCommandBuffer(cb, &bi);
|
||||
uint64_t crumb = __atomic_load_n(&gfx_counters.frames, __ATOMIC_RELAXED);
|
||||
vk_crumb(cb, VK_CRUMB_PRESENT, crumb);
|
||||
VkImage dst = g_sc_img[idx];
|
||||
VkImage dst = ngp ? ng.bb.img : g_sc_img[idx];
|
||||
sync_barrier(cb, dst, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_TRANSFER_BIT, 0,
|
||||
VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
const int rect[4] = {cx, cy, cw, ch};
|
||||
@@ -1060,7 +1473,7 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT);
|
||||
VkRenderingAttachmentInfo ca = {.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
|
||||
.imageView = menu_view ? menu_view : g_sc_view[idx],
|
||||
.imageView = menu_view ? menu_view : dst_view,
|
||||
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
.loadOp = menu_view ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD,
|
||||
.storeOp = VK_ATTACHMENT_STORE_OP_STORE};
|
||||
@@ -1069,7 +1482,7 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
vkCmdBeginRendering(cb, &ri);
|
||||
menu_overlay_draw(cb);
|
||||
vkCmdEndRendering(cb);
|
||||
if (menu_view) vk_hdr_ui_compose(cb, g_sc_view[idx], g_sc_fmt, g_sc_ext.width, g_sc_ext.height);
|
||||
if (menu_view) vk_hdr_ui_compose(cb, dst_view, g_sc_fmt, g_sc_ext.width, g_sc_ext.height);
|
||||
last = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
last_stage = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
last_write = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
@@ -1096,7 +1509,12 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
if (src) vk_fg_frame(cb, g_sc, g_sc_fmt, g_sc_ext, hudless, g_hl_view, to); // frame generation: this frame's preparation
|
||||
if (src) output_rate();
|
||||
if (src) vk_dlssg_frame(cb, g_sc_ext); // DLSS Frame Generation: constants and tags (vk_dlssg.c)
|
||||
sync_barrier(cb, dst, last, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, last_stage, last_write, VK_PIPELINE_STAGE_2_NONE, 0);
|
||||
// through the presenter: the picture stays in ng.bb, and DLSS Frame Generation's frames and the copy for the
|
||||
// presenter are recorded; otherwise the swapchain image is ready to present
|
||||
int ng_set = 0;
|
||||
const int ng_count = ngp ? ng_record(cb, dst, &last, &last_stage, &last_write, hudless, &ng_set, src != VK_NULL_HANDLE) : -1;
|
||||
const int via_ng = ng_count >= 0;
|
||||
if (!via_ng) sync_barrier(cb, dst, last, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, last_stage, last_write, VK_PIPELINE_STAGE_2_NONE, 0);
|
||||
vk_crumb(cb, VK_CRUMB_PRESENT_END, crumb);
|
||||
vkEndCommandBuffer(cb);
|
||||
|
||||
@@ -1108,10 +1526,25 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
VkSubmitInfo2 si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, .waitSemaphoreInfoCount = 1, .pWaitSemaphoreInfos = &wait,
|
||||
.commandBufferInfoCount = 1, .pCommandBufferInfos = &cbi, .signalSemaphoreInfoCount = 1,
|
||||
.pSignalSemaphoreInfos = &sig};
|
||||
const uint64_t ng_n = ng.n + 1; // through the presenter: this frame's number on its timelines
|
||||
if (via_ng) {
|
||||
// No acquire to wait for. The frame writes one of the presenter's two image sets: it waits on the GPU until
|
||||
// the presenter is done with the frame before last, which used that set, and that signal is submitted first
|
||||
// (ng_wait_signaled). It signals the timeline the presenter's copies wait for instead of a render-done semaphore.
|
||||
wait = (VkSemaphoreSubmitInfo){.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = ng.copied,
|
||||
.value = ng_n > 2 ? ng_n - 2 : 0, .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT};
|
||||
sig = (VkSemaphoreSubmitInfo){.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = ng.rendered,
|
||||
.value = ng_n, .stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT};
|
||||
si.waitSemaphoreInfoCount = ng_n > 2 ? 1u : 0u;
|
||||
if (ng_n > 2) ng_wait_signaled(ng_n - 2);
|
||||
}
|
||||
vk_queue_lock();
|
||||
r = vkQueueSubmit2(c->gfx_queue, 1, &si, g_fence[slot]);
|
||||
vk_queue_unlock();
|
||||
if (r == VK_SUCCESS) {
|
||||
if (r == VK_SUCCESS && via_ng) { // the presenter shows the generated frames, then this one
|
||||
ng.n = ng_n;
|
||||
ng_hand(idx, ng_count, ng_set, ng_n);
|
||||
} else if (r == VK_SUCCESS) {
|
||||
g_flip_on = 1;
|
||||
g_flip_idx = idx;
|
||||
if (!defer) flip_locked();
|
||||
@@ -1121,7 +1554,8 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
// The fence was reset after the acquire and the acquire's semaphore is signalled: left so, this slot's fence
|
||||
// wait would time out (5 s) at every later present and the picture stop for good. An empty batch takes the
|
||||
// semaphore and signals the fence; the acquired image goes back with the swapchain, made again next frame.
|
||||
VkSubmitInfo2 settle = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, .waitSemaphoreInfoCount = 1,
|
||||
// (through the presenter nothing was acquired: the batch only signals the fence)
|
||||
VkSubmitInfo2 settle = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2, .waitSemaphoreInfoCount = via_ng ? 0u : 1u,
|
||||
.pWaitSemaphoreInfos = &wait};
|
||||
vk_queue_lock();
|
||||
VkResult r2 = vkQueueSubmit2(c->gfx_queue, 1, &settle, g_fence[slot]);
|
||||
@@ -1177,6 +1611,7 @@ void gfx_window_release_vk(void) {
|
||||
vk_queue_lock(), vkDeviceWaitIdle(c->device), vk_queue_unlock();
|
||||
vk_queue_unlock();
|
||||
g_flip_on = 0; // its blit is done (device idle); the swapchain goes away unpresented
|
||||
ng_release(); // NGX frame generation's presenter shows nothing more; its images go before the device
|
||||
menu_vk_release();
|
||||
vk_fsr_release();
|
||||
vk_color_release();
|
||||
|
||||
@@ -67,6 +67,8 @@ int vk_hdr_wanted(void);
|
||||
int vk_hdr_active(void);
|
||||
int vk_hdr_needs_restart(void);
|
||||
int vk_dlaa_needs_restart(void);
|
||||
int vk_ngxfg_wanted(void); // vk_dlaa.c: DLSS's frame generation through NGX itself can run in this run
|
||||
int vk_ngxfg_max(void); // ... and how many frames it may generate per rendered one (0: it cannot)
|
||||
int vk_ffx_available(void); // vk_ffx.cpp: FSR 3.1 upscaling can run (asked without loading the library)
|
||||
int vk_ffx_framegen_available(void); // ... and FSR 3.1 frame generation
|
||||
double frame_step_limit_hz(void);
|
||||
@@ -941,6 +943,36 @@ static void row_framegen_mult(bool restart) {
|
||||
if (nv >= 0 && nv < n) opt_apply("NX2NAVI_FRAMEGEN_MULTIPLIER", values[nv]);
|
||||
}
|
||||
|
||||
// NVIDIA's frame generation runs through NGX itself, not through Streamline (vk_dlaa.c vk_ngxfg_*, gfx_present.c
|
||||
// ng_*): every build but the Windows one.
|
||||
static bool framegen_ngx(void) {
|
||||
#ifdef _WIN32
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Its multiplier there (cfg.c FG_MULT, gfx_present.c ng_multiplier): 2x up to what the GPU allows, or Dynamic. Left out
|
||||
// while frame generation is set but cannot run: the frame generation row says why.
|
||||
static void row_framegen_mult_ngx(void) {
|
||||
static const char *const mult[] = {"2", "3", "4", "5", "6"};
|
||||
static const char *const mult_names[] = {"2x", "3x", "4x", "5x", "6x"};
|
||||
const int max = vk_ngxfg_max(); // generated frames per rendered one
|
||||
if (max < 1) return;
|
||||
const char *values[6], *names[6];
|
||||
int n = 0;
|
||||
for (; n < max && n < 5; n++) values[n] = mult[n], names[n] = mult_names[n];
|
||||
const int top = n - 1;
|
||||
values[n] = "dynamic", names[n++] = menu_text(UI_MENU_FG_MULT_DYNAMIC);
|
||||
const char *v = cfg_value("NX2NAVI_FG_MULT");
|
||||
int cur = strcmp(v, "dynamic") ? top : n - 1; // a multiplier above what the GPU allows shows as the highest
|
||||
for (int i = 0; i < n; i++)
|
||||
if (!strcmp(v, values[i])) cur = i;
|
||||
int nv = opt_choice_index("NX2NAVI_FG_MULT", UI_MENU_FRAMEGEN_MULT, names, n, cur, 0, UI_MENU_FG_MULT_DESC, nullptr);
|
||||
if (nv >= 0 && nv < n) opt_apply("NX2NAVI_FG_MULT", values[nv]);
|
||||
}
|
||||
|
||||
// Frame generation (vk_fg.cpp, at once; NVIDIA's after a restart); it needs a temporal upscaler's motion vectors
|
||||
static void row_framegen(void) {
|
||||
// the generated frames are made from DLSS's or FSR 3.1's motion vectors: without one of them it would do nothing,
|
||||
@@ -969,16 +1001,20 @@ static void row_framegen(void) {
|
||||
if (i == cur) at = n;
|
||||
values[n] = all_values[i], names[n++] = all_names[i];
|
||||
}
|
||||
// AMD's follows the setting; NVIDIA's is set up with the device, so only a change to or from it waits for a restart
|
||||
const bool restart = dlss && (cur == 2) != (vk_dlssg_wanted() != 0);
|
||||
// AMD's follows the setting; NVIDIA's is set up with the device, so only a change to or from it waits for a restart.
|
||||
// Through NGX it follows the setting too, once NGX is up, which needs DLSS upscaling on when the game starts.
|
||||
const bool ngx = framegen_ngx();
|
||||
const bool restart = ngx ? dlss && cur == 2 && !vk_ngxfg_wanted() : dlss && (cur == 2) != (vk_dlssg_wanted() != 0);
|
||||
if (restart) note = UI_MENU_AFTER_RESTART;
|
||||
else if (cur == 1 && !fsr) note = UI_MENU_FSR3_OFF; // set where it cannot load: "in use: off" and why
|
||||
// NVIDIA's set since the start but not running: "in use: off" and why (vk_dlssg_status; the log has the details)
|
||||
const int st = cur == 2 && !restart ? vk_dlssg_status() : DLSSG_RUNS;
|
||||
// NVIDIA's set since the start but not running: "in use: off" and why (vk_dlssg_status; the log has the details).
|
||||
// Through NGX that is NVIDIA's frame generation library missing beside the game, or a GPU it does not run on.
|
||||
const int st = cur != 2 || restart ? DLSSG_RUNS : ngx ? (vk_ngxfg_max() ? DLSSG_RUNS : DLSSG_NO_FILES) : vk_dlssg_status();
|
||||
if (st == DLSSG_NO_GPU) note = UI_MENU_FRAMEGEN_OFF_GPU;
|
||||
else if (st == DLSSG_NO_FILES) note = UI_MENU_FRAMEGEN_OFF_FILES;
|
||||
int nv = opt_choice_index("NX2NAVI_FRAMEGEN", UI_MENU_FRAMEGEN, names, n, at, 0, desc, menu_text(note));
|
||||
if (nv >= 0 && nv < n) opt_apply("NX2NAVI_FRAMEGEN", values[nv]);
|
||||
else if (cur == 2 && upscaled && dlss && ngx) row_framegen_mult_ngx();
|
||||
else if (cur == 2 && upscaled && dlss) row_framegen_mult(restart);
|
||||
}
|
||||
|
||||
|
||||
@@ -562,6 +562,10 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
"here.",
|
||||
[UI_MENU_FRAMEGEN_NEEDS_NONE] = "needs DLSS or FSR 3.1, which do not run here",
|
||||
[UI_MENU_FRAMEGEN_MULT] = "DLSS multiplier",
|
||||
[UI_MENU_FG_MULT_DESC] = "Frames on screen for each frame the game draws with DLSS Frame Generation: 2x adds one "
|
||||
"generated frame, up to 6x on an RTX 50 card. Dynamic adds only the frames needed to "
|
||||
"reach your display's refresh rate. Changes take effect at once.",
|
||||
[UI_MENU_FG_MULT_DYNAMIC] = "Dynamic",
|
||||
[UI_MENU_FRAMEGEN_MULT_DESC] = "Frames on screen for each frame the game draws: 2x adds one generated frame, 3x "
|
||||
"two, 4x three. Higher is smoother but adds a little delay. The frame limit counts "
|
||||
"the frames on screen, so the game draws the limit divided by this: set the limit "
|
||||
|
||||
@@ -283,6 +283,8 @@ typedef enum {
|
||||
// DLSS frame generation (ui/menu.cpp row_framegen, row_framegen_mult): the multiplier row, its notes when this GPU
|
||||
// and driver allow less than 4x, and the frame generation row's notes when DLSS's does not run
|
||||
UI_MENU_FRAMEGEN_MULT, UI_MENU_FRAMEGEN_MULT_DESC,
|
||||
// the multiplier row where DLSS's frame generation runs through NGX itself (ui/menu.cpp row_framegen_mult_ngx)
|
||||
UI_MENU_FG_MULT_DESC, UI_MENU_FG_MULT_DYNAMIC,
|
||||
UI_MENU_FRAMEGEN_MULT_RTX50, // this GPU and driver do 2x only
|
||||
UI_MENU_FRAMEGEN_MULT_UPTO, // printf: "%d", the highest multiplier this GPU and driver allow
|
||||
UI_MENU_FRAMEGEN_MULT_OLD_DLL, // nx2navi_dlssg.dll is older than multi frame generation
|
||||
|
||||
@@ -78,6 +78,7 @@ static struct {
|
||||
PFN_Create create;
|
||||
PFN_Evaluate evaluate;
|
||||
PFN_Release release;
|
||||
void *feat_inst_req; // NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements (vk_ngxfg), NULL when the core lacks it
|
||||
int tried, loaded, available;
|
||||
int ngx_up; // vk_dlaa_init brought NGX up: DLSS serves this run
|
||||
NgxParam *capsp; // the capability parameters (vk_dlaa_init), which DLSS's statistics callback reads and fills
|
||||
@@ -90,6 +91,30 @@ static struct {
|
||||
#endif
|
||||
} N = {.wanted = -1};
|
||||
|
||||
// DLSS Frame Generation through NGX itself (vk_ngxfg_*, at the end of this file): NX2NAVI_FRAMEGEN = dlss where
|
||||
// Streamline does not run, which is every build but the Windows one.
|
||||
static struct {
|
||||
int max; // generated frames per rendered one this GPU allows (MultiFrameCountMax), 0: none
|
||||
NgxHandle *feat;
|
||||
NgxParam *p;
|
||||
uint32_t w, h; // the picture the feature was created for
|
||||
VkFormat fmt;
|
||||
int failed, reset, evals;
|
||||
} FG;
|
||||
|
||||
// Whether this run can generate frames through NGX: DLSS upscaling on at the start, as NGX comes up only then. The
|
||||
// swapchain then gets the presenter's extra images and NGX is asked about frame generation; whether frames are
|
||||
// generated is NX2NAVI_FRAMEGEN = dlss, read live (vk_ngxfg_on).
|
||||
int vk_ngxfg_wanted(void) {
|
||||
#ifdef _WIN32
|
||||
return 0; // Streamline's path there (vk_dlssg.c)
|
||||
#else
|
||||
static int w = -1;
|
||||
if (w < 0) w = cfg_bool("NX2NAVI_DLAA") && strcmp(cfg_value("NX2NAVI_TAA"), "fsr3");
|
||||
return w;
|
||||
#endif
|
||||
}
|
||||
|
||||
typedef void (*SetI)(NgxParam *, const char *, int);
|
||||
typedef void (*SetU)(NgxParam *, const char *, unsigned);
|
||||
typedef void (*SetF)(NgxParam *, const char *, float);
|
||||
@@ -217,6 +242,7 @@ static int core_load(void) {
|
||||
FN(create, "NVSDK_NGX_VULKAN_CreateFeature");
|
||||
FN(evaluate, "NVSDK_NGX_VULKAN_EvaluateFeature");
|
||||
FN(release, "NVSDK_NGX_VULKAN_ReleaseFeature");
|
||||
FN(feat_inst_req, "NVSDK_NGX_VULKAN_GetFeatureInstanceExtensionRequirements"); // optional: frame generation's
|
||||
#undef FN
|
||||
if (!N.init || !N.req_ext || !N.caps || !N.alloc_params || !N.create || !N.evaluate || !N.release) {
|
||||
rt_log("dlaa: the NGX core %s lacks Vulkan entry points: DLAA off", path);
|
||||
@@ -243,12 +269,51 @@ static int listed(const char *const *list, uint32_t n, const char *s) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NVSDK_NGX_FeatureDiscoveryInfo with an application id (the layout of the SDK's struct, 64-bit), for the question
|
||||
// which instance extensions frame generation wants.
|
||||
typedef struct {
|
||||
int sdk, feature;
|
||||
struct { int type; union { struct { const char *id; int engine; const char *ver; } project; unsigned long long app; } v; } ident;
|
||||
const wchar_t *path;
|
||||
const void *info;
|
||||
} NgxDiscovery;
|
||||
typedef NgxResult (*PFN_FeatInstReq)(const NgxDiscovery *, uint32_t *, VkExtensionProperties **);
|
||||
#define NGX_FEATURE_FRAME_GENERATION 11 // NVSDK_NGX_Feature_FrameGeneration
|
||||
// The names NGX lists are copied: its list may not outlive the call, and the device creation keeps the pointers.
|
||||
static char fg_names[16][VK_MAX_EXTENSION_NAME_SIZE];
|
||||
static int fg_nnames;
|
||||
static const char *fg_keep(const char *name) {
|
||||
for (int i = 0; i < fg_nnames; i++)
|
||||
if (!strcmp(fg_names[i], name)) return fg_names[i];
|
||||
if (fg_nnames >= 16) return NULL;
|
||||
snprintf(fg_names[fg_nnames], sizeof fg_names[0], "%s", name);
|
||||
return fg_names[fg_nnames++];
|
||||
}
|
||||
|
||||
void vk_dlaa_instance_exts(const char **ext, uint32_t *n, uint32_t max) {
|
||||
unsigned ni = 0, nd = 0;
|
||||
const char **ie = NULL, **de = NULL;
|
||||
if (!vk_dlaa_wanted() || fsr3() || !core_load() || !NGX_OK(N.req_ext(&ni, &ie, &nd, &de))) return;
|
||||
for (unsigned i = 0; i < ni; i++)
|
||||
if (!listed(ext, *n, ie[i]) && *n < max) ext[(*n)++] = ie[i];
|
||||
// Frame generation's own instance extensions (vk_ngxfg_evaluate). Its device extensions are not asked for: the
|
||||
// core's GetFeatureDeviceExtensionRequirements answered "not implemented" (0xBAD00012) with NVIDIA's 615 Linux
|
||||
// driver, and NGX's general list (vk_dlaa_device_exts) served the feature.
|
||||
if (vk_ngxfg_wanted() && N.feat_inst_req) {
|
||||
static const wchar_t here[] = L".";
|
||||
NgxDiscovery d;
|
||||
memset(&d, 0, sizeof d);
|
||||
d.sdk = NGX_VERSION_API, d.feature = NGX_FEATURE_FRAME_GENERATION;
|
||||
d.ident.type = 0, d.ident.v.app = 231313132ull; // the application id vk_dlaa_init gives NGX
|
||||
d.path = here;
|
||||
uint32_t k = 0;
|
||||
VkExtensionProperties *pr = NULL;
|
||||
if (NGX_OK(((PFN_FeatInstReq)N.feat_inst_req)(&d, &k, &pr)) && pr)
|
||||
for (uint32_t i = 0; i < k; i++) {
|
||||
const char *nm = fg_keep(pr[i].extensionName);
|
||||
if (nm && !listed(ext, *n, nm) && *n < max) ext[(*n)++] = nm, rt_log("ngxfg: instance extension %s", nm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vk_dlaa_device_exts(const VkExtensionProperties *have, uint32_t nhave, const char **ext, uint32_t *n,
|
||||
@@ -354,6 +419,18 @@ void vk_dlaa_init(void) {
|
||||
N.ngx_up = 1;
|
||||
rt_log("dlaa: NGX up; DLSS %s%s", avail ? "available" : "NOT available (is " DLSS_MODEL " next to the executable?)",
|
||||
driver ? "; the driver needs an update" : "");
|
||||
if (vk_ngxfg_wanted() && caps) {
|
||||
int fga = 0;
|
||||
unsigned mx = 0;
|
||||
p_geti(caps, "FrameGeneration.Available", &fga);
|
||||
if (!p_getu(caps, "DLSSG.MultiFrameCountMax", &mx)) mx = 1;
|
||||
// MultiFrameCountMax said 3 (4x) with the 310.9.16 library on an RTX 5090, yet that library evaluated 5
|
||||
// generated frames without an error on NVIDIA's 615 driver, and NVIDIA ships up to 6x on RTX 50 cards: a GPU
|
||||
// with multi-frame generation gets up to 5 (6x).
|
||||
FG.max = fga ? (mx >= 3 ? 5 : mx < 1 ? 1 : (int)mx) : 0;
|
||||
rt_log("ngxfg: DLSS Frame Generation %s (up to %dx)",
|
||||
fga ? "available" : "NOT available (is libnvidia-ngx-dlssg.so.<version> next to the executable?)", FG.max + 1);
|
||||
}
|
||||
}
|
||||
|
||||
int vk_dlaa_available(void) { return vk_dlaa_wanted() && N.available; }
|
||||
@@ -1250,6 +1327,9 @@ void vk_dlaa_release(void) {
|
||||
if (!c) return;
|
||||
for (int i = 0; i < 5; i++)
|
||||
if (D.sizes[i].feat) N.release(D.sizes[i].feat), D.sizes[i].feat = NULL;
|
||||
if (FG.feat) N.release(FG.feat); // frame generation's feature and parameters (vk_ngxfg_evaluate), before NGX goes
|
||||
if (FG.p) N.destroy_params(FG.p);
|
||||
memset(&FG, 0, sizeof FG);
|
||||
if (D.params) N.destroy_params(D.params);
|
||||
if (N.caps_map) N.destroy_params(N.caps_map);
|
||||
for (int i = 0; i < 5; i++) destroy_size(&D.sizes[i]);
|
||||
@@ -1994,3 +2074,129 @@ int vk_dlaa_skip_draw(VkExec *ex, const GfxShader *fs) {
|
||||
return !fxaa;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------- DLSS Frame Generation through NGX (vk_ngxfg)
|
||||
// gfx_present.c records this into the present command buffer once the frame's picture, HUD included, is in bb. It
|
||||
// generates count frames between the previous call's picture and this one into out[0..count-1] (MultiFrameIndex 1 to
|
||||
// count) and a copy of bb into real; gfx_present.c's presenter shows them in that order, then the rendered one. The
|
||||
// inputs are the ones the Streamline path gets (vk_dlssg.c): the depth, motion vectors, jitter and camera of the frame
|
||||
// DLSS last ran on, and the picture without the HUD.
|
||||
int vk_ngxfg_max(void) { return vk_ngxfg_wanted() && N.available && !FG.failed ? FG.max : 0; }
|
||||
int vk_ngxfg_on(void) { return vk_ngxfg_max() && !strcmp(cfg_value("NX2NAVI_FRAMEGEN"), "dlss"); }
|
||||
void vk_ngxfg_reset(void) { FG.reset = 1; }
|
||||
|
||||
int vk_ngxfg_evaluate(VkCommandBuffer cb, const VkNgxImage *bb, const VkNgxImage *hudless, const VkNgxImage *out, int count,
|
||||
const VkNgxImage *real) {
|
||||
if (!vk_ngxfg_max() || count < 1) return 0;
|
||||
if (count > FG.max) count = FG.max;
|
||||
VkFfxImage d, m;
|
||||
VkImageView dv, mvv;
|
||||
VkDlaaCamera c;
|
||||
float jx = 0, jy = 0;
|
||||
int reset = 0;
|
||||
if (!vk_dlaa_fg_inputs(&d, &m, &jx, &jy, &reset) || !vk_dlaa_fg_views(&dv, &mvv) || !vk_dlaa_fg_camera(&c)) {
|
||||
FG.reset = 1; // a frame without DLSS's inputs: the next generation has no valid previous frame
|
||||
return 0;
|
||||
}
|
||||
if (!FG.p && !NGX_OK(N.alloc_params(&FG.p))) {
|
||||
FG.failed = 1;
|
||||
rt_log("ngxfg: parameter allocation failed: frame generation off");
|
||||
return 0;
|
||||
}
|
||||
NgxParam *p = FG.p;
|
||||
if (FG.feat && (FG.w != bb->w || FG.h != bb->h || FG.fmt != bb->fmt)) {
|
||||
N.release(FG.feat);
|
||||
FG.feat = NULL;
|
||||
}
|
||||
if (!FG.feat) {
|
||||
p_setu(p, "CreationNodeMask", 1);
|
||||
p_setu(p, "VisibilityNodeMask", 1);
|
||||
p_setu(p, "Width", bb->w);
|
||||
p_setu(p, "Height", bb->h);
|
||||
p_setu(p, "DLSSG.BackbufferFormat", (unsigned)bb->fmt);
|
||||
NgxResult r = N.create(cb, NGX_FEATURE_FRAME_GENERATION, p, &FG.feat);
|
||||
if (!NGX_OK(r) || !FG.feat) {
|
||||
FG.failed = 1, FG.feat = NULL;
|
||||
rt_log("ngxfg: DLSS Frame Generation feature creation failed (%08x): frame generation off", (unsigned)r);
|
||||
return 0;
|
||||
}
|
||||
FG.w = bb->w, FG.h = bb->h, FG.fmt = bb->fmt, FG.reset = 1;
|
||||
rt_log("ngxfg: DLSS Frame Generation created at %ux%u (format %d), depth and motion %ux%u", bb->w, bb->h,
|
||||
(int)bb->fmt, d.width, d.height);
|
||||
}
|
||||
NgxResourceVK rb = res_image(bb->view, bb->img, bb->fmt, bb->w, bb->h, VK_IMAGE_ASPECT_COLOR_BIT, 0);
|
||||
NgxResourceVK rz = res_image(dv, d.image, d.format, d.width, d.height, VK_IMAGE_ASPECT_DEPTH_BIT, 0);
|
||||
NgxResourceVK rm = res_image(mvv, m.image, m.format, m.width, m.height, VK_IMAGE_ASPECT_COLOR_BIT, 0);
|
||||
NgxResourceVK rh = hudless ? res_image(hudless->view, hudless->img, hudless->fmt, hudless->w, hudless->h,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT, 0) : rb;
|
||||
NgxResourceVK rr = res_image(real->view, real->img, real->fmt, real->w, real->h, VK_IMAGE_ASPECT_COLOR_BIT, 1);
|
||||
p_setp(p, "DLSSG.Backbuffer", &rb);
|
||||
p_setp(p, "DLSSG.Depth", &rz);
|
||||
p_setp(p, "DLSSG.MVecs", &rm);
|
||||
p_setp(p, "DLSSG.HUDLess", hudless ? &rh : NULL);
|
||||
p_setp(p, "DLSSG.UI", NULL);
|
||||
p_setp(p, "DLSSG.UIAlpha", NULL);
|
||||
p_setp(p, "DLSSG.BidirectionalDistortionField", NULL);
|
||||
p_setp(p, "DLSSG.OutputReal", &rr);
|
||||
p_setp(p, "DLSSG.OutputDisableInterpolation", NULL);
|
||||
p_setp(p, "DLSSG.CameraViewToClip", c.view_to_clip);
|
||||
p_setp(p, "DLSSG.ClipToCameraView", c.clip_to_view);
|
||||
p_setp(p, "DLSSG.ClipToLensClip", NULL);
|
||||
p_setp(p, "DLSSG.ClipToPrevClip", c.clip_to_prev_clip);
|
||||
p_setp(p, "DLSSG.PrevClipToClip", c.prev_clip_to_clip);
|
||||
p_setf(p, "DLSSG.JitterOffsetX", jx);
|
||||
p_setf(p, "DLSSG.JitterOffsetY", jy);
|
||||
p_setf(p, "DLSSG.MvecScaleX", 1.0f / (float)m.width); // the vectors are in render pixels
|
||||
p_setf(p, "DLSSG.MvecScaleY", 1.0f / (float)m.height);
|
||||
p_setf(p, "DLSSG.CameraPinholeOffsetX", 0.0f);
|
||||
p_setf(p, "DLSSG.CameraPinholeOffsetY", 0.0f);
|
||||
static const char *const pos[] = {"DLSSG.CameraPosX", "DLSSG.CameraPosY", "DLSSG.CameraPosZ"};
|
||||
static const char *const up[] = {"DLSSG.CameraUpX", "DLSSG.CameraUpY", "DLSSG.CameraUpZ"};
|
||||
static const char *const rt[] = {"DLSSG.CameraRightX", "DLSSG.CameraRightY", "DLSSG.CameraRightZ"};
|
||||
static const char *const fw[] = {"DLSSG.CameraFwdX", "DLSSG.CameraFwdY", "DLSSG.CameraFwdZ"};
|
||||
for (int i = 0; i < 3; i++)
|
||||
p_setf(p, pos[i], c.pos[i]), p_setf(p, up[i], c.up[i]), p_setf(p, rt[i], c.right[i]), p_setf(p, fw[i], c.fwd[i]);
|
||||
p_setf(p, "DLSSG.CameraNear", 0.1f); // Ocarina's projection, as vk_dlssg.c gives it
|
||||
p_setf(p, "DLSSG.CameraFar", 1e8f);
|
||||
p_setf(p, "DLSSG.CameraFOV", c.fov_y);
|
||||
p_setf(p, "DLSSG.CameraAspectRatio", c.aspect);
|
||||
p_setu(p, "DLSSG.ColorBuffersHDR", 0);
|
||||
p_setu(p, "DLSSG.DepthInverted", 0);
|
||||
p_setu(p, "DLSSG.CameraMotionIncluded", 1);
|
||||
p_setu(p, "DLSSG.Reset", (unsigned)(reset || FG.reset));
|
||||
p_setu(p, "DLSSG.AutomodeOverrideReset", 0);
|
||||
p_setu(p, "DLSSG.NotRenderingGameFrames", 0);
|
||||
p_setu(p, "DLSSG.OrthoProjection", 0);
|
||||
p_setu(p, "DLSSG.MvecDilated", 0);
|
||||
p_setu(p, "DLSSG.MenuDetectionEnabled", 0);
|
||||
struct { const char *base; uint32_t w, h; } rects[] = {
|
||||
{"DLSSG.MVecsSubrect", m.width, m.height}, {"DLSSG.DepthSubrect", d.width, d.height},
|
||||
{"DLSSG.HUDLessSubrect", bb->w, bb->h}, {"DLSSG.InputBackbufferSubrect", bb->w, bb->h},
|
||||
{"DLSSG.OutputInterpolatedSubrect", bb->w, bb->h}, {"DLSSG.OutputRealSubrect", bb->w, bb->h}};
|
||||
for (size_t i = 0; i < sizeof rects / sizeof *rects; i++) {
|
||||
char k[64];
|
||||
snprintf(k, sizeof k, "%sBaseX", rects[i].base), p_setu(p, k, 0);
|
||||
snprintf(k, sizeof k, "%sBaseY", rects[i].base), p_setu(p, k, 0);
|
||||
snprintf(k, sizeof k, "%sWidth", rects[i].base), p_setu(p, k, rects[i].w);
|
||||
snprintf(k, sizeof k, "%sHeight", rects[i].base), p_setu(p, k, rects[i].h);
|
||||
}
|
||||
p_setu(p, "DLSSG.MultiFrameCount", (unsigned)count);
|
||||
int made = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
NgxResourceVK ro = res_image(out[i].view, out[i].img, out[i].fmt, out[i].w, out[i].h, VK_IMAGE_ASPECT_COLOR_BIT, 1);
|
||||
p_setp(p, "DLSSG.OutputInterpolated", &ro);
|
||||
p_setu(p, "DLSSG.MultiFrameIndex", (unsigned)(i + 1));
|
||||
NgxResult r = N.evaluate(cb, FG.feat, p, NULL);
|
||||
if (!NGX_OK(r)) {
|
||||
static int told;
|
||||
if (told++ < 5) rt_log("ngxfg: evaluation %d of %d failed (%08x)", i + 1, count, (unsigned)r);
|
||||
break;
|
||||
}
|
||||
made++;
|
||||
}
|
||||
full_barrier(cb);
|
||||
FG.reset = 0;
|
||||
if (made && (++FG.evals == 1 || FG.evals % 3600 == 0))
|
||||
rt_log("ngxfg: %d frames with generation (%d generated per rendered frame)", FG.evals, made);
|
||||
return made;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,17 @@
|
||||
#define DLAA_SCENE_MIN_W 320u
|
||||
|
||||
int vk_dlaa_wanted(void); // the setting is on (the device may still lack DLSS)
|
||||
// DLSS Frame Generation through NGX itself (vk_dlaa.c vk_ngxfg_*, gfx_present.c's presenter): NX2NAVI_FRAMEGEN = dlss
|
||||
// where Streamline does not run (every build but the Windows one, where these all answer 0).
|
||||
typedef struct { VkImage img; VkImageView view; VkFormat fmt; uint32_t w, h; } VkNgxImage; // in GENERAL
|
||||
int vk_ngxfg_wanted(void); // this run can generate frames through NGX (DLSS upscaling on at the start)
|
||||
int vk_ngxfg_max(void); // generated frames per rendered one the GPU allows (1 = 2x, 5 = 6x), 0 when unavailable
|
||||
int vk_ngxfg_on(void); // ... and NX2NAVI_FRAMEGEN = dlss now (the menu switches it live)
|
||||
void vk_ngxfg_reset(void); // the next generation starts without history (a resize, the menu)
|
||||
// Records count generated frames into out[] and a copy of bb into real; returns how many were made (0: none, and real
|
||||
// is not written).
|
||||
int vk_ngxfg_evaluate(VkCommandBuffer cb, const VkNgxImage *bb, const VkNgxImage *hudless, const VkNgxImage *out, int count,
|
||||
const VkNgxImage *real);
|
||||
int vk_dlaa_fsr3(void); // the setting is on and its upscaler is AMD FSR 3.1, which loads
|
||||
int vk_dlaa_needs_restart(void); // the menu's upscaler cannot start in this run
|
||||
// Frame generation (vk_fg.cpp): the depth and motion vectors of the frame DLSS or FSR 3.1 last ran on, with its jitter
|
||||
|
||||
Reference in New Issue
Block a user