mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Let FSR 3.1 and FSR 4 destroy a context they let go of once the GPU is past its last upscale, instead of waiting for the device.
vk_ffx.cpp context_for (two places) and vk_ffx4.cpp context_for (three) waited for the whole device to be idle, with the queue lock held, on the thread that replays the game's submissions, before they destroyed an upscale context of another render size, the least recently used one for a new output size, or (FSR 4) every idle one after a refused creation: a stall of the present's flip, the completion thread and upload submits at every pause-screen size, reload, window size, dynamic resolution step or mode change with FSR on. A context that goes is now let go of (retire_up): it goes into a list (vk_up_defer.h) with the timeline value of the submission of its last upscale (Up.fence, set after each dispatch) and is destroyed on the same thread once the GPU is past it, at the points where vk_dlaa.c takes its own list (every evaluation, every pass while none runs, and after something is let go of). The 90-second trims, vk_ffx_trim_idle and vk_ffx_release_idle (the upscaler off, which no longer needs retire_idle's wait) let go of theirs the same way. FSR 4's create descriptors, which the API asks to stay live until the context is destroyed, move from the slot into a calloc'd Made that goes with the context, so the slot can take a new context while the old one waits. A refused FSR 4 creation now waits on the timeline from the host for the contexts let go of, no queue touched (vk_upscaler_gpu_wait), and tries again when any went; with NX2NAVI_UPSCALER_IDLE_WAIT=1 that wait and every let go wait for the device as before. docs/architecture.md says where the upscalers' releases go. C-5 is narrowed: on the default path no upscaler waits for the device any more, so these waits no longer touch the frame generation swapchain's own queues; vk_fg.cpp, vk_fg4.cpp, gfx_present.c, vk_hdr.c, vk_fsr.c and vk_color.c keep theirs. Not reproduced before the change (no FSR session log here). Every runtime file compiles, vk_dlaa.c, vk_ffx.cpp and vk_ffx4.cpp with -Wall -Wextra and no warning; vk_ffx4.cpp's non-Windows stubs compile against vk_ffx.h; up_defer_check passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -377,7 +377,11 @@ keeps per-core data without locks and relies on that, so the host models it (`ru
|
||||
(a quarter of the video memory budget) stays as the safety net for a burst of area changes, and young textures may
|
||||
pass it, up to twice it, while the card and the system's commit have room. Times are wall seconds from a clock of
|
||||
the presents, not present counts. What a switched-off upscaler or SMAA held is freed after it has gone unused for a
|
||||
while (`vk_dlaa.c` `idle_release`, `vk_fsr.c` `idle_note`, `vk_aa.c` `work_idle`).
|
||||
while (`vk_dlaa.c` `idle_release`, `vk_fsr.c` `idle_note`, `vk_aa.c` `work_idle`). What DLSS or FSR lets go of at
|
||||
a change of size or mode (a network, an FSR context, a work image) goes once the GPU is past the last submission that
|
||||
used it, and an image frame generation reads at a present some presents later, without a wait for the device on the
|
||||
replaying thread (`vk_dlaa.c` "what the upscalers let go of", `vk_up_defer.h`; `upscaler_idle_wait = 1` waits as
|
||||
before).
|
||||
- Translated shaders at rest: the shader memory cache keeps every shader for the whole session, but a shader's SPIR-V
|
||||
is read only while modules are made of it. A background thread compresses (zstd, checked by decoding it) the SPIR-V
|
||||
of each shader no reader used for `shader_compress_idle_s` seconds (30) and frees the plain copy; a reader pins it
|
||||
|
||||
@@ -803,7 +803,9 @@ static void dead_release(const UpdEntry *e, void *teardown) {
|
||||
|
||||
// What the upscalers let go of goes once the GPU is past it: DLSS's here, FSR's in vk_ffx.cpp and vk_ffx4.cpp.
|
||||
static void upscalers_drain(void) {
|
||||
if (dead.n) upd_take(&dead, vk_sync_fence_done(), presents_now(), dead_release, NULL);
|
||||
const uint64_t done = vk_sync_fence_done();
|
||||
if (dead.n) upd_take(&dead, done, presents_now(), dead_release, NULL);
|
||||
vk_ffx_dead_take(done); // returns at once with both of its lists empty
|
||||
}
|
||||
|
||||
// A wait for the device, then everything the upscalers let go of that a submission made so far names goes. An image a
|
||||
@@ -812,6 +814,7 @@ void vk_upscaler_settle(void) {
|
||||
const uint64_t issued = vk_sync_issued(); // read before the wait: every submission up to it is done after it
|
||||
vk_upscaler_device_idle();
|
||||
upd_take(&dead, issued, presents_now(), dead_release, NULL);
|
||||
vk_ffx_dead_take(issued);
|
||||
}
|
||||
|
||||
void vk_upscaler_let_go(void) {
|
||||
@@ -3054,7 +3057,6 @@ 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;
|
||||
for (int i = 0; i < SNAPS; i++) retire_snap(&R.s[i]);
|
||||
if (vk_ffx_holds()) vk_upscaler_device_idle(); // vk_ffx_release_idle destroys the FSR contexts at once
|
||||
vk_ffx_release_idle();
|
||||
if (D.failed) { // as idle_release does for a size: frame generation is handed nothing of one from here on
|
||||
for (int i = 0; i < DLSS_SIZES; i++) retire_size(&D.sizes[i]);
|
||||
|
||||
@@ -21,12 +21,14 @@
|
||||
#include "third_party/ffx_api/ffx_api.h"
|
||||
#include "third_party/ffx_api/ffx_upscale.h"
|
||||
#include "third_party/ffx_api/vk/ffx_api_vk.h"
|
||||
#include "vk_up_defer.h"
|
||||
|
||||
extern "C" {
|
||||
#include "cfg.h"
|
||||
#include "host.h"
|
||||
#include "platform/platform.h"
|
||||
#include "vk.h"
|
||||
#include "vk_exec.h"
|
||||
#include "vk_ffx.h"
|
||||
#include "vk_fg.h"
|
||||
}
|
||||
@@ -119,37 +121,58 @@ ffxReturnCode_t create_counted(ffxContext *ctx, ffxCreateContextDescHeader *desc
|
||||
// each dispatch and without that flag, preceded an access violation in AMD's driver inside FSR 4's dispatch at the
|
||||
// first frame after a switch from 67 % to 100 % (vk_ffx4.cpp keeps its contexts the same way). With the game's dynamic
|
||||
// resolution on (NX2NAVI_DYN_RES, dynres.c), whose steps change the render size every few seconds, one context with
|
||||
// that flag takes them all. A context of another render size goes once no command buffer can still hold it
|
||||
// (UP_RECENT), so a switch keeps at most two of an output size for a few frames, and the game's second scenes, of
|
||||
// another output size, keep their own. When all slots are taken, the least recently used one is destroyed for the new
|
||||
// size once the GPU is idle, as vk_dlaa.c free_size does for DLSS.
|
||||
// that flag takes them all. A context of another render size goes once it is not among the recent ones (UP_RECENT), so
|
||||
// a switch keeps at most two of an output size for a few frames, and the game's second scenes, of another output size,
|
||||
// keep their own. When all slots are taken, the least recently used one goes for the new size. A context that goes is
|
||||
// let go of (retire_up) and destroyed once the GPU is past the submission of its last upscale (g_dead, vk_dlaa.c "what
|
||||
// the upscalers let go of"), without a wait for the device.
|
||||
struct Up {
|
||||
ffxContext ctx = nullptr;
|
||||
uint32_t mw = 0, mh = 0, ow = 0, oh = 0; // the render size it was made for (the largest it takes with drs); the output size
|
||||
bool drs = false; // made for dynamic resolution (FFX_UPSCALE_ENABLE_DYNAMIC_RESOLUTION)
|
||||
uint64_t used = 0, used_ns = 0; // up_tick and the time when context_for last returned it
|
||||
uint64_t vram = 0; // its video memory as the library reported it at its creation, 0 not told
|
||||
uint64_t fence = 0; // the submission of its last upscale (vk_exec_recording_fence), 0 none
|
||||
} U[4];
|
||||
uint64_t up_tick;
|
||||
// A context used in the last few calls may sit in a command buffer that is not submitted yet, where waiting for the
|
||||
// GPU does not cover it: a frame makes one or two calls (the scene, and a second scene after it), so this is a few
|
||||
// frames back.
|
||||
// The contexts used in the last few calls stay: a frame makes one or two calls (the scene, and a second scene after
|
||||
// it), so this is a few frames back. That is the policy only: a context that goes waits for the GPU through its fence,
|
||||
// also when the command buffer that names it is not submitted yet.
|
||||
const uint64_t UP_RECENT = 8;
|
||||
// A context no upscale used for this long goes (trim): FSR 3.1's after a switch to FSR 4, and a second scene's (the
|
||||
// pause screen's) a while after it closed. Unused that long, no command buffer names it.
|
||||
// pause screen's) a while after it closed.
|
||||
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);
|
||||
// The contexts let go of, destroyed once the GPU is past their last upscale (vk_ffx_dead_take). Only the thread that
|
||||
// makes the upscales (vk_dlaa.c's pass hooks) touches it; vk_ffx_release at teardown.
|
||||
UpdList g_dead;
|
||||
const int DEAD_CTX = 3; // its one kind
|
||||
|
||||
void dead_release(const UpdEntry *e, void *) {
|
||||
uint64_t t0 = perf_mark_begin(); // NX2NAVI_TRACE=perf: "upscaler releases"
|
||||
A.destroy(&u->ctx, nullptr);
|
||||
ffxContext c = (ffxContext)e->handle;
|
||||
A.destroy(&c, nullptr);
|
||||
perf_mark_end(PERF_M_UPREL, t0, 0);
|
||||
}
|
||||
|
||||
void retire_up(Up *u, const char *why) {
|
||||
rt_log("ffx: the FSR 3.1 context for %ux%u (render sizes up to %ux%u) let go of (destroyed once the GPU is past its "
|
||||
"last upscale): %s", u->ow, u->oh, u->mw, u->mh, why);
|
||||
if (!upd_put(&g_dead, DEAD_CTX, u->ctx, nullptr, u->fence, 0)) {
|
||||
// full, which the GPU's progress does not let happen: room after a wait for the device, or else all of them go
|
||||
// as before this list, the device idle
|
||||
vk_upscaler_settle();
|
||||
if (!upd_put(&g_dead, DEAD_CTX, u->ctx, nullptr, u->fence, 0)) {
|
||||
upd_take_all(&g_dead, dead_release, nullptr);
|
||||
upd_put(&g_dead, DEAD_CTX, u->ctx, nullptr, u->fence, 0);
|
||||
}
|
||||
}
|
||||
*u = Up();
|
||||
}
|
||||
|
||||
void trim(uint64_t now_ns) {
|
||||
for (Up &u : U)
|
||||
if (u.ctx && now_ns - u.used_ns > UP_IDLE_NS) destroy_up(&u, "no upscale used it for 90 s");
|
||||
if (u.ctx && now_ns - u.used_ns > UP_IDLE_NS) retire_up(&u, "no upscale used it for 90 s");
|
||||
}
|
||||
|
||||
FfxApiResource image(const VkFfxImage *i, uint32_t state, uint32_t usage) {
|
||||
@@ -194,12 +217,11 @@ Up *context_for(uint32_t w, uint32_t h, uint32_t ow, uint32_t oh) {
|
||||
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_upscaler_device_idle(); // their work may be in flight
|
||||
destroy_up(&u, "the render size changed");
|
||||
}
|
||||
int gone = 0;
|
||||
for (Up &u : U) // the contexts of this output size for another render size, once they are not recent
|
||||
if (u.ctx && &u != found && u.ow == ow && u.oh == oh && up_tick - u.used >= UP_RECENT)
|
||||
retire_up(&u, "the render size changed"), gone++; // their work may be in flight
|
||||
if (gone) vk_upscaler_let_go();
|
||||
if (found) {
|
||||
found->used = up_tick, found->used_ns = now;
|
||||
return found;
|
||||
@@ -213,8 +235,8 @@ 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_upscaler_device_idle(); // its work may still be in flight
|
||||
destroy_up(lru, "its slot went to another output size");
|
||||
retire_up(lru, "its slot went to another output size"); // its work may still be in flight
|
||||
vk_upscaler_let_go();
|
||||
slot = lru;
|
||||
}
|
||||
if (!slot) return nullptr;
|
||||
@@ -315,10 +337,20 @@ extern "C" int vk_ffx_fsr4(void) { return cfg_bool("NX2NAVI_FSR4") && vk_ffx4_av
|
||||
extern "C" int vk_ffx_trim_idle(uint64_t recent) {
|
||||
int n = 0;
|
||||
for (Up &u : U)
|
||||
if (u.ctx && up_tick - u.used >= recent) destroy_up(&u, "room for an FSR 4 context"), n++;
|
||||
if (u.ctx && up_tick - u.used >= recent) retire_up(&u, "room for an FSR 4 context"), n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
extern "C" int vk_ffx_dead_take(uint64_t done) {
|
||||
int n = g_dead.n ? upd_take(&g_dead, done, UINT64_MAX, dead_release, nullptr) : 0;
|
||||
return n + vk_ffx4_dead_take(done);
|
||||
}
|
||||
|
||||
extern "C" uint64_t vk_ffx_dead_fence(void) {
|
||||
uint64_t a = upd_max_fence(&g_dead), b = vk_ffx4_dead_fence();
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
extern "C" int vk_ffx_upscale(VkCommandBuffer cb, const VkFfxImage *color, const VkFfxImage *depth, const VkFfxImage *mv,
|
||||
const VkFfxImage *out, float jx, float jy, int reset, float dt_ms, const VkFfxCamera *cam) {
|
||||
if (!vk_ffx_load()) return 0;
|
||||
@@ -370,6 +402,7 @@ extern "C" int vk_ffx_upscale(VkCommandBuffer cb, const VkFfxImage *color, const
|
||||
d.cameraFovAngleVertical = cam && cam->fov_y > 0 ? cam->fov_y : 1.0f;
|
||||
d.viewSpaceToMetersFactor = cam && cam->meters > 0 ? cam->meters : 1.0f;
|
||||
ffxReturnCode_t r = A.dispatch(&u->ctx, &d.header);
|
||||
u->fence = vk_exec_recording_fence(); // recorded into cb (a refusal too, for all this side knows)
|
||||
if (r != FFX_API_RETURN_OK) {
|
||||
static int told;
|
||||
if (told++ < 8) rt_log("ffx: FSR 3.1 dispatch failed (%u)", (unsigned)r);
|
||||
@@ -440,25 +473,28 @@ extern "C" ffxReturnCode_t vk_ffx_dispatch(ffxContext *ctx, const ffxDispatchDes
|
||||
return A.ok ? A.dispatch(ctx, desc) : FFX_API_RETURN_ERROR;
|
||||
}
|
||||
|
||||
// The upscaler is off (vk_dlaa.c retire_idle; the device is idle): FSR 3.1's and FSR 4's upscale contexts go, made again
|
||||
// at the next upscale. Frame generation's contexts are not these.
|
||||
// The upscaler is off (vk_dlaa.c retire_idle): FSR 3.1's and FSR 4's upscale contexts are let go of, made again at the
|
||||
// next upscale; the caller's vk_upscaler_let_go frees them once the GPU is past them. Frame generation's contexts are
|
||||
// not these.
|
||||
extern "C" int vk_ffx_holds(void) {
|
||||
for (Up &u : U)
|
||||
if (u.ctx) return 1;
|
||||
return vk_ffx4_holds();
|
||||
}
|
||||
extern "C" void vk_ffx_release_idle(void) {
|
||||
vk_ffx4_release();
|
||||
vk_ffx4_release_idle();
|
||||
if (!A.ok) return;
|
||||
for (Up &u : U)
|
||||
if (u.ctx) destroy_up(&u, "the upscaler is off");
|
||||
if (u.ctx) retire_up(&u, "the upscaler is off");
|
||||
}
|
||||
|
||||
// Teardown and the probe: the device is idle.
|
||||
extern "C" void vk_ffx_release(void) {
|
||||
vk_ffx4_release();
|
||||
if (!A.ok) return;
|
||||
for (Up &u : U)
|
||||
if (u.ctx) A.destroy(&u.ctx, nullptr), u.ctx = nullptr;
|
||||
upd_take_all(&g_dead, dead_release, nullptr);
|
||||
}
|
||||
|
||||
// The memory report's FSR figures (vk_memory.c outside_log): the video memory of the upscaling contexts that exist,
|
||||
|
||||
@@ -44,13 +44,18 @@ int vk_ffx_framegen_available(void);
|
||||
// frame's camera. 0 on failure (the caller falls back as it does when DLSS fails).
|
||||
int vk_ffx_upscale(VkCommandBuffer cb, const VkFfxImage *color, const VkFfxImage *depth, const VkFfxImage *mv,
|
||||
const VkFfxImage *out, float jx, float jy, int reset, float dt_ms, const VkFfxCamera *cam);
|
||||
void vk_ffx_release(void); // before the device goes away
|
||||
// The upscale contexts of FSR 3.1 and FSR 4 (vk_dlaa.c retire_idle, the device idle): any held, and give them back.
|
||||
void vk_ffx_release(void); // before the device goes away (the device idle)
|
||||
// The upscale contexts of FSR 3.1 and FSR 4 (vk_dlaa.c retire_idle): any held, and let them all go (the caller's
|
||||
// vk_upscaler_let_go frees them once the GPU is past them).
|
||||
int vk_ffx_holds(void);
|
||||
void vk_ffx_release_idle(void);
|
||||
// FSR 3.1's contexts that no upscale used in the last `recent` calls, destroyed with the device idle (the caller waited
|
||||
// for it): room for an FSR 4 context whose creation failed. Their count.
|
||||
// FSR 3.1's contexts that no upscale used in the last `recent` calls, let go of: room for an FSR 4 context whose
|
||||
// creation failed, whose caller makes their memory free (vk_ffx_dead_take after vk_upscaler_gpu_wait). Their count.
|
||||
int vk_ffx_trim_idle(uint64_t recent);
|
||||
// The FSR 3.1 and FSR 4 contexts let go of: those whose last upscale's submission is at most done are destroyed (their
|
||||
// count); the highest such submission among those left (0: none).
|
||||
int vk_ffx_dead_take(uint64_t done);
|
||||
uint64_t vk_ffx_dead_fence(void);
|
||||
// The device's functions as the library is given them (ffxCreateBackendVKDesc.vkDeviceProcAddr): its pipeline creates
|
||||
// go through the runtime's pipeline caches (vk_pipeline.c vk_pipeline_create_foreign; NX2NAVI_FFX_PIPE_CACHE=0: not).
|
||||
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL vk_ffx_proc_addr(VkDevice dev, const char *name);
|
||||
@@ -66,6 +71,9 @@ int vk_ffx4_upscale(VkCommandBuffer cb, const VkFfxImage *color, const VkFfxImag
|
||||
const VkFfxCamera *cam); // as vk_ffx_upscale
|
||||
void vk_ffx4_release(void);
|
||||
int vk_ffx4_holds(void); // an FSR 4 upscale context is made
|
||||
void vk_ffx4_release_idle(void); // every FSR 4 upscale context let go of (vk_ffx_release_idle)
|
||||
int vk_ffx4_dead_take(uint64_t done); // FSR 4's part of vk_ffx_dead_take
|
||||
uint64_t vk_ffx4_dead_fence(void); // FSR 4's part of vk_ffx_dead_fence
|
||||
int vk_ffx4_failed(void); // it failed three times in a row: FSR 3.1 serves until vk_ffx4_retry
|
||||
void vk_ffx4_retry(void); // the menu chose FSR 4 again: try it anew after such failures
|
||||
void vk_ffx4_trim(uint64_t now_ns); // at each upscale: FSR 4's contexts unused for a while go
|
||||
|
||||
@@ -33,6 +33,9 @@ extern "C" int vk_ffx4_failed(void) { return 0; }
|
||||
extern "C" void vk_ffx4_retry(void) {}
|
||||
extern "C" void vk_ffx4_trim(uint64_t) {}
|
||||
extern "C" uint64_t vk_ffx4_vram(int *contexts) { return *contexts = 0, 0; }
|
||||
extern "C" void vk_ffx4_release_idle(void) {}
|
||||
extern "C" int vk_ffx4_dead_take(uint64_t) { return 0; }
|
||||
extern "C" uint64_t vk_ffx4_dead_fence(void) { return 0; }
|
||||
#else
|
||||
#include <windows.h>
|
||||
#include <atomic>
|
||||
@@ -45,12 +48,14 @@ extern "C" uint64_t vk_ffx4_vram(int *contexts) { return *contexts = 0, 0; }
|
||||
|
||||
#include "third_party/ffx_sdk/upscalers/include/ffx_upscale.h"
|
||||
#include "vk_ffx4_api.h"
|
||||
#include "vk_up_defer.h"
|
||||
|
||||
extern "C" {
|
||||
#include "cfg.h"
|
||||
#include "host.h"
|
||||
#include "platform/platform.h"
|
||||
#include "vk.h"
|
||||
#include "vk_exec.h"
|
||||
#include "vk_ffx.h"
|
||||
}
|
||||
|
||||
@@ -187,29 +192,57 @@ int probe(void) {
|
||||
// beside FSR 3.1's and three creations that failed (the card out of memory) turned FSR 4 off for the run; one context
|
||||
// for every render size, as next, preceded an access violation in AMD's driver inside the dispatch after a switch from
|
||||
// 67 % to 100 %. The descriptors a context was made from stay with it, as the API asks ("must remain live until
|
||||
// ffxDestroyContext").
|
||||
// ffxDestroyContext"): in a Made of its own on the heap, so that a context let go of (retire_up) keeps them until it is
|
||||
// destroyed, once the GPU is past its last upscale (g_dead), while its slot already holds the next one.
|
||||
struct Made {
|
||||
ffxCreateContextDescUpscale desc;
|
||||
ffxCreateBackendVKDesc backend;
|
||||
ffxOverrideVersion version;
|
||||
ffxCreateContextDescUpscaleVersion built;
|
||||
};
|
||||
struct Up {
|
||||
ffxContext ctx = nullptr;
|
||||
uint32_t mw = 0, mh = 0, ow = 0, oh = 0; // the render size it was made for (the largest it takes with drs); the output size
|
||||
bool drs = false; // made for dynamic resolution (FFX_UPSCALE_ENABLE_DYNAMIC_RESOLUTION)
|
||||
uint64_t used = 0, used_ns = 0; // up_tick and the time when context_for last returned it
|
||||
ffxCreateContextDescUpscale desc = {};
|
||||
ffxCreateBackendVKDesc backend = {};
|
||||
ffxOverrideVersion version = {};
|
||||
ffxCreateContextDescUpscaleVersion built = {};
|
||||
Made *made = nullptr; // the descriptors it was made from (calloc), freed after its destroy
|
||||
uint64_t vram = 0; // its video memory as the library reported it at its creation
|
||||
uint64_t fence = 0; // the submission of its last upscale (vk_exec_recording_fence), 0 none
|
||||
} U[4];
|
||||
uint64_t up_tick;
|
||||
const uint64_t UP_RECENT = 8; // contexts used this recently may sit in a command buffer not submitted yet
|
||||
// Contexts used this recently stay (as vk_ffx.cpp's UP_RECENT): the policy only, as one that goes waits for the GPU
|
||||
// through its fence.
|
||||
const uint64_t UP_RECENT = 8;
|
||||
// A context no upscale used for this long goes (vk_ffx4_trim): FSR 4's after a switch to FSR 3.1, and a second scene's
|
||||
// (the pause screen's) a while after it closed. Unused that long, no command buffer names it.
|
||||
// (the pause screen's) a while after it closed.
|
||||
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);
|
||||
// The contexts let go of, destroyed once the GPU is past their last upscale (vk_ffx4_dead_take): handle the context,
|
||||
// aux its Made. Only the thread that makes the upscales (vk_dlaa.c's pass hooks) touches it; vk_ffx4_release at
|
||||
// teardown.
|
||||
UpdList g_dead;
|
||||
const int DEAD_CTX4 = 4; // its one kind
|
||||
|
||||
void dead_release(const UpdEntry *e, void *) {
|
||||
uint64_t t0 = perf_mark_begin(); // NX2NAVI_TRACE=perf: "upscaler releases"
|
||||
A.destroy(&u->ctx, nullptr);
|
||||
ffxContext c = (ffxContext)e->handle;
|
||||
A.destroy(&c, nullptr);
|
||||
free(e->aux);
|
||||
perf_mark_end(PERF_M_UPREL, t0, 0);
|
||||
}
|
||||
|
||||
void retire_up(Up *u, const char *why) {
|
||||
rt_log("ffx4: the %ux%u context (render sizes up to %ux%u) let go of (destroyed once the GPU is past its last "
|
||||
"upscale): %s", u->ow, u->oh, u->mw, u->mh, why);
|
||||
if (!upd_put(&g_dead, DEAD_CTX4, u->ctx, u->made, u->fence, 0)) {
|
||||
// full, which the GPU's progress does not let happen: room after a wait for the device, or else all of them go
|
||||
// as before this list, the device idle
|
||||
vk_upscaler_settle();
|
||||
if (!upd_put(&g_dead, DEAD_CTX4, u->ctx, u->made, u->fence, 0)) {
|
||||
upd_take_all(&g_dead, dead_release, nullptr);
|
||||
upd_put(&g_dead, DEAD_CTX4, u->ctx, u->made, u->fence, 0);
|
||||
}
|
||||
}
|
||||
*u = Up();
|
||||
}
|
||||
|
||||
@@ -229,32 +262,39 @@ FfxApiResource image(const VkFfxImage *i, uint32_t state, uint32_t usage) {
|
||||
Up *create(Up *slot, uint32_t mw, uint32_t mh, uint32_t ow, uint32_t oh, bool drs) {
|
||||
const VkCtx *c = vk_ctx();
|
||||
*slot = Up();
|
||||
slot->built.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION;
|
||||
slot->built.version = FFX_UPSCALER_VERSION;
|
||||
slot->version.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
|
||||
slot->version.header.pNext = &slot->built.header;
|
||||
slot->version.versionId = g_version;
|
||||
slot->backend.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK;
|
||||
slot->backend.header.pNext = &slot->version.header;
|
||||
slot->backend.deviceContext = {c->device, c->phys, vkGetDeviceProcAddr};
|
||||
slot->desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE;
|
||||
slot->desc.header.pNext = &slot->backend.header;
|
||||
slot->desc.flags =
|
||||
Made *m = (Made *)calloc(1, sizeof *m); // zeroed, as the descriptors' {} were
|
||||
if (!m) {
|
||||
rt_log("ffx4: no memory for the descriptors of a %s context", g_name);
|
||||
return nullptr;
|
||||
}
|
||||
m->built.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION;
|
||||
m->built.version = FFX_UPSCALER_VERSION;
|
||||
m->version.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
|
||||
m->version.header.pNext = &m->built.header;
|
||||
m->version.versionId = g_version;
|
||||
m->backend.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK;
|
||||
m->backend.header.pNext = &m->version.header;
|
||||
m->backend.deviceContext = {c->device, c->phys, vkGetDeviceProcAddr};
|
||||
m->desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE;
|
||||
m->desc.header.pNext = &m->backend.header;
|
||||
m->desc.flags =
|
||||
FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE | FFX_UPSCALE_ENABLE_AUTO_EXPOSURE; // a finite far plane, as FSR 3.1's
|
||||
// NX2NAVI_FFX_DEBUG=1 (test): the library checks the values of each dispatch and says what it finds (message)
|
||||
if (rt_knob("FFX_DEBUG", "0")) slot->desc.flags |= FFX_UPSCALE_ENABLE_DEBUG_CHECKING;
|
||||
if (drs) slot->desc.flags |= FFX_UPSCALE_ENABLE_DYNAMIC_RESOLUTION;
|
||||
slot->desc.maxRenderSize = {mw, mh};
|
||||
slot->desc.maxUpscaleSize = {ow, oh};
|
||||
slot->desc.fpMessage = message;
|
||||
if (rt_knob("FFX_DEBUG", "0")) m->desc.flags |= FFX_UPSCALE_ENABLE_DEBUG_CHECKING;
|
||||
if (drs) m->desc.flags |= FFX_UPSCALE_ENABLE_DYNAMIC_RESOLUTION;
|
||||
m->desc.maxRenderSize = {mw, mh};
|
||||
m->desc.maxUpscaleSize = {ow, oh};
|
||||
m->desc.fpMessage = message;
|
||||
uint64_t t0 = plat_now_ns();
|
||||
ffxReturnCode_t r = A.create(&slot->ctx, &slot->desc.header, nullptr);
|
||||
ffxReturnCode_t r = A.create(&slot->ctx, &m->desc.header, nullptr);
|
||||
if (r != FFX_API_RETURN_OK || !slot->ctx) {
|
||||
rt_log("ffx4: %s context creation for %ux%u (render sizes up to %ux%u) failed (%u)", g_name, ow, oh, mw, mh,
|
||||
(unsigned)r);
|
||||
free(m);
|
||||
*slot = Up();
|
||||
return nullptr;
|
||||
}
|
||||
slot->made = m;
|
||||
slot->mw = mw, slot->mh = mh, slot->ow = ow, slot->oh = oh, slot->drs = drs;
|
||||
ffxQueryGetProviderVersion v = {};
|
||||
v.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION;
|
||||
@@ -271,7 +311,8 @@ Up *create(Up *slot, uint32_t mw, uint32_t mh, uint32_t ow, uint32_t oh, bool dr
|
||||
// The provider is asked for the version it runs: anything but a 4.x here means the loader gave another upscaler
|
||||
if (v.versionName && atoi(v.versionName) < 4) {
|
||||
rt_log("ffx4: the context runs FSR %s, not FSR 4: FSR 4 off", v.versionName);
|
||||
A.destroy(&slot->ctx, nullptr);
|
||||
A.destroy(&slot->ctx, nullptr); // never used by the GPU
|
||||
free(m);
|
||||
*slot = Up();
|
||||
return nullptr;
|
||||
}
|
||||
@@ -279,9 +320,10 @@ Up *create(Up *slot, uint32_t mw, uint32_t mh, uint32_t ow, uint32_t oh, bool dr
|
||||
}
|
||||
|
||||
// The context for a render size and an output size: the one made for them, else a new one, in a free slot or in place
|
||||
// of the least recently used one, with the device idle first; the others of that output size go once no command
|
||||
// buffer can hold them. A creation that fails is tried once more after every context not in use has gone, FSR 3.1's
|
||||
// idle ones too (vk_ffx_trim_idle): what it lacked was most often video memory.
|
||||
// of the least recently used one; the others of that output size go once they are not recent. What goes is let go of
|
||||
// and destroyed once the GPU is past it (retire_up). A creation that fails is tried once more after every context not
|
||||
// in use has gone, FSR 3.1's idle ones too (vk_ffx_trim_idle), and the GPU has finished with them and with those let go
|
||||
// of before: what it lacked was most often video memory.
|
||||
Up *context_for(uint32_t w, uint32_t h, uint32_t ow, uint32_t oh) {
|
||||
up_tick++;
|
||||
uint64_t now = plat_now_ns();
|
||||
@@ -290,12 +332,11 @@ 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;
|
||||
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_upscaler_device_idle(); // their work may be in flight
|
||||
destroy_up(&u, "the render size changed");
|
||||
}
|
||||
int gone = 0;
|
||||
for (Up &u : U) // the contexts of this output size for another render size, once they are not recent
|
||||
if (u.ctx && &u != found && u.ow == ow && u.oh == oh && up_tick - u.used >= UP_RECENT)
|
||||
retire_up(&u, "the render size changed"), gone++; // their work may be in flight
|
||||
if (gone) vk_upscaler_let_go();
|
||||
if (found) {
|
||||
found->used = up_tick, found->used_ns = now;
|
||||
return found;
|
||||
@@ -309,8 +350,8 @@ 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_upscaler_device_idle(); // its work may still be in flight
|
||||
destroy_up(lru, "its slot went to another output size");
|
||||
retire_up(lru, "its slot went to another output size"); // its work may still be in flight
|
||||
vk_upscaler_let_go();
|
||||
slot = lru;
|
||||
}
|
||||
if (!slot) return nullptr;
|
||||
@@ -319,11 +360,13 @@ 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_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++;
|
||||
if (o.ctx && up_tick - o.used >= UP_RECENT) retire_up(&o, "room for a context whose creation failed"), freed++;
|
||||
freed += vk_ffx_trim_idle(UP_RECENT);
|
||||
if (freed) u = create(slot, mw, mh, ow, oh, drs);
|
||||
// the creation needs their memory now: a wait for the GPU to pass them all, this file's and FSR 3.1's (with
|
||||
// NX2NAVI_UPSCALER_IDLE_WAIT=1 for the device, as before), and they go
|
||||
const int dead = vk_ffx_dead_take(vk_upscaler_gpu_wait(vk_ffx_dead_fence()));
|
||||
if (freed || dead) u = create(slot, mw, mh, ow, oh, drs);
|
||||
}
|
||||
if (u) u->used = up_tick, u->used_ns = now;
|
||||
return u;
|
||||
@@ -366,7 +409,7 @@ extern "C" void vk_ffx4_retry(void) {
|
||||
// Called at every upscale, with FSR 4 or FSR 3.1 (vk_ffx.cpp vk_ffx_upscale), on the thread that records them.
|
||||
extern "C" void vk_ffx4_trim(uint64_t now_ns) {
|
||||
for (Up &u : U)
|
||||
if (u.ctx && now_ns - u.used_ns > UP_IDLE_NS) destroy_up(&u, "no upscale used it for 90 s");
|
||||
if (u.ctx && now_ns - u.used_ns > UP_IDLE_NS) retire_up(&u, "no upscale used it for 90 s");
|
||||
}
|
||||
|
||||
extern "C" int vk_ffx4_upscale(VkCommandBuffer cb, const VkFfxImage *color, const VkFfxImage *depth, const VkFfxImage *mv,
|
||||
@@ -404,6 +447,7 @@ extern "C" int vk_ffx4_upscale(VkCommandBuffer cb, const VkFfxImage *color, cons
|
||||
d.cameraFovAngleVertical = cam && cam->fov_y > 0 ? cam->fov_y : 1.0f;
|
||||
d.viewSpaceToMetersFactor = cam && cam->meters > 0 ? cam->meters : 1.0f;
|
||||
ffxReturnCode_t r = A.dispatch(&u->ctx, &d.header);
|
||||
u->fence = vk_exec_recording_fence(); // recorded into cb (a refusal too, for all this side knows)
|
||||
if (r != FFX_API_RETURN_OK) {
|
||||
static int told;
|
||||
if (told++ < 8) rt_log("ffx4: %s dispatch failed (%u)", g_name, (unsigned)r);
|
||||
@@ -445,12 +489,26 @@ extern "C" int vk_ffx4_holds(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Teardown and the probe: the device is idle.
|
||||
extern "C" void vk_ffx4_release(void) {
|
||||
if (!A.destroy) return;
|
||||
for (Up &u : U)
|
||||
if (u.ctx) A.destroy(&u.ctx, nullptr), u = Up();
|
||||
if (u.ctx) A.destroy(&u.ctx, nullptr), free(u.made), u = Up();
|
||||
upd_take_all(&g_dead, dead_release, nullptr);
|
||||
}
|
||||
|
||||
// The upscaler is off (vk_ffx.cpp vk_ffx_release_idle): every context is let go of.
|
||||
extern "C" void vk_ffx4_release_idle(void) {
|
||||
for (Up &u : U)
|
||||
if (u.ctx) retire_up(&u, "the upscaler is off");
|
||||
}
|
||||
|
||||
extern "C" int vk_ffx4_dead_take(uint64_t done) {
|
||||
return g_dead.n ? upd_take(&g_dead, done, UINT64_MAX, dead_release, nullptr) : 0;
|
||||
}
|
||||
|
||||
extern "C" uint64_t vk_ffx4_dead_fence(void) { return upd_max_fence(&g_dead); }
|
||||
|
||||
// FSR 4's upscaling contexts that exist and their video memory, for the memory report (vk_ffx.cpp vk_ffx_vram).
|
||||
extern "C" uint64_t vk_ffx4_vram(int *contexts) {
|
||||
uint64_t bytes = 0;
|
||||
|
||||
Reference in New Issue
Block a user