diff --git a/runtime/host.h b/runtime/host.h index 4d40a88f..c8ff0e3a 100644 --- a/runtime/host.h +++ b/runtime/host.h @@ -63,7 +63,10 @@ void perf_count(int kind); // pipe_create_in), guest memory maps (heap size, MapPhysicalMemory), NVN memory pool initialize and finalize // (vk_memory.c vk_pool_init, vk_pool_fini), device memory VMA allocates (blocks and dedicated memory, vk_device.c // timed_alloc), and DLSS and DLSS frame generation feature creates (vk_dlaa.c). perf_mark_begin returns 0 while the trace is off, so a normal run pays one branch per site. -enum { PERF_M_FILE, PERF_M_ZSTD, PERF_M_PIPE, PERF_M_GMEM, PERF_M_POOL, PERF_M_DEVMEM, PERF_M_NGX, PERF_M_N }; +// UPWAIT: the upscalers' waits for the GPU before they free what they let go of (vk_dlaa.c vk_upscaler_device_idle); +// UPREL: their releases of DLSS features and FSR contexts (vk_dlaa.c, vk_ffx.cpp, vk_ffx4.cpp). +enum { PERF_M_FILE, PERF_M_ZSTD, PERF_M_PIPE, PERF_M_GMEM, PERF_M_POOL, PERF_M_DEVMEM, PERF_M_NGX, PERF_M_UPWAIT, + PERF_M_UPREL, PERF_M_N }; uint64_t perf_mark_begin(void); void perf_mark_end(int kind, uint64_t t0, uint64_t bytes); // nothing when t0 is 0 void perf_mark_add(int kind, uint64_t ns, uint64_t bytes); // nothing while the trace is off diff --git a/runtime/perf.c b/runtime/perf.c index 5522a250..063e2856 100644 --- a/runtime/perf.c +++ b/runtime/perf.c @@ -151,7 +151,7 @@ void perf_count(int kind) { // names them on a slow frame's line and adds them to the 60-frame window's line. static const char *const mark_name[PERF_M_N] = {"file reads", "zstd decodes", "pipeline creates", "guest memory maps", "pool init/finalize", "device memory allocations", - "DLSS feature creates"}; + "DLSS feature creates", "upscaler device waits", "upscaler releases"}; static volatile int64_t mk_ns[PERF_M_N], mk_n[PERF_M_N], mk_bytes[PERF_M_N], mk_max[PERF_M_N]; // since the last present typedef struct { uint64_t ns[PERF_M_N], n[PERF_M_N], bytes[PERF_M_N], max[PERF_M_N]; } MarkSums; static MarkSums mk_win; // the 60-frame window's, present thread only diff --git a/runtime/vk_dlaa.c b/runtime/vk_dlaa.c index 1eb62c00..9b3bea1d 100644 --- a/runtime/vk_dlaa.c +++ b/runtime/vk_dlaa.c @@ -726,6 +726,22 @@ static int idle_free(void) { return m; } +// The upscalers' waits for the device to be idle before they free what the GPU may still use (vk_dlaa.c, vk_ffx.cpp, +// vk_ffx4.cpp), with the queue lock held: the present's flip, the completion thread's hold release and every upload +// submit wait as long. NX2NAVI_TRACE=perf names them "upscaler device waits" on a slow frame's line. +void vk_upscaler_device_idle(void) { + uint64_t t0 = perf_mark_begin(); + vk_queue_lock(), vkDeviceWaitIdle(vk_ctx()->device), vk_queue_unlock(); + perf_mark_end(PERF_M_UPWAIT, t0, 0); +} + +// A DLSS feature's release, timed as "upscaler releases" (NX2NAVI_TRACE=perf). +static void feat_release(NgxHandle *f) { + uint64_t t0 = perf_mark_begin(); + N.release(f); + perf_mark_end(PERF_M_UPREL, t0, 0); +} + // The images and views of a size, then its feature. The device must be idle: free_size waits for it, vk_dlaa_release // has. Whatever was not made is skipped, so a half-made size goes the same way. static void destroy_size(struct Size *e) { @@ -744,13 +760,13 @@ static void destroy_size(struct Size *e) { if (e->dist_view[i]) vkDestroyImageView(c->device, e->dist_view[i], NULL); if (e->dist[i]) vmaDestroyImage(c->vma, e->dist[i], e->dist_mem[i]); } - if (e->feat && N.release) N.release(e->feat), feat_released = 1; + if (e->feat && N.release) feat_release(e->feat), feat_released = 1; if (D.host == e) D.host = NULL; memset(e, 0, sizeof *e); } static void free_size(struct Size *e) { - vk_queue_lock(), vkDeviceWaitIdle(vk_ctx()->device), vk_queue_unlock(); // a third scene size: the evicted images may still be in flight + vk_upscaler_device_idle(); // a third scene size: the evicted images may still be in flight destroy_size(e); } @@ -1420,7 +1436,7 @@ static void destroy_snap(struct Snap *s) { } static void free_snap(struct Snap *s) { - vk_queue_lock(), vkDeviceWaitIdle(vk_ctx()->device), vk_queue_unlock(); // a fourth size: the evicted images may still be in flight + vk_upscaler_device_idle(); // a fourth size: the evicted images may still be in flight destroy_snap(s); } @@ -1871,8 +1887,8 @@ static void stale_sizes(const struct Size *cur, const struct Size *host) { } else if (s->feat && D.evals >= s->feat_used + SIZE_STALE) { rt_log("dlaa: the DLSS feature at %ux%u -> %ux%u went unused for %d frames while its size ran on another " "size's: released", s->w, s->h, s->ow, s->oh, SIZE_STALE); - vk_queue_lock(), vkDeviceWaitIdle(vk_ctx()->device), vk_queue_unlock(); - N.release(s->feat), s->feat = NULL, feat_released = 1; + vk_upscaler_device_idle(); + feat_release(s->feat), s->feat = NULL, feat_released = 1; } } } @@ -1886,8 +1902,8 @@ static void release_kept(const struct Size *making, int all) { struct Size *s = &D.sizes[i]; if (s == making || !s->feat) continue; if (!all && (uint64_t)s->w * s->h <= (uint64_t)making->w * making->h) continue; - if (!n++) vk_queue_lock(), vkDeviceWaitIdle(vk_ctx()->device), vk_queue_unlock(); - N.release(s->feat), s->feat = NULL; + if (!n++) vk_upscaler_device_idle(); + feat_release(s->feat), s->feat = NULL; if (D.last == s) D.last = NULL; if (D.host == s) D.host = NULL; } @@ -2292,8 +2308,6 @@ own_feature: // The feature is made for the size of its entry: this frame's own, or the larger one of the DLSS mode's, which // was released since (release_kept) and takes this frame's size as a sub-rectangle. const uint32_t fw = host->w, fh = host->h; - // NX2NAVI_TRACE=perf: the release of the kept features (a device idle wait) and the create, as one mark - uint64_t t0 = perf_mark_begin(); if (idle_free() != 2) release_kept(host, feat_released); feat_released = 0; p_setu(p, "CreationNodeMask", 1); @@ -2317,6 +2331,8 @@ own_feature: p_setu(p, "DLSS.Hint.Render.Preset.Balanced", (unsigned)pk); p_setu(p, "DLSS.Hint.Render.Preset.Performance", (unsigned)pm); p_setu(p, "DLSS.Hint.Render.Preset.UltraPerformance", (unsigned)pl); + // NX2NAVI_TRACE=perf: the create alone; the wait and the releases of release_kept have marks of their own + uint64_t t0 = perf_mark_begin(); NgxResult r = N.create(cb, NGX_FEATURE_SUPERSAMPLING, p, &host->feat); perf_mark_end(PERF_M_NGX, t0, 0); if (!NGX_OK(r) || !host->feat) return dl_fail("DLSS feature creation", r); @@ -2469,7 +2485,7 @@ static void hudless_from(VkCommandBuffer cb, VkImage src, uint32_t level, uint32 if (vk_hdr_native()) return; VkFormat fmt = hdr ? VK_FORMAT_R16G16B16A16_SFLOAT : VK_FORMAT_R8G8B8A8_UNORM; if (D.hl && (D.hl_w != w || D.hl_h != h || D.hl_fmt != fmt)) { - vk_queue_lock(), vkDeviceWaitIdle(c->device), vk_queue_unlock(); + vk_upscaler_device_idle(); hl_destroy(); } if (!D.hl) { @@ -2592,7 +2608,7 @@ static int pb_images(VkCommandBuffer cb, uint32_t w, uint32_t h) { PB.used = D.presents; if (PB.scene && PB.w == w && PB.h == h) return 1; if (PB.scene || PB.comp) { - vk_queue_lock(), vkDeviceWaitIdle(vk_ctx()->device), vk_queue_unlock(); + vk_upscaler_device_idle(); pb_destroy(); } VkImageUsageFlags u = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | @@ -2832,7 +2848,7 @@ static void retire_idle(void) { } if (!retire_off_at) retire_off_at = now ? now : 1; if (now < retire_off_at + RETIRE_PRESENTS || !up_holds()) return; - vk_queue_lock(), vkDeviceWaitIdle(vk_ctx()->device), vk_queue_unlock(); + vk_upscaler_device_idle(); for (int i = 0; i < SNAPS; i++) destroy_snap(&R.s[i]); vk_ffx_release_idle(); if (D.failed) { // as idle_release does for a size: frame generation is handed nothing of one from here on @@ -3056,7 +3072,7 @@ static void idle_release(void) { if (s->feat) { rt_log("dlaa: the DLSS feature at %ux%u went unused for %d presents with the upscaler off: released; " "its size's motion images stay for frame generation", s->w, s->h, IDLE_FREE_PRESENTS); - N.release(s->feat), s->feat = NULL, feat_released = 1; + feat_release(s->feat), s->feat = NULL, feat_released = 1; } if (D.host == s) D.host = NULL; continue; diff --git a/runtime/vk_ffx.cpp b/runtime/vk_ffx.cpp index e4c4fe7f..1b2cf1ef 100644 --- a/runtime/vk_ffx.cpp +++ b/runtime/vk_ffx.cpp @@ -141,7 +141,9 @@ const uint64_t UP_IDLE_NS = 90ull * 1000000000ull; void destroy_up(Up *u, const char *why) { rt_log("ffx: the FSR 3.1 context for %ux%u (render sizes up to %ux%u) destroyed: %s", u->ow, u->oh, u->mw, u->mh, why); + uint64_t t0 = perf_mark_begin(); // NX2NAVI_TRACE=perf: "upscaler releases" A.destroy(&u->ctx, nullptr); + perf_mark_end(PERF_M_UPREL, t0, 0); *u = Up(); } @@ -195,7 +197,7 @@ Up *context_for(uint32_t w, uint32_t h, uint32_t ow, uint32_t oh) { int waited = 0; for (Up &u : U) // the contexts of this output size for another render size, once no command buffer holds them if (u.ctx && &u != found && u.ow == ow && u.oh == oh && up_tick - u.used >= UP_RECENT) { - if (!waited++) vk_queue_lock(), vkDeviceWaitIdle(c->device), vk_queue_unlock(); // their work may be in flight + if (!waited++) vk_upscaler_device_idle(); // their work may be in flight destroy_up(&u, "the render size changed"); } if (found) { @@ -211,7 +213,7 @@ Up *context_for(uint32_t w, uint32_t h, uint32_t ow, uint32_t oh) { if (up_tick - u.used >= UP_RECENT && (!lru || u.used < lru->used)) lru = &u; } if (!slot && lru) { - vk_queue_lock(), vkDeviceWaitIdle(c->device), vk_queue_unlock(); // its work may still be in flight + vk_upscaler_device_idle(); // its work may still be in flight destroy_up(lru, "its slot went to another output size"); slot = lru; } diff --git a/runtime/vk_ffx.h b/runtime/vk_ffx.h index a2d7471d..188d75c6 100644 --- a/runtime/vk_ffx.h +++ b/runtime/vk_ffx.h @@ -16,6 +16,9 @@ typedef struct { typedef struct { float fov_y, znear, zfar, meters; } VkFfxCamera; // vk_dlaa.c: the share (0..1) of the extra sharpening FSR 3.1 gets at a render width w of an output width ow float vk_dlaa_fsr3_boost(uint32_t w, uint32_t ow); +// vk_dlaa.c: a wait for the device to be idle, the queue lock held, before an upscaler frees what the GPU may still +// use (NX2NAVI_TRACE=perf: "upscaler device waits") +void vk_upscaler_device_idle(void); int vk_ffx_load(void); // 1 when the library is there with its entry points (loaded once) // Whether FSR 3.1 upscaling can run in this build, for the settings menu: after vk_ffx_load ran, what it found; before, diff --git a/runtime/vk_ffx4.cpp b/runtime/vk_ffx4.cpp index 5728dffb..c5862695 100644 --- a/runtime/vk_ffx4.cpp +++ b/runtime/vk_ffx4.cpp @@ -207,7 +207,9 @@ const uint64_t UP_IDLE_NS = 90ull * 1000000000ull; void destroy_up(Up *u, const char *why) { rt_log("ffx4: the %ux%u context (render sizes up to %ux%u) destroyed: %s", u->ow, u->oh, u->mw, u->mh, why); + uint64_t t0 = perf_mark_begin(); // NX2NAVI_TRACE=perf: "upscaler releases" A.destroy(&u->ctx, nullptr); + perf_mark_end(PERF_M_UPREL, t0, 0); *u = Up(); } @@ -288,11 +290,10 @@ Up *context_for(uint32_t w, uint32_t h, uint32_t ow, uint32_t oh) { for (Up &u : U) if (u.ctx && u.ow == ow && u.oh == oh && u.drs == drs && (drs ? w <= u.mw && h <= u.mh : w == u.mw && h == u.mh)) found = &u; - const VkCtx *c = vk_ctx(); int waited = 0; for (Up &u : U) // the contexts of this output size for another render size, once no command buffer holds them if (u.ctx && &u != found && u.ow == ow && u.oh == oh && up_tick - u.used >= UP_RECENT) { - if (!waited++) vk_queue_lock(), vkDeviceWaitIdle(c->device), vk_queue_unlock(); // their work may be in flight + if (!waited++) vk_upscaler_device_idle(); // their work may be in flight destroy_up(&u, "the render size changed"); } if (found) { @@ -308,7 +309,7 @@ Up *context_for(uint32_t w, uint32_t h, uint32_t ow, uint32_t oh) { if (up_tick - u.used >= UP_RECENT && (!lru || u.used < lru->used)) lru = &u; } if (!slot && lru) { - vk_queue_lock(), vkDeviceWaitIdle(c->device), vk_queue_unlock(); // its work may still be in flight + vk_upscaler_device_idle(); // its work may still be in flight destroy_up(lru, "its slot went to another output size"); slot = lru; } @@ -318,7 +319,7 @@ Up *context_for(uint32_t w, uint32_t h, uint32_t ow, uint32_t oh) { Up *u = create(slot, mw, mh, ow, oh, drs); if (!u) { int freed = 0; - vk_queue_lock(), vkDeviceWaitIdle(c->device), vk_queue_unlock(); + vk_upscaler_device_idle(); for (Up &o : U) if (o.ctx && up_tick - o.used >= UP_RECENT) destroy_up(&o, "room for a context whose creation failed"), freed++; freed += vk_ffx_trim_idle(UP_RECENT);