mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
With Frame generation set to AMD FSR 3.1 (after a restart), AMD's frame generation swapchain takes the place of the present path's own: the device creates two more graphics queues for its presents and image acquires, the present path acquires, gets images and presents through its replacement functions, and every frame configures generation and records its preparation from the depth and camera motion vectors the DLSS or FSR 3.1 upscaling pass made (one of them must be on). Generation pauses while the menu is open. On an RTX 5090 at a 60 fps game, about one frame was generated for every frame drawn in play. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
186 lines
7.6 KiB
C++
186 lines
7.6 KiB
C++
// AMD FidelityFX through its API library (amd_fidelityfx_vk.dll next to the executable, loaded at run time): FSR 3.1
|
|
// temporal upscaling here, in the place of DLSS in vk_dlaa.c, and frame generation (vk_ffx_fg_*, below). The API's
|
|
// headers are in third_party/ffx_api (MIT); they are C++ in places, hence this file. Nothing of it runs unless the
|
|
// upscaler or frame generation asks for FSR 3.1.
|
|
//
|
|
// FSR 3.1 takes the same inputs vk_dlaa.c makes for DLSS: the scene's light target (linear HDR), its D32F depth
|
|
// (OpenGL-style, an infinite far plane, not inverted) and the camera motion vectors in render pixels from the current
|
|
// frame to the previous one, the convention both share, so the vectors go in unscaled.
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
#include <math.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <wchar.h>
|
|
|
|
#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"
|
|
|
|
extern "C" {
|
|
#include "cfg.h"
|
|
#include "host.h"
|
|
#include "vk.h"
|
|
#include "vk_ffx.h"
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct Api {
|
|
void *lib = nullptr;
|
|
PfnFfxCreateContext create = nullptr;
|
|
PfnFfxDestroyContext destroy = nullptr;
|
|
PfnFfxConfigure configure = nullptr;
|
|
PfnFfxQuery query = nullptr;
|
|
PfnFfxDispatch dispatch = nullptr;
|
|
int tried = 0, ok = 0;
|
|
} A;
|
|
|
|
void message(uint32_t type, const wchar_t *msg) {
|
|
char s[512];
|
|
size_t n = 0;
|
|
for (; msg && msg[n] && n < sizeof s - 1; n++) s[n] = msg[n] < 128 ? (char)msg[n] : '?';
|
|
s[n] = 0;
|
|
static int told;
|
|
if (told++ < 40) rt_log("ffx: %s %s", type == FFX_API_MESSAGE_TYPE_ERROR ? "error:" : "warning:", s);
|
|
}
|
|
|
|
// One upscaling context per render and output size (the menu switches the mode live, so a few may exist); never
|
|
// destroyed before shutdown, so none is released while the GPU may still run its work.
|
|
struct Up {
|
|
ffxContext ctx = nullptr;
|
|
uint32_t w = 0, h = 0, ow = 0, oh = 0;
|
|
} U[6];
|
|
|
|
FfxApiResource image(const VkFfxImage *i, uint32_t state, uint32_t usage) {
|
|
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;
|
|
ci.usage = i->storage ? VK_IMAGE_USAGE_STORAGE_BIT : 0;
|
|
return ffxApiGetResourceVK((void *)i->image, ffxApiGetImageResourceDescriptionVK(i->image, ci, usage), state);
|
|
}
|
|
|
|
Up *context_for(uint32_t w, uint32_t h, uint32_t ow, uint32_t oh) {
|
|
for (Up &u : U)
|
|
if (u.ctx && u.w == w && u.h == h && u.ow == ow && u.oh == oh) return &u;
|
|
Up *slot = nullptr;
|
|
for (Up &u : U)
|
|
if (!u.ctx) { slot = &u; break; }
|
|
if (!slot) return nullptr;
|
|
const VkCtx *c = vk_ctx();
|
|
ffxCreateBackendVKDesc be = {};
|
|
be.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK;
|
|
be.vkDevice = c->device;
|
|
be.vkPhysicalDevice = c->phys;
|
|
be.vkDeviceProcAddr = vkGetDeviceProcAddr;
|
|
ffxCreateContextDescUpscale d = {};
|
|
d.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE;
|
|
d.header.pNext = &be.header;
|
|
d.flags = FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE | FFX_UPSCALE_ENABLE_DEPTH_INFINITE | FFX_UPSCALE_ENABLE_AUTO_EXPOSURE;
|
|
d.maxRenderSize = {w, h};
|
|
d.maxUpscaleSize = {ow, oh};
|
|
d.fpMessage = message;
|
|
ffxReturnCode_t r = A.create(&slot->ctx, &d.header, nullptr);
|
|
if (r != FFX_API_RETURN_OK) {
|
|
rt_log("ffx: FSR 3.1 context creation at %ux%u -> %ux%u failed (%u)", w, h, ow, oh, (unsigned)r);
|
|
slot->ctx = nullptr;
|
|
return nullptr;
|
|
}
|
|
slot->w = w, slot->h = h, slot->ow = ow, slot->oh = oh;
|
|
ffxQueryGetProviderVersion v = {};
|
|
v.header.type = FFX_API_QUERY_DESC_TYPE_GET_PROVIDER_VERSION;
|
|
A.query(&slot->ctx, &v.header);
|
|
rt_log("ffx: FSR 3.1 upscaling at %ux%u -> %ux%u (%s)", w, h, ow, oh, v.versionName ? v.versionName : "provider unknown");
|
|
return slot;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" int vk_ffx_load(void) {
|
|
if (A.tried) return A.ok;
|
|
A.tried = 1;
|
|
#ifdef _WIN32
|
|
char path[MAX_PATH];
|
|
DWORD n = GetModuleFileNameA(NULL, path, MAX_PATH);
|
|
while (n && path[n - 1] != '\\') n--;
|
|
snprintf(path + n, sizeof path - n, "amd_fidelityfx_vk.dll");
|
|
HMODULE m = LoadLibraryA(path);
|
|
if (!m) {
|
|
rt_log("ffx: %s not found: FSR 3.1 off", path);
|
|
return 0;
|
|
}
|
|
A.lib = m;
|
|
A.create = (PfnFfxCreateContext)(void *)GetProcAddress(m, "ffxCreateContext");
|
|
A.destroy = (PfnFfxDestroyContext)(void *)GetProcAddress(m, "ffxDestroyContext");
|
|
A.configure = (PfnFfxConfigure)(void *)GetProcAddress(m, "ffxConfigure");
|
|
A.query = (PfnFfxQuery)(void *)GetProcAddress(m, "ffxQuery");
|
|
A.dispatch = (PfnFfxDispatch)(void *)GetProcAddress(m, "ffxDispatch");
|
|
#endif
|
|
A.ok = A.create && A.destroy && A.configure && A.query && A.dispatch;
|
|
rt_log("ffx: amd_fidelityfx_vk.dll %s", A.ok ? "loaded" : "lacks the API entry points: FSR 3.1 off");
|
|
return A.ok;
|
|
}
|
|
|
|
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, float fov_y) {
|
|
if (!vk_ffx_load()) return 0;
|
|
Up *u = context_for(color->width, color->height, out->width, out->height);
|
|
if (!u) return 0;
|
|
ffxDispatchDescUpscale d = {};
|
|
d.header.type = FFX_API_DISPATCH_DESC_TYPE_UPSCALE;
|
|
d.commandList = cb;
|
|
// Every image is in GENERAL, which the backend reads as its unordered-access state, and is left there
|
|
d.color = image(color, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS, 0);
|
|
d.depth = image(depth, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS, 0);
|
|
d.motionVectors = image(mv, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS, 0);
|
|
d.output = image(out, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS, FFX_API_RESOURCE_USAGE_UAV);
|
|
d.jitterOffset = {jx, jy};
|
|
d.motionVectorScale = {1.0f, 1.0f};
|
|
d.renderSize = {color->width, color->height};
|
|
d.upscaleSize = {out->width, out->height};
|
|
// The sharpness slider (0-100) drives FSR's own RCAS here, as it does for FSR 1
|
|
int sharp = cfg_int("NX2NAVI_SHARPNESS");
|
|
d.enableSharpening = sharp > 0;
|
|
d.sharpness = sharp > 100 ? 1.0f : sharp / 100.0f;
|
|
d.frameTimeDelta = dt_ms;
|
|
d.preExposure = 1.0f;
|
|
d.reset = reset != 0;
|
|
d.cameraNear = 0.1f; // Ocarina's projection (vk_dlaa.c ocam_vp): near 0.1, far at infinity
|
|
d.cameraFar = 1e8f;
|
|
d.cameraFovAngleVertical = fov_y > 0 ? fov_y : 1.0f;
|
|
d.viewSpaceToMetersFactor = 0.01f; // the game's units are centimetres
|
|
ffxReturnCode_t r = A.dispatch(&u->ctx, &d.header);
|
|
if (r != FFX_API_RETURN_OK) {
|
|
static int told;
|
|
if (told++ < 8) rt_log("ffx: FSR 3.1 dispatch failed (%u)", (unsigned)r);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
extern "C" ffxReturnCode_t vk_ffx_create(ffxContext *ctx, ffxCreateContextDescHeader *desc) {
|
|
return vk_ffx_load() ? A.create(ctx, desc, nullptr) : FFX_API_RETURN_ERROR;
|
|
}
|
|
extern "C" ffxReturnCode_t vk_ffx_destroy(ffxContext *ctx) { return A.ok ? A.destroy(ctx, nullptr) : FFX_API_RETURN_ERROR; }
|
|
extern "C" ffxReturnCode_t vk_ffx_configure(ffxContext *ctx, const ffxConfigureDescHeader *desc) {
|
|
return A.ok ? A.configure(ctx, desc) : FFX_API_RETURN_ERROR;
|
|
}
|
|
extern "C" ffxReturnCode_t vk_ffx_query(ffxContext *ctx, ffxQueryDescHeader *desc) {
|
|
return A.ok ? A.query(ctx, desc) : FFX_API_RETURN_ERROR;
|
|
}
|
|
extern "C" ffxReturnCode_t vk_ffx_dispatch(ffxContext *ctx, const ffxDispatchDescHeader *desc) {
|
|
return A.ok ? A.dispatch(ctx, desc) : FFX_API_RETURN_ERROR;
|
|
}
|
|
|
|
extern "C" void vk_ffx_release(void) {
|
|
if (!A.ok) return;
|
|
for (Up &u : U)
|
|
if (u.ctx) A.destroy(&u.ctx, nullptr), u.ctx = nullptr;
|
|
}
|