Files
NX2navi/runtime/vk_fg4.cpp
chrg6 d9429b0c00 Break the memory report down past VMA, by owner in system memory, and by growth over a session.
Every report adds a line for video memory outside VMA (the driver's usage and budget per heap against VMA's blocks, and counts of pipelines by kind and by last use, stage libraries, compute pipelines, layouts and descriptor pools, with the swapchain, DLSS and FSR sizes), a line that splits the process's commit by owner (the game's heap and maps, device-local memory, VMA's host blocks, the C runtime heap with the shader caches, pipeline tables, texture cache, log queue and flight recorder in it, the driver's pipeline cache data and the rest), and a line naming what grew by more than 64 MiB since the report before and since play began. Every ten minutes of play and at exit a ranking line orders the categories by growth and marks those that only climbed; tests/mem_growth_check.py checks that bookkeeping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 06:46:31 -05:00

447 lines
23 KiB
C++

// Frame generation through AMD's FSR SDK 2.4: amd_fidelityfx_loader_vk.dll (vk_ffx4.cpp loads it) with
// amd_fidelityfx_framegeneration_vk.dll next to the executable. vk_fg.cpp takes this path instead of the FSR 3.1
// library's where the setting asks for it while FSR 4 is the upscaler (vk_fg.cpp use4, NX2NAVI_FSR4_FG), and its
// functions here are called by vk_fg.cpp alone. The
// loader picks the frame generation and the swapchain for the card: the machine-learned FSR Frame Generation 4.x on
// Radeon RX 9000 cards with Windows 11, FSR Frame Generation 3.1.x on every other. The log names both versions.
//
// What happens each frame is what vk_fg.cpp does (the configuration, then the preparation from the frame's depth and
// motion vectors, then the generation from the swapchain's callback at present, the order FSR Frame Generation 4
// requires), in this SDK's layouts and with what it adds: the camera's position and axes in the preparation, which
// FSR Frame Generation 4 needs, and the version each context is made for.
//
// The swapchain's life differs from the FSR 3.1 library's:
// - A new size remakes the swapchain in place: its create function takes the swapchain's own handle, and the handle
// stays. vk_fg4_destroy_swapchain therefore does nothing while the swapchain context lives.
// - Taking it down (vk_fg4_release) waits for its presents, destroys the frame generation context, then the swapchain
// context, and calls its destroy function last, as the SDK asks.
#define VK_FFX_NO_API // vk_ffx.h: without the API declarations of the other set of headers
#ifndef _WIN32
// AMD publishes the SDK's libraries for Windows only: in any other build this path is never available.
extern "C" {
#include "vk.h"
#include "vk_ffx.h"
#include "vk_fg.h"
}
extern "C" int vk_fg4_available(void) { return 0; }
extern "C" int vk_fg4_failed(void) { return 0; }
extern "C" const char *vk_fg4_name(void) { return ""; }
extern "C" int vk_fg4_active(void) { return 0; }
extern "C" int vk_fg4_generating(void) { return 0; }
extern "C" void vk_fg4_queues(VkQueue, uint32_t, VkQueue, uint32_t) {}
extern "C" VkResult vk_fg4_create_swapchain(const VkSwapchainCreateInfoKHR *, VkSwapchainKHR *) {
return VK_ERROR_FEATURE_NOT_PRESENT;
}
extern "C" void vk_fg4_destroy_swapchain(VkSwapchainKHR) {}
extern "C" VkResult vk_fg4_get_images(VkSwapchainKHR, uint32_t *, VkImage *) { return VK_ERROR_FEATURE_NOT_PRESENT; }
extern "C" VkResult vk_fg4_acquire(VkSwapchainKHR, uint64_t, VkSemaphore, uint32_t *) { return VK_ERROR_FEATURE_NOT_PRESENT; }
extern "C" VkResult vk_fg4_present(const VkPresentInfoKHR *) { return VK_ERROR_FEATURE_NOT_PRESENT; }
extern "C" uint64_t vk_fg4_presents(VkSwapchainKHR) { return 0; }
extern "C" PFN_vkSetHdrMetadataEXT vk_fg4_hdr_metadata_fn(void) { return nullptr; }
extern "C" void vk_fg4_frame(VkCommandBuffer, VkSwapchainKHR, VkFormat, VkExtent2D, VkImage, const int *) {}
extern "C" void vk_fg4_release(void) {}
extern "C" uint64_t vk_fg4_vram(void) { return 0; }
#else
#include <windows.h>
#include <mutex>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include "third_party/ffx_sdk/framegeneration/include/ffx_framegeneration.h"
#include "third_party/ffx_sdk/framegeneration/include/vk/ffx_api_framegeneration_vk.h"
#include "vk_ffx4_api.h"
extern "C" {
#include "cfg.h"
#include "host.h"
#include "platform/platform.h"
#include "vk.h"
#include "vk_dlaa.h"
#include "vk_ffx.h"
#include "vk_fg.h"
#include "ui/menu.h"
}
namespace {
struct {
VkQueue present = VK_NULL_HANDLE, acquire = VK_NULL_HANDLE;
uint32_t pfam = 0, afam = 0; // their queue families
ffxContext sc = nullptr; // the swapchain context
ffxContext fg = nullptr; // the frame generation context
VkSwapchainKHR handle = VK_NULL_HANDLE; // the swapchain the context made: remade in place, destroyed after the context
VkExtent2D fg_ext = {0, 0};
VkExtent2D fg_max = {0, 0}; // the context's largest render size
VkFormat fg_fmt = VK_FORMAT_UNDEFINED;
ffxQueryDescSwapchainReplacementFunctionsVK fn = {};
// what the contexts were made from: the API asks that it stays until the context is destroyed
ffxCreateContextDescFrameGenerationSwapChainVK sc_desc = {};
ffxCreateContextDescFrameGenerationSwapChainVersionVK sc_ver = {};
ffxOverrideVersion sc_pin = {};
ffxCreateContextDescFrameGeneration fg_desc = {};
ffxCreateBackendVKDesc fg_be = {};
ffxCreateContextDescFrameGenerationHudless fg_hl = {};
ffxCreateContextDescFrameGenerationVersion fg_ver = {};
ffxOverrideVersion fg_pin = {};
uint64_t frame = 0;
int failed = 0;
uint64_t generated = 0, paused = 0, no_inputs = 0, hudless = 0, no_hudless = 0;
int on = 0; // the last configured frame had generation on (vk_fg4_generating)
uint64_t vram = 0; // the frame generation context's video memory as the library reported it
} F;
std::mutex g_lock; // the probe: the menu asks from its thread, the present from its own
int g_avail = -1; // -1 not asked yet; 0 this path does not run here; 1 it does
char g_sc_list[96]; // the swapchain versions the loader listed, for the log
char g_fg_name[24]; // the frame generation version a context gets: the one asked for, else the first listed
uint64_t g_fg_pin, g_sc_pin; // NX2NAVI_FG4_VERSION, NX2NAVI_FG4_SWAPCHAIN (tests): the version asked for by its id; 0 the loader's choice
VkResult submit_game(uint32_t n, const VkSubmitInfo *si, VkFence fence) {
vk_queue_lock(); // the game's queue is shared with the recorder and the present path
VkResult r = vkQueueSubmit(vk_ctx()->gfx_queue, n, si, fence);
vk_queue_unlock();
return r;
}
ffxReturnCode_t generate(ffxDispatchDescFrameGeneration *d, void *user) {
F.generated++;
return vk_ffx4_api_dispatch((ffxContext *)user, &d->header);
}
FfxApiResource image(const VkFfxImage *i) {
VkImageCreateInfo ci = {};
ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
ci.imageType = VK_IMAGE_TYPE_2D;
ci.format = i->format;
ci.extent = {i->width, i->height, 1};
ci.mipLevels = 1;
ci.arrayLayers = 1;
return ffxApiGetResourceVK((void *)i->image, ffxApiGetImageResourceDescriptionVK(i->image, ci, 0),
FFX_API_RESOURCE_STATE_UNORDERED_ACCESS);
}
// After this the FSR 3.1 library generates the frames: vk_fg.cpp's vk_fg_wanted stops wanting this swapchain, the
// present code takes it down as it does for a switch in the menu, and the next one is made by the other path.
void fail(const char *what, unsigned r) {
rt_log("fg4: %s failed (%u): the FSR SDK's frame generation is off for this run, FSR 3.1's takes its place", what, r);
F.failed = 1;
}
// The list the loader gives for one kind of context, as text, and the id of the version a test asks for by the start
// of its name (0: none asked, or not listed).
int list_versions(uint64_t type, const char *want, char *out, size_t size, uint64_t *pin) {
uint64_t ids[8] = {};
const char *names[8] = {};
int n = vk_ffx4_api_versions(type, ids, names, 8);
size_t len = 0;
out[0] = 0;
for (int i = 0; i < n; i++) {
const char *nm = names[i] ? names[i] : "?";
len += (size_t)snprintf(out + len, size - len, "%s%s", i ? ", " : "", nm);
if (len >= size) len = size - 1;
if (want && *want && !*pin && !strncmp(nm, want, strlen(want))) *pin = ids[i];
}
return n;
}
// The version a context runs, as its provider names it ("3.1.6"); "?" when it does not say.
const char *provider(ffxContext *ctx) {
ffxQueryGetProviderVersion v = {};
v.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION;
vk_ffx4_api_query(ctx, &v.header);
return v.versionName ? v.versionName : "?";
}
// An HDR10 swapchain (10-bit PQ or 16-bit float): frame generation is told its inputs are HDR.
bool hdr_format(VkFormat f) {
return f == VK_FORMAT_A2B10G10R10_UNORM_PACK32 || f == VK_FORMAT_A2R10G10B10_UNORM_PACK32 ||
f == VK_FORMAT_R16G16B16A16_SFLOAT;
}
// The swapchain's own presents are done before it is remade or taken down.
void wait_presents(void) {
ffxDispatchDescFrameGenerationSwapChainWaitForPresentsVK w = {};
w.header.type = FFX_API_DISPATCH_DESC_TYPE_FGSWAPCHAIN_WAIT_FOR_PRESENTS_VK;
vk_ffx4_api_dispatch(&F.sc, &w.header);
}
} // namespace
// The loader and its frame generation library are beside the executable and list a frame generation and a swapchain
// for the device. Asked once, with the device; 0 before it exists.
extern "C" int vk_fg4_available(void) {
int a = __atomic_load_n(&g_avail, __ATOMIC_ACQUIRE); // asked several times a frame
if (a >= 0) return a;
std::lock_guard<std::mutex> hold(g_lock);
if (g_avail >= 0) return g_avail;
const VkCtx *c = vk_ctx();
if (!c || !c->device) return 0;
if (!vk_ffx4_api_file("amd_fidelityfx_framegeneration_vk.dll")) { // not shipped beside this build
__atomic_store_n(&g_avail, 0, __ATOMIC_RELEASE);
return 0;
}
char fg[96];
char *sc = g_sc_list;
int nf = list_versions(FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION, cfg_get("NX2NAVI_FG4_VERSION"), fg, sizeof fg, &g_fg_pin);
int ns = list_versions(FFX_API_CREATE_CONTEXT_DESC_TYPE_FGSWAPCHAIN_VK, cfg_get("NX2NAVI_FG4_SWAPCHAIN"), sc, sizeof g_sc_list,
&g_sc_pin);
a = nf > 0 && ns > 0;
// the menu's name for it: the first of the list, which the loader takes when none is asked for (a test's version
// changes the context, not this name); a version the driver brings carries a star
snprintf(g_fg_name, sizeof g_fg_name, "%.*s", (int)strcspn(fg, ",*"), fg);
for (size_t k = strlen(g_fg_name); k && g_fg_name[k - 1] == ' '; k--) g_fg_name[k - 1] = 0;
__atomic_store_n(&g_avail, a, __ATOMIC_RELEASE);
if (a) rt_log("fg4: the FSR SDK lists for this device: frame generation %s; its swapchain %s", fg, sc);
else rt_log("fg4: the FSR SDK lists no frame generation for this device: FSR 3.1's library generates the frames");
return a;
}
extern "C" int vk_fg4_failed(void) { return F.failed; }
extern "C" const char *vk_fg4_name(void) { return g_fg_name; }
extern "C" void vk_fg4_queues(VkQueue present, uint32_t present_family, VkQueue acquire, uint32_t acquire_family) {
F.present = present, F.acquire = acquire, F.pfam = present_family, F.afam = acquire_family;
}
// The SDK's swapchain is in place: the window's swapchain handle is its object, and every swapchain call has to go
// through its functions, after a failure too, until the present code has taken it down.
extern "C" int vk_fg4_active(void) { return F.sc != nullptr; }
extern "C" int vk_fg4_generating(void) { return vk_fg4_active() && !F.failed && __atomic_load_n(&F.on, __ATOMIC_RELAXED); }
extern "C" VkResult vk_fg4_create_swapchain(const VkSwapchainCreateInfoKHR *ci, VkSwapchainKHR *sc) {
const VkCtx *c = vk_ctx();
if (F.sc) { // a new size: the swapchain is remade in place and keeps its handle
wait_presents();
VkSwapchainCreateInfoKHR in = *ci;
in.oldSwapchain = VK_NULL_HANDLE;
VkSwapchainKHR h = F.handle;
VkResult r = F.fn.pOutCreateSwapchainFFXAPI(c->device, &in, nullptr, &h, (void *)F.handle);
if (r == VK_SUCCESS) *sc = F.handle = h;
return r;
}
if (F.failed || !F.present || !vk_fg4_available()) return VK_ERROR_FEATURE_NOT_PRESENT;
F.handle = VK_NULL_HANDLE;
F.sc_desc = {};
F.sc_desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FGSWAPCHAIN_VK;
F.sc_desc.physicalDevice = c->phys;
F.sc_desc.device = c->device;
F.sc_desc.swapchain = &F.handle;
F.sc_desc.createInfo = *ci;
F.sc_desc.createInfo.oldSwapchain = VK_NULL_HANDLE;
F.sc_desc.gameQueue = {c->gfx_queue, c->gfx_family, submit_game};
F.sc_desc.asyncComputeQueue = {VK_NULL_HANDLE, c->gfx_family, nullptr};
F.sc_desc.presentQueue = {F.present, F.pfam, nullptr};
F.sc_desc.imageAcquireQueue = {F.acquire, F.afam, nullptr};
// the version this code was built against, which the SDK asks for with every context, and a test's version
F.sc_ver = {};
F.sc_ver.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATIONSWAPCHAIN_VERSION_VK;
F.sc_ver.version = FFX_FRAMEGENERATION_SWAPCHAIN_VK_VERSION;
F.sc_desc.header.pNext = &F.sc_ver.header;
if (g_sc_pin) {
F.sc_pin = {};
F.sc_pin.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
F.sc_pin.versionId = g_sc_pin;
F.sc_ver.header.pNext = &F.sc_pin.header;
}
ffxReturnCode_t r = vk_ffx4_api_create(&F.sc, &F.sc_desc.header);
if (r != FFX_API_RETURN_OK || !F.sc) {
F.sc = nullptr, F.handle = VK_NULL_HANDLE;
fail("the frame generation swapchain", r);
return VK_ERROR_INITIALIZATION_FAILED;
}
F.fn = {};
F.fn.header.type = FFX_API_QUERY_DESC_TYPE_FGSWAPCHAIN_FUNCTIONS_VK;
// The swapchain the driver brings (listed with a star, the loader's choice where it is listed) hands out no destroy
// function: its context's destruction is the swapchain's too (vk_fg4_release).
if (vk_ffx4_api_query(&F.sc, &F.fn.header) != FFX_API_RETURN_OK || !F.fn.pOutQueuePresentKHR || !F.fn.pOutCreateSwapchainFFXAPI ||
!F.fn.pOutGetSwapchainImagesKHR || !F.fn.pOutAcquireNextImageKHR || !F.handle) {
VkSwapchainKHR h = F.handle;
PFN_vkDestroySwapchainFFXAPI destroy = F.fn.pOutDestroySwapchainFFXAPI;
vk_ffx4_api_destroy(&F.sc), F.sc = nullptr, F.handle = VK_NULL_HANDLE;
if (h && destroy) destroy(c->device, h, nullptr, nullptr); // nothing was handed out yet
fail("the swapchain functions", 0);
return VK_ERROR_INITIALIZATION_FAILED;
}
*sc = F.handle;
rt_log("fg4: AMD FSR frame generation swapchain, %ux%u (%s%s)", ci->imageExtent.width, ci->imageExtent.height,
g_sc_pin ? "the version a test asked for, of " : "the loader's choice of ", g_sc_list);
return VK_SUCCESS;
}
// Nothing while the swapchain context lives: a new size remakes the swapchain in place (vk_fg4_create_swapchain), and
// vk_fg4_release destroys it after its context.
extern "C" void vk_fg4_destroy_swapchain(VkSwapchainKHR sc) { (void)sc; }
extern "C" VkResult vk_fg4_get_images(VkSwapchainKHR sc, uint32_t *n, VkImage *img) {
return F.fn.pOutGetSwapchainImagesKHR(vk_ctx()->device, sc, n, img);
}
extern "C" VkResult vk_fg4_acquire(VkSwapchainKHR sc, uint64_t timeout, VkSemaphore sem, uint32_t *idx) {
return F.fn.pOutAcquireNextImageKHR(vk_ctx()->device, sc, timeout, sem, VK_NULL_HANDLE, idx);
}
extern "C" VkResult vk_fg4_present(const VkPresentInfoKHR *pi) { return F.fn.pOutQueuePresentKHR(vk_ctx()->gfx_queue, pi); }
extern "C" uint64_t vk_fg4_presents(VkSwapchainKHR sc) {
return vk_fg4_active() && F.fn.pOutGetLastPresentCountFFXAPI && sc ? F.fn.pOutGetLastPresentCountFFXAPI(sc) : 0;
}
extern "C" PFN_vkSetHdrMetadataEXT vk_fg4_hdr_metadata_fn(void) { return vk_fg4_active() ? F.fn.pOutSetHdrMetadataEXT : nullptr; }
extern "C" void vk_fg4_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt, VkExtent2D ext, VkImage hudless, const int pic[4]) {
if (!vk_fg4_active() || F.failed) return;
vk_dlaa_hudless_wanted = 1; // the upscaling pass keeps a copy of the picture before the HUD (vk_dlaa.c hudless)
const VkCtx *c = vk_ctx();
VkFfxImage depth, mv;
float jx = 0, jy = 0;
int reset = 0;
int have = vk_dlaa_fg_inputs(&depth, &mv, &jx, &jy, &reset);
// The context's largest render size covers the depth and motion vectors it is given: the picture's size, which
// the scene never exceeds (vk_fg.cpp vk_fg_frame has the history of this rule).
unsigned pw, ph;
wide_picture(&pw, &ph);
VkExtent2D max_render = {ext.width > pw ? ext.width : pw, ext.height > ph ? ext.height : ph};
if (F.fg && (F.fg_ext.width != ext.width || F.fg_ext.height != ext.height || F.fg_fmt != fmt)) {
vk_queue_lock(), vkDeviceWaitIdle(c->device), vk_queue_unlock(); // a new window size: the context is made for its display size
vk_ffx4_api_destroy(&F.fg);
F.fg = nullptr;
}
if (!F.fg) {
F.fg_be = {};
F.fg_be.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK;
F.fg_be.deviceContext = {c->device, c->phys, vkGetDeviceProcAddr};
// the HUD-less picture has the swapchain's format (gfx_present.c hudless_record)
F.fg_hl = {};
F.fg_hl.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION_HUDLESS;
F.fg_hl.header.pNext = &F.fg_be.header;
F.fg_hl.hudlessBackBufferFormat = ffxApiGetSurfaceFormatVK(fmt);
F.fg_ver = {};
F.fg_ver.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION_VERSION;
F.fg_ver.header.pNext = &F.fg_hl.header;
F.fg_ver.version = FFX_FRAMEGENERATION_VERSION;
if (g_fg_pin) {
F.fg_pin = {};
F.fg_pin.header.type = FFX_API_DESC_TYPE_OVERRIDE_VERSION;
F.fg_pin.versionId = g_fg_pin;
F.fg_be.header.pNext = &F.fg_pin.header;
}
F.fg_desc = {};
F.fg_desc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION;
F.fg_desc.header.pNext = &F.fg_ver.header;
F.fg_desc.flags = FFX_FRAMEGENERATION_ENABLE_DEPTH_INFINITE | (hdr_format(fmt) ? FFX_FRAMEGENERATION_ENABLE_HIGH_DYNAMIC_RANGE : 0);
F.fg_desc.displaySize = {ext.width, ext.height};
F.fg_desc.maxRenderSize = {max_render.width, max_render.height};
F.fg_desc.backBufferFormat = ffxApiGetSurfaceFormatVK(fmt);
uint64_t t0 = plat_now_ns();
ffxReturnCode_t r = vk_ffx4_api_create(&F.fg, &F.fg_desc.header);
if (r != FFX_API_RETURN_OK || !F.fg) return F.fg = nullptr, fail("the frame generation context", r);
F.fg_ext = ext, F.fg_fmt = fmt, F.fg_max = max_render;
FfxApiEffectMemoryUsage mem = {};
ffxQueryDescFrameGenerationGetGPUMemoryUsage mq = {};
mq.header.type = FFX_API_QUERY_DESC_TYPE_FRAMEGENERATION_GPU_MEMORY_USAGE;
mq.gpuMemoryUsageFrameGeneration = &mem;
vk_ffx4_api_query(&F.fg, &mq.header);
F.vram = mem.totalUsageInBytes;
rt_log("fg4: AMD FSR frame generation %s for %ux%u%s, render size up to %ux%u, made in %.0f ms, %.0f MiB of video memory",
provider(&F.fg), ext.width, ext.height, hdr_format(fmt) ? " (HDR)" : "", max_render.width, max_render.height,
(double)(plat_now_ns() - t0) / 1e6, (double)mem.totalUsageInBytes / 1048576.0);
}
if (have && (depth.width > F.fg_max.width || depth.height > F.fg_max.height)) {
static int told;
if (!told++)
rt_log("fg4: a %ux%u scene is larger than the context's %ux%u: those frames are not generated", depth.width,
depth.height, F.fg_max.width, F.fg_max.height);
have = 0;
}
// Only with the picture without the HUD: generated from the presented picture alone, the HUD would be warped along
// with the scene behind it and trail when the camera turns. A frame without that picture is shown as it is.
int on = have && hudless && !menu_is_open();
__atomic_store_n(&F.on, on, __ATOMIC_RELAXED);
if (!have) F.no_inputs++;
else if (!hudless) F.no_hudless++;
else if (!on) F.paused++;
F.frame++;
ffxConfigureDescFrameGeneration cfg = {};
cfg.header.type = FFX_API_CONFIGURE_DESC_TYPE_FRAMEGENERATION;
cfg.swapChain = (void *)sc;
cfg.frameGenerationCallback = generate;
cfg.frameGenerationCallbackUserContext = &F.fg;
cfg.frameGenerationEnabled = on != 0;
cfg.allowAsyncWorkloads = false;
// the picture's rectangle: with black bars beside or below it (a window of another shape) a generation over the
// whole swapchain warped the picture's edges into the bars
cfg.generationRect = pic && pic[2] > 0 && pic[3] > 0 ? FfxApiRect2D{pic[0], pic[1], pic[2], pic[3]}
: FfxApiRect2D{0, 0, (int32_t)ext.width, (int32_t)ext.height};
if (on) {
VkFfxImage h = {hudless, fmt, ext.width, ext.height, 1};
cfg.HUDLessColor = image(&h);
F.hudless++;
}
cfg.frameID = F.frame;
ffxReturnCode_t r = vk_ffx4_api_configure(&F.fg, &cfg.header);
if (r != FFX_API_RETURN_OK) return fail("frame generation configuration", r);
if (!on) return;
ffxDispatchDescFrameGenerationPrepareV2 p = {};
p.header.type = FFX_API_DISPATCH_DESC_TYPE_FRAMEGENERATION_PREPARE_V2;
p.frameID = F.frame;
p.commandList = cb;
p.renderSize = {depth.width, depth.height};
p.jitterOffset = {jx, jy};
p.motionVectorScale = {1.0f, 1.0f}; // render pixels, current to previous (vk_dlaa.c mv_pass), as FSR takes them
static uint64_t last_ns;
uint64_t now = plat_now_ns();
double dt = last_ns ? (now - last_ns) / 1e6 : 16.67;
last_ns = now;
p.frameTimeDelta = dt > 0 && dt < 100 ? (float)dt : 16.67f;
p.reset = reset != 0;
p.cameraNear = 0.1f; // Ocarina's projection: near 0.1, far at infinity (vk_ffx.cpp)
p.cameraFar = 1e8f;
p.cameraFovAngleVertical = 1.0f;
// The game's world is taken to be in metres (its near plane is at 0.1). With 0.01 here, the value vk_fg.cpp gives
// the FSR 3.1 library, frame generation 3.1.6 drew grey translucent shapes into the sky of its frames while the
// camera turned, and with 0 it smeared them; with 1 its frames were clean in the same camera sweep.
p.viewSpaceToMetersFactor = 1.0f;
p.depth = image(&depth);
p.motionVectors = image(&mv);
// the camera in the world: the game's own, or one at the origin looking down -z on a frame without it
VkDlaaCamera cam;
if (vk_dlaa_fg_camera(&cam)) {
memcpy(p.cameraPosition, cam.pos, sizeof p.cameraPosition), memcpy(p.cameraUp, cam.up, sizeof p.cameraUp);
memcpy(p.cameraRight, cam.right, sizeof p.cameraRight), memcpy(p.cameraForward, cam.fwd, sizeof p.cameraForward);
if (cam.fov_y > 0) p.cameraFovAngleVertical = cam.fov_y;
} else {
p.cameraUp[1] = 1.0f, p.cameraRight[0] = 1.0f, p.cameraForward[2] = -1.0f;
}
r = vk_ffx4_api_dispatch(&F.fg, &p.header);
if (r != FFX_API_RETURN_OK) return fail("frame generation preparation", r);
if (F.frame == 2 || F.frame % 3600 == 0)
rt_log("fg4: %llu frames presented, %llu generated (%llu with the HUD kept out); paused %llu (menu), %llu without depth "
"and motion, %llu without the HUD-less picture", (unsigned long long)F.frame, (unsigned long long)F.generated,
(unsigned long long)F.hudless, (unsigned long long)F.paused, (unsigned long long)F.no_inputs,
(unsigned long long)F.no_hudless);
}
extern "C" void vk_fg4_release(void) {
__atomic_store_n(&F.on, 0, __ATOMIC_RELAXED);
if (!F.sc && !F.fg) return;
if (F.sc) wait_presents();
if (F.fg) vk_ffx4_api_destroy(&F.fg), F.fg = nullptr;
if (F.sc) {
VkSwapchainKHR h = F.handle;
vk_ffx4_api_destroy(&F.sc), F.sc = nullptr, F.handle = VK_NULL_HANDLE;
if (h && F.fn.pOutDestroySwapchainFFXAPI) F.fn.pOutDestroySwapchainFFXAPI(vk_ctx()->device, h, nullptr, nullptr);
}
F.fn = {};
}
// The frame generation context's video memory, for the memory report (vk_fg.cpp vk_fg_vram).
extern "C" uint64_t vk_fg4_vram(void) {
return __atomic_load_n(&F.fg, __ATOMIC_RELAXED) ? __atomic_load_n(&F.vram, __ATOMIC_RELAXED) : 0;
}
#endif // _WIN32