mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Keep the HUD out of frame generation, show the frame generation rate, and start DLSS Frame Generation.
Frame generation now gets the picture without the HUD each frame: the upscaler's output before the HUD is drawn over it, encoded to sRGB as the game's last pass encodes its picture and put through the same colour, scaling and borders as the presented frame, so it tells the HUD apart and leaves it unwarped in the generated frames (NX2NAVI_FG_HUDLESS_SHOW=1 shows that picture, a test). The performance overlay gains an FG row and the fps line the display's rate, generated frames included. The device enables VK_KHR_get_memory_requirements2 whenever AMD's frame generation is on, which crashed at start with DLSS upscaling. DLSS Frame Generation through NVIDIA Streamline (vk_dlssg.c, and nx2navi_dlssg.dll from runtime/dlssg, built with MSVC by tools/build_dlssg.py against the SDK kept outside the repository) creates the instance and device through Streamline's proxies and calls its swapchain hooks; Streamline still fails in its plugin start-up here, so the menu lists it only while it is set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,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_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", "LOG_CS_BINDS", "LOG_TEX_FLAGS", "LOG_MESH", "MESH", "POOL", "NATIVE_TENSOR", "LOG_RCOND", "LOG_RT_SIZES", "DUMP_WHITE", "DUMP_AFTER", "SUBMIT_FINISH", "ACQUIRE_WAIT", "CS_FALLBACK", "EXEFS", "FEEDBACK", "FIX_C0", "FIX_ZERO", "FLIGHT", "FLIGHT_AT", "FLIGHT_DIR", "FLIGHT_FRAMES",
|
||||
"FLIGHT_SPIKE_MS", "FLIGHT_THUMBS", "FLUSH_SPAN", "FOV", "FOV_PATCH", "FPS60", "FPS_LIMIT", "FRAMEGEN", "FREEZE_IDLE", "FREE_CORES",
|
||||
"FLIGHT_SPIKE_MS", "FLIGHT_THUMBS", "FLUSH_SPAN", "FOV", "FOV_PATCH", "FG_HUDLESS_SHOW", "FPS60", "FPS_LIMIT", "FRAMEGEN", "FREEZE_IDLE", "FREE_CORES",
|
||||
"FULLSCREEN", "FXAA", "GAME_ASPECT", "GAME_RES", "GPA", "GPA_EMPTY", "GPL", "GPL_DRAW_BUILD", "GPU", "GPU_INDEX", "GPU_NAME", "HDR", "HDR_GAME_NITS", "HDR_HUE",
|
||||
"HDR_PEAK_NITS", "HDR_UI_NITS", "HIGHFPS_FIX", "INPUT", "INPUT_RECORD", "INPUT_SCRIPT", "LANG", "LIB_PREBUILD",
|
||||
"LIB_PREBUILD_KEEP", "LIB_PREBUILD_THREADS", "LOCALE", "LOD_GUARD", "LSTICK_DEADZONE", "MEMORY", "MENU_LANG", "MENU_SOUND_VOLUME", "MENU_TEST",
|
||||
@@ -119,7 +119,7 @@ static const CfgMeta metas[] = {
|
||||
{"UPSCALER", "fsr", "fsr|bilinear", LIVE}, {"SHARPNESS", "80", "0..100", LIVE},
|
||||
{"DLAA", "0", "0|1", RESTART}, {"DLSS", "dlaa", "dlaa|quality|balanced|performance|ultra_performance", LIVE},
|
||||
{"DLSS_PRESET", "default", "default|k|j", LIVE}, {"TAA", "dlss", "dlss|fsr3", RESTART}, // vk_dlaa.c fsr3
|
||||
{"FRAMEGEN", "off", "off|fsr3", RESTART}, // vk_fg.cpp
|
||||
{"FRAMEGEN", "off", "off|fsr3|dlss", RESTART}, // vk_fg.cpp, vk_dlssg.c
|
||||
{"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", "1440|1800|2160|2880|window", RESTART}, // widescreen.c: the picture's height
|
||||
|
||||
218
runtime/dlssg/nx2_sl.cpp
Normal file
218
runtime/dlssg/nx2_sl.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
// nx2navi_dlssg.dll: DLSS Frame Generation through NVIDIA Streamline, behind the C interface of nx2_sl.h. Built with
|
||||
// MSVC against the Streamline SDK's headers (tools/build_dlssg.py; the SDK stays outside the repository), so its C++
|
||||
// types have the ABI of Streamline's own DLLs. sl.interposer.dll and its plugins are loaded from the game's folder at
|
||||
// run time; this DLL links none of them.
|
||||
//
|
||||
// Manual hooking (the SDK's ProgrammingGuideManualHooking): the game creates its instance and device through the
|
||||
// interposer's vkCreateInstance and vkCreateDevice, which add what DLSS-G needs, and calls the interposer's
|
||||
// swapchain functions; everything else goes to the Vulkan loader as before. Each frame gets a frame token, the PCL
|
||||
// latency markers, the camera constants and the depth and motion vector tags (valid until the present), then the
|
||||
// present goes through the interposer, which inserts the generated frame.
|
||||
#define NX2SL_BUILD
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sl.h>
|
||||
#include <sl_consts.h>
|
||||
#include <sl_dlss_g.h>
|
||||
#include <sl_pcl.h>
|
||||
#include <sl_reflex.h>
|
||||
|
||||
#include "nx2_sl.h"
|
||||
|
||||
namespace {
|
||||
|
||||
HMODULE g_mod;
|
||||
Nx2SlLog g_log;
|
||||
PFun_slInit *p_init;
|
||||
PFun_slShutdown *p_shutdown;
|
||||
PFun_slIsFeatureLoaded *p_loaded;
|
||||
PFun_slSetTagForFrame *p_tag;
|
||||
PFun_slSetConstants *p_constants;
|
||||
PFun_slGetNewFrameToken *p_token;
|
||||
PFun_slGetFeatureFunction *p_feature;
|
||||
PFun_slDLSSGSetOptions *p_dlssg_options;
|
||||
PFun_slDLSSGGetState *p_dlssg_state;
|
||||
PFun_slPCLSetMarker *p_marker;
|
||||
PFun_slReflexSetOptions *p_reflex;
|
||||
PFN_vkGetInstanceProcAddr p_gipa;
|
||||
PFN_vkGetDeviceProcAddr p_gdpa;
|
||||
sl::FrameToken *g_frame;
|
||||
int g_available = -1, g_mode = -1;
|
||||
uint64_t g_presented;
|
||||
uint32_t g_generated;
|
||||
|
||||
void say(const char *fmt, ...) {
|
||||
if (!g_log) return;
|
||||
char s[512];
|
||||
va_list a;
|
||||
va_start(a, fmt);
|
||||
vsnprintf(s, sizeof s, fmt, a);
|
||||
va_end(a);
|
||||
g_log(s);
|
||||
}
|
||||
|
||||
void sl_log(sl::LogType type, const char *msg) {
|
||||
if (type == sl::LogType::eInfo) return; // only warnings and errors reach the game's log
|
||||
static int n;
|
||||
if (n++ < 200) say("dlssg: streamline %s: %s", type == sl::LogType::eError ? "error" : "warning", msg);
|
||||
}
|
||||
|
||||
template <class T> T *proc(const char *name) { return reinterpret_cast<T *>(GetProcAddress(g_mod, name)); }
|
||||
|
||||
void matrix(sl::float4x4 &m, const float *v) {
|
||||
for (int r = 0; r < 4; r++) m[r] = sl::float4(v[r * 4], v[r * 4 + 1], v[r * 4 + 2], v[r * 4 + 3]);
|
||||
}
|
||||
|
||||
sl::Resource resource(const Nx2SlImage *i) {
|
||||
sl::Resource r(sl::ResourceType::eTex2d, (void *)i->image, nullptr, (void *)i->view, (uint32_t)i->layout);
|
||||
r.width = i->width;
|
||||
r.height = i->height;
|
||||
r.nativeFormat = (uint32_t)i->format;
|
||||
r.mipLevels = 1;
|
||||
r.arrayLayers = 1;
|
||||
r.usage = (uint32_t)i->usage;
|
||||
return r;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" NX2SL_API int nx2sl_version(void) { return NX2SL_VERSION; }
|
||||
|
||||
extern "C" NX2SL_API int nx2sl_init(const wchar_t *dir, Nx2SlLog log) {
|
||||
g_log = log;
|
||||
wchar_t path[MAX_PATH];
|
||||
swprintf(path, MAX_PATH, L"%ls\\sl.interposer.dll", dir);
|
||||
g_mod = LoadLibraryW(path);
|
||||
if (!g_mod) return say("dlssg: sl.interposer.dll not found next to the game: DLSS Frame Generation off"), 0;
|
||||
p_init = proc<PFun_slInit>("slInit");
|
||||
p_shutdown = proc<PFun_slShutdown>("slShutdown");
|
||||
p_loaded = proc<PFun_slIsFeatureLoaded>("slIsFeatureLoaded");
|
||||
p_tag = proc<PFun_slSetTagForFrame>("slSetTagForFrame");
|
||||
p_constants = proc<PFun_slSetConstants>("slSetConstants");
|
||||
p_token = proc<PFun_slGetNewFrameToken>("slGetNewFrameToken");
|
||||
p_feature = proc<PFun_slGetFeatureFunction>("slGetFeatureFunction");
|
||||
p_gipa = reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(g_mod, "vkGetInstanceProcAddr"));
|
||||
p_gdpa = reinterpret_cast<PFN_vkGetDeviceProcAddr>(GetProcAddress(g_mod, "vkGetDeviceProcAddr"));
|
||||
if (!p_init || !p_tag || !p_constants || !p_token || !p_feature || !p_gipa || !p_gdpa)
|
||||
return say("dlssg: sl.interposer.dll lacks its entry points"), 0;
|
||||
static const sl::Feature features[] = {sl::kFeatureDLSS_G, sl::kFeatureReflex, sl::kFeaturePCL};
|
||||
const wchar_t *paths[] = {dir};
|
||||
sl::Preferences p{};
|
||||
p.showConsole = false;
|
||||
p.logLevel = sl::LogLevel::eDefault;
|
||||
p.pathsToPlugins = paths;
|
||||
p.numPathsToPlugins = 1;
|
||||
p.pathToLogsAndData = dir;
|
||||
p.logMessageCallback = sl_log;
|
||||
// the game tracks its own command buffer state; no plugin downloads at run time
|
||||
p.flags = sl::PreferenceFlags::eDisableCLStateTracking | sl::PreferenceFlags::eUseManualHooking;
|
||||
p.featuresToLoad = features;
|
||||
p.numFeaturesToLoad = 3;
|
||||
p.applicationId = 231313132; // the id NVIDIA's samples use for builds without an issued one (as vk_dlaa.c's NGX)
|
||||
p.engine = sl::EngineType::eCustom;
|
||||
p.renderAPI = sl::RenderAPI::eVulkan;
|
||||
sl::Result r = p_init(p, sl::kSDKVersion);
|
||||
if (r != sl::Result::eOk) return say("dlssg: slInit failed (%d): DLSS Frame Generation off", (int)r), 0;
|
||||
say("dlssg: Streamline up");
|
||||
return 1;
|
||||
}
|
||||
|
||||
extern "C" NX2SL_API PFN_vkGetInstanceProcAddr nx2sl_instance_proc(void) { return p_gipa; }
|
||||
extern "C" NX2SL_API PFN_vkGetDeviceProcAddr nx2sl_device_proc(void) { return p_gdpa; }
|
||||
|
||||
extern "C" NX2SL_API int nx2sl_available(void) {
|
||||
if (g_available >= 0) return g_available;
|
||||
bool loaded = false;
|
||||
g_available = p_loaded && p_loaded(sl::kFeatureDLSS_G, loaded) == sl::Result::eOk && loaded;
|
||||
void *f = nullptr;
|
||||
if (g_available && p_feature(sl::kFeatureDLSS_G, "slDLSSGSetOptions", f) == sl::Result::eOk) p_dlssg_options = (PFun_slDLSSGSetOptions *)f;
|
||||
if (g_available && p_feature(sl::kFeatureDLSS_G, "slDLSSGGetState", f) == sl::Result::eOk) p_dlssg_state = (PFun_slDLSSGGetState *)f;
|
||||
if (p_feature(sl::kFeaturePCL, "slPCLSetMarker", f) == sl::Result::eOk) p_marker = (PFun_slPCLSetMarker *)f;
|
||||
if (p_feature(sl::kFeatureReflex, "slReflexSetOptions", f) == sl::Result::eOk) p_reflex = (PFun_slReflexSetOptions *)f;
|
||||
if (!p_dlssg_options || !p_marker) g_available = 0;
|
||||
if (g_available && p_reflex) { // DLSS-G needs Reflex's low-latency mode
|
||||
sl::ReflexOptions o{};
|
||||
o.mode = sl::ReflexMode::eLowLatency;
|
||||
p_reflex(o);
|
||||
}
|
||||
say("dlssg: DLSS Frame Generation %s", g_available ? "available" : "not available on this GPU or driver");
|
||||
return g_available;
|
||||
}
|
||||
|
||||
extern "C" NX2SL_API int nx2sl_frame(VkCommandBuffer cb, const Nx2SlImage *depth, const Nx2SlImage *mv,
|
||||
const Nx2SlImage *hudless, const Nx2SlCamera *cam, uint32_t out_w, uint32_t out_h,
|
||||
int enable) {
|
||||
if (g_available != 1) return 0;
|
||||
if (p_token(g_frame, nullptr) != sl::Result::eOk || !g_frame) return 0;
|
||||
p_marker(sl::PCLMarker::eSimulationStart, *g_frame);
|
||||
p_marker(sl::PCLMarker::eSimulationEnd, *g_frame);
|
||||
p_marker(sl::PCLMarker::eRenderSubmitStart, *g_frame);
|
||||
sl::ViewportHandle vp(0);
|
||||
if (enable && depth && mv && cam) {
|
||||
sl::Constants c{};
|
||||
matrix(c.cameraViewToClip, cam->view_to_clip);
|
||||
matrix(c.clipToCameraView, cam->clip_to_view);
|
||||
matrix(c.clipToPrevClip, cam->clip_to_prev_clip);
|
||||
matrix(c.prevClipToClip, cam->prev_clip_to_clip);
|
||||
c.jitterOffset = sl::float2(cam->jitter[0], cam->jitter[1]);
|
||||
c.mvecScale = sl::float2(cam->mv_scale[0], cam->mv_scale[1]);
|
||||
c.cameraPinholeOffset = sl::float2(0, 0);
|
||||
c.cameraPos = sl::float3(cam->pos[0], cam->pos[1], cam->pos[2]);
|
||||
c.cameraUp = sl::float3(cam->up[0], cam->up[1], cam->up[2]);
|
||||
c.cameraRight = sl::float3(cam->right[0], cam->right[1], cam->right[2]);
|
||||
c.cameraFwd = sl::float3(cam->fwd[0], cam->fwd[1], cam->fwd[2]);
|
||||
c.cameraNear = cam->znear;
|
||||
c.cameraFar = cam->zfar;
|
||||
c.cameraFOV = cam->fov_y;
|
||||
c.cameraAspectRatio = cam->aspect;
|
||||
c.depthInverted = sl::Boolean::eFalse;
|
||||
c.cameraMotionIncluded = sl::Boolean::eTrue;
|
||||
c.motionVectors3D = sl::Boolean::eFalse;
|
||||
c.reset = cam->reset ? sl::Boolean::eTrue : sl::Boolean::eFalse;
|
||||
p_constants(c, *g_frame, vp);
|
||||
sl::Resource rd = resource(depth), rm = resource(mv), rh = hudless ? resource(hudless) : sl::Resource{};
|
||||
sl::Extent ed{0, 0, depth->width, depth->height}, em{0, 0, mv->width, mv->height};
|
||||
sl::Extent eh{0, 0, hudless ? hudless->width : 0, hudless ? hudless->height : 0};
|
||||
sl::ResourceTag tags[3] = {sl::ResourceTag(&rd, sl::kBufferTypeDepth, sl::ResourceLifecycle::eValidUntilPresent, &ed),
|
||||
sl::ResourceTag(&rm, sl::kBufferTypeMotionVectors, sl::ResourceLifecycle::eValidUntilPresent, &em),
|
||||
sl::ResourceTag(&rh, sl::kBufferTypeHUDLessColor, sl::ResourceLifecycle::eValidUntilPresent, &eh)};
|
||||
p_tag(*g_frame, vp, tags, hudless ? 3 : 2, (sl::CommandBuffer *)cb);
|
||||
}
|
||||
if ((enable != 0) != g_mode) {
|
||||
sl::DLSSGOptions o{};
|
||||
o.mode = enable ? sl::DLSSGMode::eOn : sl::DLSSGMode::eOff;
|
||||
o.numFramesToGenerate = 1;
|
||||
sl::Result r = p_dlssg_options(vp, o);
|
||||
say("dlssg: frame generation %s (%d), output %ux%u", enable ? "on" : "off", (int)r, out_w, out_h);
|
||||
g_mode = enable != 0;
|
||||
}
|
||||
p_marker(sl::PCLMarker::eRenderSubmitEnd, *g_frame);
|
||||
return 1;
|
||||
}
|
||||
|
||||
extern "C" NX2SL_API void nx2sl_present_marker(int after) {
|
||||
if (g_available != 1 || !g_frame) return;
|
||||
p_marker(after ? sl::PCLMarker::ePresentEnd : sl::PCLMarker::ePresentStart, *g_frame);
|
||||
if (after) {
|
||||
g_presented++;
|
||||
if (p_dlssg_state && (g_presented == 600 || g_presented % 3600 == 0)) {
|
||||
sl::DLSSGState s{};
|
||||
if (p_dlssg_state(sl::ViewportHandle(0), s, nullptr) == sl::Result::eOk) {
|
||||
g_generated = s.numFramesActuallyPresented;
|
||||
say("dlssg: %llu frames, %u presented per frame, status %u", (unsigned long long)g_presented,
|
||||
s.numFramesActuallyPresented, (unsigned)s.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" NX2SL_API void nx2sl_stats(uint64_t *presented, uint32_t *generated_per_frame) {
|
||||
*presented = g_presented, *generated_per_frame = g_generated;
|
||||
}
|
||||
|
||||
extern "C" NX2SL_API void nx2sl_shutdown(void) {
|
||||
if (p_shutdown && g_available >= 0) p_shutdown();
|
||||
}
|
||||
56
runtime/dlssg/nx2_sl.h
Normal file
56
runtime/dlssg/nx2_sl.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// The C interface of nx2navi_dlssg.dll (runtime/dlssg/nx2_sl.cpp), which runtime/vk_dlssg.c loads for DLSS Frame
|
||||
// Generation. The DLL is built with MSVC against NVIDIA's Streamline SDK (its C++ types have MSVC's ABI); nothing of
|
||||
// the SDK is in this repository, and the game itself is built without it.
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
#define NX2SL_VERSION 1
|
||||
|
||||
typedef struct {
|
||||
VkImage image;
|
||||
VkImageView view;
|
||||
VkFormat format;
|
||||
uint32_t width, height;
|
||||
VkImageLayout layout; // the layout it is in when Streamline reads it (at the present)
|
||||
VkImageUsageFlags usage;
|
||||
} Nx2SlImage;
|
||||
|
||||
typedef struct {
|
||||
float view_to_clip[16], clip_to_view[16], clip_to_prev_clip[16], prev_clip_to_clip[16]; // row-major
|
||||
float pos[3], up[3], right[3], fwd[3];
|
||||
float znear, zfar, fov_y, aspect;
|
||||
float jitter[2]; // render pixels
|
||||
float mv_scale[2]; // what makes the motion vectors fractions of the render size
|
||||
int reset;
|
||||
} Nx2SlCamera;
|
||||
|
||||
typedef void (*Nx2SlLog)(const char *line);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#ifdef NX2SL_BUILD
|
||||
#define NX2SL_API __declspec(dllexport)
|
||||
#else
|
||||
#define NX2SL_API
|
||||
#endif
|
||||
typedef int (*PFN_nx2sl_version)(void);
|
||||
// slInit with DLSS-G, Reflex and PCL for Vulkan; dir: the folder of sl.interposer.dll and the plugins (the game's)
|
||||
typedef int (*PFN_nx2sl_init)(const wchar_t *dir, Nx2SlLog log);
|
||||
// The interposer's proc address functions: vkCreateInstance and vkCreateDevice through them add what DLSS-G needs
|
||||
// (extensions, features, queues) and tell Streamline the device; the swapchain functions through them are its hooks.
|
||||
typedef PFN_vkGetInstanceProcAddr (*PFN_nx2sl_instance_proc)(void);
|
||||
typedef PFN_vkGetDeviceProcAddr (*PFN_nx2sl_device_proc)(void);
|
||||
// 1 when DLSS-G loaded and supports this GPU (after the device exists)
|
||||
typedef int (*PFN_nx2sl_available)(void);
|
||||
// Each present, before the present call: a new frame, its latency markers up to the render's end, its constants and
|
||||
// tags, and frame generation on or off. Then nx2sl_present_marker(0) before and (1) after the present.
|
||||
typedef int (*PFN_nx2sl_frame)(VkCommandBuffer cb, const Nx2SlImage *depth, const Nx2SlImage *mv, const Nx2SlImage *hudless,
|
||||
const Nx2SlCamera *cam, uint32_t out_w, uint32_t out_h, int enable);
|
||||
typedef void (*PFN_nx2sl_present_marker)(int after);
|
||||
typedef void (*PFN_nx2sl_stats)(uint64_t *presented, uint32_t *generated_per_frame);
|
||||
typedef void (*PFN_nx2sl_shutdown)(void);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "ui/menu.h"
|
||||
#include "vk.h"
|
||||
#include "vk_fg.h"
|
||||
#include "vk_dlaa.h"
|
||||
#include "vk_dlssg.h"
|
||||
|
||||
#define FRAMES 2
|
||||
#define MAX_IMAGES 16
|
||||
@@ -290,7 +292,8 @@ static void flip_locked(void) {
|
||||
.pWaitSemaphores = &g_render_done[g_flip_idx], .swapchainCount = 1, .pSwapchains = &g_sc,
|
||||
.pImageIndices = &g_flip_idx};
|
||||
vk_queue_lock();
|
||||
VkResult r = vk_fg_active() ? vk_fg_present(&pi) : vkQueuePresentKHR(vk_ctx()->gfx_queue, &pi);
|
||||
VkResult r = vk_dlssg_active() ? vk_dlssg_present(vk_ctx()->gfx_queue, &pi)
|
||||
: vk_fg_active() ? vk_fg_present(&pi) : 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);
|
||||
@@ -354,7 +357,8 @@ static int create_swapchain(void) {
|
||||
old = g_sc = VK_NULL_HANDLE;
|
||||
r = vk_fg_create_swapchain(&ci, &g_sc);
|
||||
}
|
||||
if (r != VK_SUCCESS) r = vkCreateSwapchainKHR(c->device, &ci, NULL, &g_sc);
|
||||
if (r != VK_SUCCESS && vk_dlssg_active()) r = vk_dlssg_create_swapchain(c->device, &ci, &g_sc); // Streamline's hook
|
||||
else if (r != VK_SUCCESS) r = vkCreateSwapchainKHR(c->device, &ci, NULL, &g_sc);
|
||||
if (r != VK_SUCCESS) {
|
||||
rt_log("gfx: vkCreateSwapchainKHR failed (%d)", (int)r);
|
||||
g_sc = old;
|
||||
@@ -366,7 +370,8 @@ static int create_swapchain(void) {
|
||||
if (g_sc_view[i]) vkDestroyImageView(c->device, g_sc_view[i], NULL);
|
||||
g_sc_view[i] = VK_NULL_HANDLE;
|
||||
}
|
||||
if (old) vkDestroySwapchainKHR(c->device, old, NULL);
|
||||
if (old && vk_dlssg_active()) vk_dlssg_destroy_swapchain(c->device, old);
|
||||
else if (old) vkDestroySwapchainKHR(c->device, old, NULL);
|
||||
g_sc_fmt = sf.format;
|
||||
g_sc_hdr = hdr;
|
||||
vk_hdr_set_active(hdr);
|
||||
@@ -379,7 +384,8 @@ static int create_swapchain(void) {
|
||||
g_sc_n = n < MAX_IMAGES ? n : MAX_IMAGES;
|
||||
memset(g_sc_img, 0, sizeof g_sc_img);
|
||||
vk_fg_get_images(g_sc, &g_sc_n, g_sc_img);
|
||||
} else vkGetSwapchainImagesKHR(c->device, g_sc, &g_sc_n, g_sc_img);
|
||||
} else if (vk_dlssg_active()) vk_dlssg_get_images(c->device, g_sc, &g_sc_n, g_sc_img);
|
||||
else vkGetSwapchainImagesKHR(c->device, g_sc, &g_sc_n, g_sc_img);
|
||||
VkSemaphoreCreateInfo semi = {.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||||
for (uint32_t i = 0; i < g_sc_n; i++) {
|
||||
vkCreateSemaphore(c->device, &semi, NULL, &g_render_done[i]);
|
||||
@@ -730,6 +736,103 @@ static int movie_overlay(VkCommandBuffer cb, int slot, VkImage dst) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// The game image's crop into dst at `to`: colour adjustment (or the HDR10 encode), then FSR or the bilinear blit; dst in
|
||||
// TRANSFER_DST_OPTIMAL. Returns vk_fsr_record's result (0: the blit scaled it).
|
||||
static int put_picture(VkCommandBuffer cb, unsigned slot, VkImage src, VkFormat fmt, int w, int h, const int rect[4], VkImage dst,
|
||||
const int to[4], int general) {
|
||||
int cw = rect[2], ch = rect[3];
|
||||
// Colour adjustment (NX2NAVI_COLOR_*, vk_color.c), or on an HDR10 swapchain the PQ encode (vk_hdr.c; the adjustment is
|
||||
// for SDR): a copy of the crop takes the game image's place below
|
||||
VkImage graded = !general ? VK_NULL_HANDLE : g_sc_hdr ? vk_hdr_encode(cb, slot, src, fmt, rect)
|
||||
: vk_color_record(cb, slot, src, fmt, rect);
|
||||
const int grect[4] = {0, 0, cw, ch};
|
||||
VkImage psrc = graded ? graded : src;
|
||||
VkFormat pfmt = graded ? VK_FORMAT_R16G16B16A16_SFLOAT : fmt;
|
||||
const int *prect = graded ? grect : rect;
|
||||
// FSR (NX2NAVI_UPSCALER): EASU when the picture's rectangle is larger than the crop, the shrink and RCAS when it is
|
||||
// smaller; else the bilinear blit
|
||||
int fsr = general ? vk_fsr_record(cb, slot, psrc, pfmt, graded ? cw : w, graded ? ch : h, prect, dst, to) : 0;
|
||||
if (!fsr && src) {
|
||||
if (general) // a GENERAL source (vk_texture.c images): earlier submissions' writes become visible to the blit here
|
||||
sync_barrier(cb, psrc, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_WRITE_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
VkImageBlit region = {
|
||||
.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||
.srcOffsets = {{prect[0], prect[1], 0}, {prect[0] + cw, prect[1] + ch, 1}},
|
||||
.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstOffsets = {{to[0], to[1], 0}, {to[0] + to[2], to[1] + to[3], 1}}};
|
||||
vkCmdBlitImage(cb, psrc, general ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, VK_FILTER_LINEAR);
|
||||
if (general) // later submissions that write the source wait for the blit's read
|
||||
sync_barrier(cb, psrc, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_TRANSFER_BIT, 0,
|
||||
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, 0);
|
||||
}
|
||||
return fsr;
|
||||
}
|
||||
|
||||
// The display's frame rate with frame generation: the swapchain's present count (generated frames included, vk_fg.cpp)
|
||||
// over half a second, sampled at each of the game's presents (gfx_output_fps).
|
||||
static volatile double g_out_fps;
|
||||
static void output_rate(void) {
|
||||
static uint64_t t0, n0;
|
||||
uint64_t n = 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;
|
||||
}
|
||||
double gfx_output_fps(void) { return g_out_fps; }
|
||||
|
||||
static VkImage g_hl; // frame generation's HUD-less picture at the swapchain's size (hudless_record)
|
||||
static VmaAllocation g_hl_mem;
|
||||
static VkImageView g_hl_view;
|
||||
static uint32_t g_hl_w, g_hl_h;
|
||||
static VkFormat g_hl_fmt;
|
||||
|
||||
static VkImage hudless_record(VkCommandBuffer cb, unsigned slot, int w, int h, const int rect[4], const int to[4], int general) {
|
||||
VkImage pic;
|
||||
VkImageView pv;
|
||||
uint32_t pw, ph;
|
||||
if (!vk_fg_active() || !vk_dlaa_fg_hudless(&pic, &pv, &pw, &ph) || (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)) {
|
||||
vkDeviceWaitIdle(c->device);
|
||||
vkDestroyImageView(c->device, g_hl_view, NULL);
|
||||
vmaDestroyImage(c->vma, g_hl, g_hl_mem);
|
||||
g_hl = VK_NULL_HANDLE;
|
||||
}
|
||||
if (!g_hl) {
|
||||
VkImageCreateInfo ii = {.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = g_sc_fmt,
|
||||
.extent = {g_sc_ext.width, g_sc_ext.height, 1}, .mipLevels = 1, .arrayLayers = 1,
|
||||
.samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL,
|
||||
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT};
|
||||
VmaAllocationCreateInfo ai = {.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE};
|
||||
if (vmaCreateImage(c->vma, &ii, &ai, &g_hl, &g_hl_mem, NULL) != VK_SUCCESS) return g_hl = VK_NULL_HANDLE;
|
||||
VkImageViewCreateInfo vi = {.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = g_hl,
|
||||
.viewType = VK_IMAGE_VIEW_TYPE_2D, .format = g_sc_fmt,
|
||||
.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
||||
if (vkCreateImageView(c->device, &vi, NULL, &g_hl_view) != VK_SUCCESS) g_hl_view = VK_NULL_HANDLE;
|
||||
g_hl_w = g_sc_ext.width, g_hl_h = g_sc_ext.height, g_hl_fmt = g_sc_fmt;
|
||||
rt_log("gfx: frame generation's HUD-less picture at %ux%u", g_hl_w, g_hl_h);
|
||||
}
|
||||
// the steps' work images were just read for the presented picture: they are written again only after that
|
||||
sync_barrier(cb, g_hl, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT);
|
||||
if (to[2] != (int)g_sc_ext.width || to[3] != (int)g_sc_ext.height) {
|
||||
VkClearColorValue black = {.float32 = {0, 0, 0, 1}};
|
||||
VkImageSubresourceRange all = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
||||
vkCmdClearColorImage(cb, g_hl, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &all);
|
||||
sync_barrier(cb, g_hl, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
|
||||
VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
}
|
||||
put_picture(cb, slot, pic, VK_FORMAT_R8G8B8A8_UNORM, w, h, rect, g_hl, to, general);
|
||||
sync_barrier(cb, g_hl, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_WRITE_BIT, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT);
|
||||
return g_hl;
|
||||
}
|
||||
|
||||
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;
|
||||
gfx_window_start();
|
||||
@@ -747,13 +850,15 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
goto out;
|
||||
}
|
||||
uint32_t idx = 0;
|
||||
VkResult r = 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);
|
||||
VkResult r = 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) {
|
||||
plat_xchg32(&g_dirty, 1);
|
||||
if (!create_swapchain()) goto out;
|
||||
r = 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);
|
||||
r = 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);
|
||||
}
|
||||
vk_device_fault_report(r);
|
||||
if (r != VK_SUCCESS && r != VK_SUBOPTIMAL_KHR) goto out; // lost, not ready: skip the frame
|
||||
@@ -829,31 +934,21 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
|
||||
VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
}
|
||||
// Colour adjustment (NX2NAVI_COLOR_*, vk_color.c), or on an HDR10 swapchain the PQ encode (vk_hdr.c; the adjustment is
|
||||
// for SDR): a copy of the crop takes the game image's place below
|
||||
VkImage graded = !general ? VK_NULL_HANDLE : g_sc_hdr ? vk_hdr_encode(cb, slot, src, fmt, rect)
|
||||
: vk_color_record(cb, slot, src, fmt, rect);
|
||||
const int grect[4] = {0, 0, cw, ch};
|
||||
VkImage psrc = graded ? graded : src;
|
||||
VkFormat pfmt = graded ? VK_FORMAT_R16G16B16A16_SFLOAT : fmt;
|
||||
const int *prect = graded ? grect : rect;
|
||||
// FSR (NX2NAVI_UPSCALER): EASU when the picture's rectangle is larger than the crop, the shrink and RCAS when it is
|
||||
// smaller; else the bilinear blit
|
||||
int fsr = general ? vk_fsr_record(cb, slot, psrc, pfmt, graded ? cw : w, graded ? ch : h, prect, dst, to) : 0;
|
||||
int fsr = put_picture(cb, slot, src, fmt, w, h, rect, dst, to, general);
|
||||
if (src) g_scaler = fsr == 2 ? 3 : fsr ? 2 : cw != to[2] || ch != to[3];
|
||||
if (!fsr && src) {
|
||||
if (general) // a GENERAL source (vk_texture.c images): earlier submissions' writes become visible to the blit here
|
||||
sync_barrier(cb, psrc, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
VK_ACCESS_2_MEMORY_WRITE_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_READ_BIT);
|
||||
VkImageBlit region = {
|
||||
.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||
.srcOffsets = {{prect[0], prect[1], 0}, {prect[0] + cw, prect[1] + ch, 1}},
|
||||
.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstOffsets = {{to[0], to[1], 0}, {to[0] + to[2], to[1] + to[3], 1}}};
|
||||
vkCmdBlitImage(cb, psrc, general ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst,
|
||||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, VK_FILTER_LINEAR);
|
||||
if (general) // later submissions that write the source wait for the blit's read
|
||||
sync_barrier(cb, psrc, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_2_TRANSFER_BIT, 0,
|
||||
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, 0);
|
||||
// Frame generation without the HUD in its generated frames: the picture as it would be without the HUD (vk_dlaa.c,
|
||||
// DLSS's or FSR 3.1's output before the HUD was drawn over it) goes through the same steps into an image of the
|
||||
// swapchain's size, which frame generation compares with the presented one to find the HUD and leave it unwarped
|
||||
VkImage hudless = src && !g_sc_hdr ? hudless_record(cb, slot, w, h, rect, to, general) : VK_NULL_HANDLE;
|
||||
static int show_hl = -1; // NX2NAVI_FG_HUDLESS_SHOW=1 (test): the HUD-less picture on screen instead
|
||||
if (show_hl < 0) show_hl = cfg_get("NX2NAVI_FG_HUDLESS_SHOW") && !strcmp(cfg_get("NX2NAVI_FG_HUDLESS_SHOW"), "1");
|
||||
if (hudless && show_hl) {
|
||||
VkImageCopy cp = {.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||
.extent = {g_sc_ext.width, g_sc_ext.height, 1}};
|
||||
vkCmdCopyImage(cb, hudless, VK_IMAGE_LAYOUT_GENERAL, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &cp);
|
||||
sync_barrier(cb, dst, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT,
|
||||
VK_ACCESS_2_TRANSFER_WRITE_BIT);
|
||||
}
|
||||
if (!g_sc_hdr && src) movie_overlay(cb, slot, dst); // a movie frame over the game image (SDR swapchains)
|
||||
// last: the image's layout, and the stage and access of its last write, as each step leaves it
|
||||
@@ -901,7 +996,9 @@ static void present_image(VkImage src, VkImageLayout layout, VkFormat fmt, int w
|
||||
last_write = 0;
|
||||
record_thumb(cb, dst, slot);
|
||||
}
|
||||
if (src) vk_fg_frame(cb, g_sc, g_sc_fmt, g_sc_ext); // frame generation: this frame's preparation (vk_fg.cpp)
|
||||
if (src) vk_fg_frame(cb, g_sc, g_sc_fmt, g_sc_ext, hudless, g_hl_view); // 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);
|
||||
vk_crumb(cb, VK_CRUMB_PRESENT_END, crumb);
|
||||
vkEndCommandBuffer(cb);
|
||||
@@ -991,6 +1088,7 @@ void gfx_window_release_vk(void) {
|
||||
}
|
||||
if (g_pool) vkDestroyCommandPool(c->device, g_pool, NULL);
|
||||
if (g_sc && vk_fg_active()) vk_fg_destroy_swapchain(g_sc), vk_fg_release();
|
||||
else if (g_sc && vk_dlssg_active()) vk_dlssg_destroy_swapchain(c->device, g_sc);
|
||||
else if (g_sc) vkDestroySwapchainKHR(c->device, g_sc, NULL);
|
||||
if (g_surface) vkDestroySurfaceKHR(c->instance, g_surface, NULL);
|
||||
g_sc = VK_NULL_HANDLE; g_surface = VK_NULL_HANDLE; g_pool = VK_NULL_HANDLE; g_sc_n = 0;
|
||||
|
||||
@@ -759,10 +759,13 @@ static void row_upscaling(void) {
|
||||
|
||||
// Frame generation (vk_fg.cpp), after a restart; it needs a temporal upscaler's motion vectors
|
||||
static void row_framegen(void) {
|
||||
static const char *const values[] = {"off", "fsr3"};
|
||||
const char *names[] = {menu_text(UI_MENU_FRAMEGEN_OFF), "AMD FSR 3.1"};
|
||||
int cur = strcmp(cfg_value("NX2NAVI_FRAMEGEN"), "fsr3") ? 0 : 1;
|
||||
int nv = opt_choice_index("NX2NAVI_FRAMEGEN", UI_MENU_FRAMEGEN, names, 2, cur, 1, UI_MENU_FRAMEGEN_DESC,
|
||||
static const char *const values[] = {"off", "fsr3", "dlss"};
|
||||
const char *names[] = {menu_text(UI_MENU_FRAMEGEN_OFF), "AMD FSR 3.1", "NVIDIA DLSS"};
|
||||
const char *v = cfg_value("NX2NAVI_FRAMEGEN");
|
||||
int cur = !strcmp(v, "fsr3") ? 1 : !strcmp(v, "dlss") ? 2 : 0;
|
||||
// NVIDIA DLSS (vk_dlssg.c) is listed only while it is set: Streamline still fails in its plugin start-up here
|
||||
int n = cur == 2 ? 3 : 2;
|
||||
int nv = opt_choice_index("NX2NAVI_FRAMEGEN", UI_MENU_FRAMEGEN, names, n, cur, 0, UI_MENU_FRAMEGEN_DESC,
|
||||
menu_text(UI_MENU_FRAMEGEN_NOTE));
|
||||
if (nv >= 0) opt_apply("NX2NAVI_FRAMEGEN", values[nv]);
|
||||
}
|
||||
@@ -1166,7 +1169,11 @@ static void perf_tick(uint64_t now, const MenuTarget *t) {
|
||||
g_perf_frames++;
|
||||
if (d > g_perf_max_ns) g_perf_max_ns = d;
|
||||
if (g_perf_win_ns >= 500000000ull) {
|
||||
snprintf(g_perf_text, sizeof g_perf_text, menu_text(UI_PERF_LINE_FPS), g_perf_frames * 1e9 / g_perf_win_ns);
|
||||
double out = gfx_output_fps(); // with frame generation: the display's rate too
|
||||
if (out > 0)
|
||||
snprintf(g_perf_text, sizeof g_perf_text, menu_text(UI_PERF_LINE_FPS_FG), g_perf_frames * 1e9 / g_perf_win_ns, out);
|
||||
else
|
||||
snprintf(g_perf_text, sizeof g_perf_text, menu_text(UI_PERF_LINE_FPS), g_perf_frames * 1e9 / g_perf_win_ns);
|
||||
g_perf_win_ns = g_perf_max_ns = 0;
|
||||
g_perf_frames = 0;
|
||||
}
|
||||
@@ -1323,6 +1330,12 @@ static void perf_full_draw(const MenuTarget *t, float scale) {
|
||||
perf_cell(UI_MENU_OV_FMT_FPS, s.fps);
|
||||
perf_cell(UI_MENU_OV_LOW1, s.low1_fps);
|
||||
perf_cell(UI_MENU_OV_LOW01, s.low01_fps);
|
||||
if (gfx_output_fps() > 0) { // frame generation: what the display gets, generated frames included
|
||||
perf_label(UI_MENU_OV_FG_LABEL, fps_c);
|
||||
perf_cell(UI_MENU_OV_FMT_FPS, (float)gfx_output_fps());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TableNextColumn();
|
||||
}
|
||||
perf_label(UI_MENU_OV_FRAME_LABEL, fps_c);
|
||||
perf_cell(UI_MENU_OV_FRAME_MS, s.frame_ms_avg);
|
||||
perf_cell(UI_MENU_OV_MAX_MS, s.frame_ms_max);
|
||||
|
||||
@@ -74,6 +74,7 @@ unsigned gfx_window_dpi(void); // the window's DPI (96 at 100% d
|
||||
// The present mode the swapchain got, as a NX2NAVI_VK_PRESENT value ("fifo" when mailbox is missing); "" before the first
|
||||
// swapchain. Any thread.
|
||||
const char *gfx_window_present_mode(void);
|
||||
double gfx_output_fps(void); // frames the display gets per second with frame generation (generated ones too); 0 when off
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -463,7 +463,8 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
|
||||
[UI_MENU_OV_FPS] = "%.0f FPS", [UI_MENU_OV_FRAME_MS] = "%.1f ms", [UI_MENU_OV_GAME_PCT] = "game %.0f%%",
|
||||
[UI_MENU_OV_MAX_MS] = "max %.1f ms",
|
||||
[UI_MENU_OV_FPS_LABEL] = "FPS", [UI_MENU_OV_FRAME_LABEL] = "Frame", [UI_MENU_OV_FMT_FPS] = "%.0f",
|
||||
[UI_MENU_OV_FPS_LABEL] = "FPS", [UI_MENU_OV_FG_LABEL] = "FG", [UI_PERF_LINE_FPS_FG] = "%.0f fps (%.0f frame gen)",
|
||||
[UI_MENU_OV_FRAME_LABEL] = "Frame", [UI_MENU_OV_FMT_FPS] = "%.0f",
|
||||
[UI_MENU_OV_LOW1] = "1%% low %.0f", [UI_MENU_OV_LOW01] = "0.1%% low %.0f",
|
||||
[UI_MENU_OV_GPU] = "GPU", [UI_MENU_OV_CPU] = "CPU", [UI_MENU_OV_RAM] = "RAM", [UI_MENU_OV_VRAM] = "VRAM",
|
||||
[UI_MENU_OV_TEMP] = "Temperature", [UI_MENU_OV_POWER] = "Power", [UI_MENU_OV_CLOCK] = "Clock",
|
||||
@@ -517,7 +518,9 @@ static const char *const text_en[UI_TEXT_COUNT] = {
|
||||
[UI_MENU_FRAMEGEN_DESC] = "Shows a generated frame between every two the game draws, for about twice the frame rate "
|
||||
"on a fast display. It needs DLSS or FSR 3.1 under Upscaling (any mode), whose motion "
|
||||
"vectors it uses. AMD FSR 3.1 frame generation runs on any GPU (amd_fidelityfx_vk.dll next "
|
||||
"to the game's .exe). It pauses while this menu is open. Takes effect after a restart.",
|
||||
"to the game's .exe); NVIDIA DLSS frame generation needs an RTX 40 or 50 GPU and "
|
||||
"NVIDIA's Streamline files next to the .exe. It pauses while this menu is open. Takes "
|
||||
"effect after a restart.",
|
||||
[UI_MENU_FRAMEGEN_OFF] = "Off",
|
||||
[UI_MENU_FRAMEGEN_NOTE] = "after restart; needs DLSS or FSR 3.1",
|
||||
[UI_MENU_GAME_RES] = "Resolution",
|
||||
|
||||
@@ -213,7 +213,7 @@ typedef enum {
|
||||
|
||||
// Performance overlay
|
||||
UI_MENU_OV_FPS, UI_MENU_OV_FRAME_MS, UI_MENU_OV_GAME_PCT, UI_MENU_OV_MAX_MS,
|
||||
UI_MENU_OV_FPS_LABEL, UI_MENU_OV_FRAME_LABEL, UI_MENU_OV_FMT_FPS, // the frame rows: their labels, the rate
|
||||
UI_MENU_OV_FPS_LABEL, UI_MENU_OV_FG_LABEL, UI_PERF_LINE_FPS_FG, UI_MENU_OV_FRAME_LABEL, UI_MENU_OV_FMT_FPS, // the frame rows: their labels, the rate
|
||||
UI_MENU_OV_LOW1, UI_MENU_OV_LOW01, // printf: the fps of the slowest 1% / 0.1% of the frames, no unit (the overlay column stays narrow)
|
||||
UI_MENU_OV_GPU, UI_MENU_OV_CPU, UI_MENU_OV_RAM, UI_MENU_OV_VRAM, UI_MENU_OV_TEMP, UI_MENU_OV_POWER,
|
||||
UI_MENU_OV_CLOCK, UI_MENU_OV_NONE,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "platform/platform.h"
|
||||
#include "vk_dlaa.h"
|
||||
#include "vk_fg.h"
|
||||
#include "vk_dlssg.h"
|
||||
|
||||
static uint32_t g_gfx_queues = 1; // queues the graphics family offers (pick_device)
|
||||
|
||||
@@ -69,6 +70,7 @@ static void mesa_cache_beside_exe(void) {
|
||||
}
|
||||
|
||||
static int create_instance(void) {
|
||||
vk_dlssg_early(); // DLSS Frame Generation: Streamline before any Vulkan call of the process (its slInit asks so)
|
||||
mesa_cache_beside_exe();
|
||||
const char *ext[12];
|
||||
uint32_t n_ext = (uint32_t)gfx_window_vk_extensions(ext, 7);
|
||||
@@ -78,7 +80,9 @@ static int create_instance(void) {
|
||||
rt_log("vk: NX2NAVI_VK_VALIDATION set but %s is not installed; continuing without", layers[0]);
|
||||
validate = 0;
|
||||
}
|
||||
if (validate) ext[n_ext++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
|
||||
// DLSS Frame Generation: Streamline's plugins call its naming functions even when they say it is off
|
||||
if (validate || (vk_dlssg_wanted() && instance_ext_present(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)))
|
||||
ext[n_ext++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
|
||||
if (vk_hdr_wanted() && instance_ext_present(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME)) // HDR10 surface formats
|
||||
ext[n_ext++] = VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME;
|
||||
vk_dlaa_instance_exts(ext, &n_ext, 12);
|
||||
@@ -88,7 +92,7 @@ static int create_instance(void) {
|
||||
.enabledExtensionCount = n_ext, .ppEnabledExtensionNames = ext,
|
||||
.enabledLayerCount = validate ? 1 : 0, .ppEnabledLayerNames = layers};
|
||||
rdoc_init();
|
||||
VkResult r = vkCreateInstance(&ci, NULL, &g_vk.instance);
|
||||
VkResult r = vk_dlssg_create_instance(&ci, &g_vk.instance);
|
||||
if (r != VK_SUCCESS) { rt_log("vk: vkCreateInstance failed (%d); is a Vulkan 1.3 driver installed?", (int)r); return 0; }
|
||||
g_vk.validation = validate;
|
||||
if (validate) {
|
||||
@@ -228,7 +232,7 @@ static int create_device(void) {
|
||||
vkEnumerateDeviceExtensionProperties(g_vk.phys, NULL, &ne, NULL);
|
||||
VkExtensionProperties *ep = calloc(ne ? ne : 1, sizeof *ep);
|
||||
vkEnumerateDeviceExtensionProperties(g_vk.phys, NULL, &ne, ep);
|
||||
const char *ext[48];
|
||||
const char *ext[64];
|
||||
uint32_t n_ext = 0;
|
||||
for (size_t i = 0; i < sizeof k_required_ext / sizeof *k_required_ext; i++) ext[n_ext++] = k_required_ext[i];
|
||||
|
||||
@@ -270,8 +274,19 @@ static int create_device(void) {
|
||||
vk_dlaa_device_exts(ep, ne, ext, &n_ext, 40); // NGX's; the eight slots after it stay for the eight below
|
||||
// FSR 3.1 (vk_ffx.cpp): its Vulkan back end looks vkGetBufferMemoryRequirements2KHR up by that name, which resolves
|
||||
// only with the extension enabled (the core function has no suffix); without it the back end called address 0
|
||||
if (vk_dlaa_fsr3() && has_ext(ep, ne, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME))
|
||||
if ((vk_dlaa_fsr3() || vk_fg_wanted() || vk_dlssg_wanted()) && has_ext(ep, ne, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME))
|
||||
ext[n_ext++] = VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME;
|
||||
// DLSS Frame Generation (vk_dlssg.c): Streamline's plugins look functions of extensions core in Vulkan 1.3 up by
|
||||
// their suffixed names too, which resolve only with the extension itself enabled
|
||||
if (vk_dlssg_wanted()) {
|
||||
static const char *const promoted[] = {VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME,
|
||||
VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME,
|
||||
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
|
||||
VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME, VK_KHR_MAINTENANCE_1_EXTENSION_NAME,
|
||||
VK_KHR_MAINTENANCE_2_EXTENSION_NAME, VK_KHR_MAINTENANCE_3_EXTENSION_NAME};
|
||||
for (size_t i = 0; i < sizeof promoted / sizeof *promoted && n_ext < 56; i++)
|
||||
if (has_ext(ep, ne, promoted[i])) ext[n_ext++] = promoted[i];
|
||||
}
|
||||
if (has_ext(ep, ne, VK_EXT_MEMORY_BUDGET_EXTENSION_NAME)) { g_vk.has_memory_budget = 1; ext[n_ext++] = VK_EXT_MEMORY_BUDGET_EXTENSION_NAME; }
|
||||
// GPU timestamps on the CPU clock (vk_sync.c): the perf line splits GPU idle time into waiting for a submission or not.
|
||||
if (has_ext(ep, ne, VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME)) { g_vk.has_calibrated = 2; ext[n_ext++] = VK_KHR_CALIBRATED_TIMESTAMPS_EXTENSION_NAME; }
|
||||
@@ -358,9 +373,10 @@ static int create_device(void) {
|
||||
}
|
||||
VkDeviceCreateInfo di = {.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &c->f2, .queueCreateInfoCount = nqi,
|
||||
.pQueueCreateInfos = qi, .enabledExtensionCount = n_ext, .ppEnabledExtensionNames = ext};
|
||||
VkResult r = vkCreateDevice(g_vk.phys, &di, NULL, &g_vk.device);
|
||||
VkResult r = vk_dlssg_create_device(g_vk.instance, g_vk.phys, &di, &g_vk.device);
|
||||
free(c);
|
||||
if (r != VK_SUCCESS) { rt_log("vk: vkCreateDevice failed (%d)", (int)r); return 0; }
|
||||
vk_dlssg_device_ready(g_vk.device);
|
||||
vkGetDeviceQueue(g_vk.device, g_vk.gfx_family, 0, &g_vk.gfx_queue);
|
||||
if (nq_gfx >= 2) {
|
||||
VkQueue present, acquire;
|
||||
|
||||
@@ -355,10 +355,38 @@ static struct {
|
||||
unsigned cfg_seen; // cfg_version when preset was read
|
||||
uint64_t last_eval_ns; // plat_now_ns of the previous evaluation (FrameTimeDeltaInMsec)
|
||||
VkFfxImage fg_depth, fg_mv; // the last evaluation's depth and motion vectors, for frame generation
|
||||
VkImageView fg_depth_view, fg_mv_view;
|
||||
VkDlaaCamera fg_cam; // this frame's camera (mv_pass), fg_cam_ok when known
|
||||
int fg_cam_ok;
|
||||
float fg_jx, fg_jy;
|
||||
int fg_fresh, fg_reset; // fresh: not handed out yet (vk_dlaa_fg_inputs)
|
||||
// the picture without the HUD for frame generation (vk_dlaa_fg_hudless): DLSS's output encoded to sRGB as the game's
|
||||
// last pass encodes its picture, then copied bit for bit into an RGBA8 UNORM image, the format the present reads
|
||||
VkImage hl_srgb, hl;
|
||||
VmaAllocation hl_srgb_mem, hl_mem;
|
||||
VkImageView hl_view;
|
||||
uint32_t hl_w, hl_h;
|
||||
int hl_fresh;
|
||||
} D = {.mv_ok = -1, .cfg_seen = ~0u};
|
||||
|
||||
int vk_dlaa_fg_hudless(VkImage *img, VkImageView *view, uint32_t *w, uint32_t *h) {
|
||||
if (!D.hl_fresh) return 0;
|
||||
D.hl_fresh = 0;
|
||||
*img = D.hl, *view = D.hl_view, *w = D.hl_w, *h = D.hl_h;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int vk_dlaa_fg_views(VkImageView *depth, VkImageView *mv) {
|
||||
*depth = D.fg_depth_view, *mv = D.fg_mv_view;
|
||||
return D.fg_depth_view && D.fg_mv_view;
|
||||
}
|
||||
|
||||
int vk_dlaa_fg_camera(VkDlaaCamera *c) {
|
||||
if (!D.fg_cam_ok) return 0;
|
||||
*c = D.fg_cam;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int vk_dlaa_fg_inputs(VkFfxImage *depth, VkFfxImage *mv, float *jx, float *jy, int *reset) {
|
||||
if (!D.fg_fresh || D.failed) return 0;
|
||||
D.fg_fresh = 0;
|
||||
@@ -640,6 +668,30 @@ static void ocam_vp(const float *v, double out[16]) {
|
||||
}
|
||||
}
|
||||
|
||||
// Frame generation's camera (vk_dlaa_fg_camera) from the Ocarina block: the projection is rows 7-10, the view rows 0-2
|
||||
// (rows: right, up, back; the camera looks down -z), m the reprojection from this frame's clip space to last frame's.
|
||||
// Each matrix is handed out transposed, for row vectors.
|
||||
static void fg_store(float out[16], const double m[16]) {
|
||||
for (int r = 0; r < 4; r++)
|
||||
for (int k = 0; k < 4; k++) out[r * 4 + k] = (float)m[k * 4 + r];
|
||||
}
|
||||
static void fg_camera(const float *v, const double m[16]) {
|
||||
double P[16], Pi[16], mi[16];
|
||||
for (int i = 0; i < 16; i++) P[i] = v[28 + i];
|
||||
if (!inv4(P, Pi) || !inv4(m, mi)) return;
|
||||
VkDlaaCamera *c = &D.fg_cam;
|
||||
fg_store(c->view_to_clip, P), fg_store(c->clip_to_view, Pi);
|
||||
fg_store(c->clip_to_prev_clip, m), fg_store(c->prev_clip_to_clip, mi);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
c->right[i] = v[i], c->up[i] = v[4 + i], c->fwd[i] = -v[8 + i];
|
||||
// the position: -R^T t, R the view's 3x3 and t its last column
|
||||
c->pos[i] = -(v[i] * v[3] + v[4 + i] * v[7] + v[8 + i] * v[11]);
|
||||
}
|
||||
c->fov_y = P[5] != 0 ? 2.0f * (float)atan(1.0 / fabs(P[5])) : 1.0f;
|
||||
c->aspect = P[0] != 0 ? (float)fabs(P[5] / P[0]) : 1.777f;
|
||||
D.fg_cam_ok = 1;
|
||||
}
|
||||
|
||||
// The pass, recorded through the recorder like the replay's own commands. 0: no camera this frame (the vectors stay
|
||||
// zero and DLSS starts a new history). bias > 0 with this frame's G-buffer classes (D.ids) also writes e->mask, the
|
||||
// bias mask (dlaa_mv.comp); *masked says whether it did.
|
||||
@@ -674,6 +726,8 @@ static int mv_pass(VkExec *ex, struct Size *e, VkImageView zv, float jx, float j
|
||||
have_m = 1;
|
||||
}
|
||||
}
|
||||
D.fg_cam_ok = 0;
|
||||
if (cr && have_m && D.cam_size == OCAM_BLOCK) fg_camera(cr, m);
|
||||
int s = D.cam_slot, had_prev = D.cam_prev_ok;
|
||||
if (D.cam_size == CAM_BLOCK) { // the whole block, for the per-object motion pass (the deferred engine's only)
|
||||
VkBufferCopy bc = {cam.offset, (VkDeviceSize)s * CAM_SLOT, CAM_BLOCK};
|
||||
@@ -1039,6 +1093,8 @@ static int perf_quality(uint32_t w, uint32_t ow) {
|
||||
}
|
||||
|
||||
// dst: the target DLSS's picture goes to, NULL for the light target itself (DLAA in place).
|
||||
static void hudless(VkCommandBuffer cb, struct Size *e, uint32_t w, uint32_t h);
|
||||
|
||||
static void evaluate(VkExec *ex, const VkExecTarget *dst) {
|
||||
const VkExecTarget *t = &D.color, *z = &D.depth;
|
||||
if (!dst) dst = t;
|
||||
@@ -1179,12 +1235,14 @@ evaluated:
|
||||
VK_FILTER_NEAREST);
|
||||
full_barrier(cb);
|
||||
if (dst != t) vk_dlaa_swap_img = t->img, vk_dlaa_swap_view = e->out_view; // the scaling draw reads DLSS's picture
|
||||
if (vk_dlaa_hudless_wanted) hudless(cb, e, ow, oh); // frame generation keeps the HUD out of its generated frames
|
||||
ex->recorded += 4;
|
||||
vk_exec_draw_new_cb(); // NGX bound pipelines and descriptor sets of its own: the next draw binds everything again
|
||||
D.jdepth = z->image;
|
||||
// for frame generation at this frame's present (vk_dlaa_fg_inputs)
|
||||
D.fg_depth = (VkFfxImage){z->image, z->format, w, h, 0};
|
||||
D.fg_mv = (VkFfxImage){e->mv, VK_FORMAT_R16G16_SFLOAT, w, h, 1};
|
||||
D.fg_depth_view = zv, D.fg_mv_view = e->mv_view;
|
||||
D.fg_jx = jx, D.fg_jy = jy, D.fg_fresh = 1;
|
||||
next_jitter(w, ow);
|
||||
if (++D.evals == 1 || D.evals == 300 || D.evals % 3600 == 0) // 300: one early line with the frames counted
|
||||
@@ -1197,6 +1255,54 @@ evaluated:
|
||||
(unsigned long long)D.why[2], (unsigned long long)R.made, (unsigned long long)R.missed);
|
||||
}
|
||||
|
||||
// The HUD-less picture (vk_dlaa_fg_hudless): DLSS's output, which the HUD is then drawn over, through the encode the
|
||||
// game's last pass gives its picture (linear to sRGB), as RGBA8 UNORM bytes like the picture the present reads.
|
||||
int vk_dlaa_hudless_wanted;
|
||||
static void hudless(VkCommandBuffer cb, struct Size *e, uint32_t w, uint32_t h) {
|
||||
const VkCtx *c = vk_ctx();
|
||||
if (D.hl && (D.hl_w != w || D.hl_h != h)) {
|
||||
vkDeviceWaitIdle(c->device);
|
||||
vkDestroyImageView(c->device, D.hl_view, NULL);
|
||||
vmaDestroyImage(c->vma, D.hl, D.hl_mem), vmaDestroyImage(c->vma, D.hl_srgb, D.hl_srgb_mem);
|
||||
D.hl = D.hl_srgb = VK_NULL_HANDLE;
|
||||
}
|
||||
if (!D.hl) {
|
||||
VkImageView v2 = VK_NULL_HANDLE;
|
||||
VkImageUsageFlags u = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
if (!image2d(w, h, VK_FORMAT_R8G8B8A8_SRGB, u, &D.hl_srgb, &D.hl_srgb_mem, &v2) ||
|
||||
!image2d(w, h, VK_FORMAT_R8G8B8A8_UNORM, u | VK_IMAGE_USAGE_STORAGE_BIT, &D.hl, &D.hl_mem, &D.hl_view)) {
|
||||
rt_log("dlaa: the HUD-less picture's images could not be made: frame generation sees the HUD");
|
||||
vk_dlaa_hudless_wanted = 0;
|
||||
return;
|
||||
}
|
||||
if (v2) vkDestroyImageView(c->device, v2, NULL);
|
||||
D.hl_w = w, D.hl_h = h;
|
||||
VkImageMemoryBarrier2 ib[2];
|
||||
VkImage im[2] = {D.hl_srgb, D.hl};
|
||||
for (int i = 0; i < 2; i++)
|
||||
ib[i] = (VkImageMemoryBarrier2){.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
|
||||
.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
||||
.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT | VK_ACCESS_2_MEMORY_WRITE_BIT,
|
||||
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_GENERAL,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = im[i],
|
||||
.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}};
|
||||
VkDependencyInfo di = {.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .imageMemoryBarrierCount = 2,
|
||||
.pImageMemoryBarriers = ib};
|
||||
vkCmdPipelineBarrier2(cb, &di);
|
||||
}
|
||||
VkImageBlit b = {.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .srcOffsets = {{0, 0, 0}, {(int32_t)w, (int32_t)h, 1}},
|
||||
.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstOffsets = {{0, 0, 0}, {(int32_t)w, (int32_t)h, 1}}};
|
||||
vkCmdBlitImage(cb, e->out, VK_IMAGE_LAYOUT_GENERAL, D.hl_srgb, VK_IMAGE_LAYOUT_GENERAL, 1, &b, VK_FILTER_NEAREST);
|
||||
full_barrier(cb);
|
||||
VkImageCopy cp = {.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}, .dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1},
|
||||
.extent = {w, h, 1}};
|
||||
vkCmdCopyImage(cb, D.hl_srgb, VK_IMAGE_LAYOUT_GENERAL, D.hl, VK_IMAGE_LAYOUT_GENERAL, 1, &cp);
|
||||
full_barrier(cb);
|
||||
D.hl_fresh = 1;
|
||||
}
|
||||
|
||||
static int scene_color(VkFormat f) {
|
||||
return f == VK_FORMAT_B10G11R11_UFLOAT_PACK32 || f == VK_FORMAT_R16G16B16A16_SFLOAT;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,20 @@ int vk_dlaa_fsr3(void);
|
||||
// Frame generation (vk_fg.cpp): the depth and motion vectors of the frame DLSS or FSR 3.1 last ran on, with its jitter
|
||||
// and whether its history reset; 1 once per evaluation (0: none since the last call). The images stay valid until the
|
||||
// next frame's scene draws over them, after this present.
|
||||
int vk_dlaa_fg_inputs(VkFfxImage *depth, VkFfxImage *mv, float *jx, float *jy, int *reset); // ... and its temporal upscaler is AMD FSR 3.1 (NX2NAVI_TAA = fsr3) instead of DLSS
|
||||
int vk_dlaa_fg_inputs(VkFfxImage *depth, VkFfxImage *mv, float *jx, float *jy, int *reset);
|
||||
// ... their views, and this frame's camera for DLSS Frame Generation (vk_dlssg.c): matrices for row vectors (v * M, the
|
||||
// rows stored in order), as Streamline takes them; 0 without a camera that frame.
|
||||
typedef struct {
|
||||
float view_to_clip[16], clip_to_view[16], clip_to_prev_clip[16], prev_clip_to_clip[16];
|
||||
float pos[3], up[3], right[3], fwd[3];
|
||||
float fov_y, aspect;
|
||||
} VkDlaaCamera;
|
||||
int vk_dlaa_fg_views(VkImageView *depth, VkImageView *mv);
|
||||
// The picture without the HUD at the size of the game's picture, RGBA8 UNORM bytes like it (sRGB-encoded), made at
|
||||
// each evaluation while vk_dlaa_hudless_wanted (frame generation sets it); 1 once per evaluation.
|
||||
extern int vk_dlaa_hudless_wanted;
|
||||
int vk_dlaa_fg_hudless(VkImage *img, VkImageView *view, uint32_t *w, uint32_t *h);
|
||||
int vk_dlaa_fg_camera(VkDlaaCamera *c); // ... and its temporal upscaler is AMD FSR 3.1 (NX2NAVI_TAA = fsr3) instead of DLSS
|
||||
// vk_device.c: NGX's instance and device extensions join the lists (room for max names); vk_dlaa_init after the
|
||||
// device exists brings NGX up and checks that DLSS is available.
|
||||
void vk_dlaa_instance_exts(const char **ext, uint32_t *n, uint32_t max);
|
||||
|
||||
157
runtime/vk_dlssg.c
Normal file
157
runtime/vk_dlssg.c
Normal file
@@ -0,0 +1,157 @@
|
||||
// DLSS Frame Generation (NX2NAVI_FRAMEGEN = dlss; applies after a restart) through nx2navi_dlssg.dll
|
||||
// (runtime/dlssg/nx2_sl.cpp), which drives NVIDIA Streamline. Like AMD's frame generation (vk_fg.cpp) it needs DLSS or
|
||||
// FSR 3.1 upscaling on, whose depth, motion vectors and camera it hands over, and an RTX 40 or 50 GPU.
|
||||
//
|
||||
// The DLL and Streamline (sl.interposer.dll and its plugins, nvngx_dlssg.dll) sit next to the game. The instance and
|
||||
// device are created through Streamline's proxies (vk_device.c), so it can add the extensions, features and queues
|
||||
// DLSS-G needs; the present path calls Streamline's swapchain functions (gfx_present.c), and every present records the
|
||||
// frame's constants and tags first (vk_dlssg_frame). Generation pauses while the menu is open.
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cfg.h"
|
||||
#include "host.h"
|
||||
#include "vk.h"
|
||||
#include "vk_dlaa.h"
|
||||
#include "vk_dlssg.h"
|
||||
#include "dlssg/nx2_sl.h"
|
||||
#include "ui/menu.h"
|
||||
|
||||
static struct {
|
||||
int wanted, loaded, active;
|
||||
PFN_nx2sl_init init;
|
||||
PFN_nx2sl_instance_proc gipa;
|
||||
PFN_nx2sl_device_proc gdpa;
|
||||
PFN_nx2sl_available available;
|
||||
PFN_nx2sl_frame frame;
|
||||
PFN_nx2sl_present_marker marker;
|
||||
PFN_nx2sl_shutdown shutdown;
|
||||
PFN_vkCreateSwapchainKHR create_sc;
|
||||
PFN_vkDestroySwapchainKHR destroy_sc;
|
||||
PFN_vkGetSwapchainImagesKHR images;
|
||||
PFN_vkAcquireNextImageKHR acquire;
|
||||
PFN_vkQueuePresentKHR present;
|
||||
} S = {.wanted = -1};
|
||||
|
||||
static void sl_line(const char *line) { rt_log("%s", line); }
|
||||
|
||||
int vk_dlssg_wanted(void) {
|
||||
if (S.wanted < 0) {
|
||||
S.wanted = !strcmp(cfg_value("NX2NAVI_FRAMEGEN"), "dlss");
|
||||
if (S.wanted && !vk_dlaa_wanted()) {
|
||||
rt_log("dlssg: frame generation needs DLSS or FSR 3.1 upscaling (any mode): off");
|
||||
S.wanted = 0;
|
||||
}
|
||||
}
|
||||
return S.wanted;
|
||||
}
|
||||
|
||||
int vk_dlssg_early(void) {
|
||||
if (!vk_dlssg_wanted()) return 0;
|
||||
if (S.loaded) return S.loaded > 0;
|
||||
S.loaded = -1;
|
||||
#ifdef _WIN32
|
||||
wchar_t dir[MAX_PATH], path[MAX_PATH + 32];
|
||||
DWORD n = GetModuleFileNameW(NULL, dir, MAX_PATH);
|
||||
while (n && dir[n - 1] != L'\\') n--;
|
||||
dir[n ? n - 1 : 0] = 0;
|
||||
swprintf(path, MAX_PATH + 32, L"%ls\\nx2navi_dlssg.dll", dir);
|
||||
HMODULE m = LoadLibraryW(path);
|
||||
if (!m) {
|
||||
rt_log("dlssg: nx2navi_dlssg.dll is not next to the game: DLSS Frame Generation off");
|
||||
return 0;
|
||||
}
|
||||
PFN_nx2sl_version ver = (PFN_nx2sl_version)(void *)GetProcAddress(m, "nx2sl_version");
|
||||
S.init = (PFN_nx2sl_init)(void *)GetProcAddress(m, "nx2sl_init");
|
||||
S.gipa = (PFN_nx2sl_instance_proc)(void *)GetProcAddress(m, "nx2sl_instance_proc");
|
||||
S.gdpa = (PFN_nx2sl_device_proc)(void *)GetProcAddress(m, "nx2sl_device_proc");
|
||||
S.available = (PFN_nx2sl_available)(void *)GetProcAddress(m, "nx2sl_available");
|
||||
S.frame = (PFN_nx2sl_frame)(void *)GetProcAddress(m, "nx2sl_frame");
|
||||
S.marker = (PFN_nx2sl_present_marker)(void *)GetProcAddress(m, "nx2sl_present_marker");
|
||||
S.shutdown = (PFN_nx2sl_shutdown)(void *)GetProcAddress(m, "nx2sl_shutdown");
|
||||
if (!ver || ver() != NX2SL_VERSION || !S.init || !S.gipa || !S.gdpa || !S.available || !S.frame || !S.marker) {
|
||||
rt_log("dlssg: nx2navi_dlssg.dll is from another build: DLSS Frame Generation off");
|
||||
return 0;
|
||||
}
|
||||
if (!S.init(dir, sl_line)) return 0;
|
||||
S.loaded = 1;
|
||||
return 1;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
VkResult vk_dlssg_create_instance(const VkInstanceCreateInfo *ci, VkInstance *out) {
|
||||
PFN_vkCreateInstance f = S.loaded > 0 ? (PFN_vkCreateInstance)S.gipa()(VK_NULL_HANDLE, "vkCreateInstance") : NULL;
|
||||
return f ? f(ci, NULL, out) : vkCreateInstance(ci, NULL, out);
|
||||
}
|
||||
|
||||
VkResult vk_dlssg_create_device(VkInstance inst, VkPhysicalDevice pd, const VkDeviceCreateInfo *ci, VkDevice *out) {
|
||||
PFN_vkCreateDevice f = S.loaded > 0 ? (PFN_vkCreateDevice)S.gipa()(inst, "vkCreateDevice") : NULL;
|
||||
return f ? f(pd, ci, NULL, out) : vkCreateDevice(pd, ci, NULL, out);
|
||||
}
|
||||
|
||||
void vk_dlssg_device_ready(VkDevice dev) {
|
||||
if (S.loaded <= 0 || !S.available()) return;
|
||||
PFN_vkGetDeviceProcAddr g = S.gdpa();
|
||||
S.create_sc = (PFN_vkCreateSwapchainKHR)g(dev, "vkCreateSwapchainKHR");
|
||||
S.destroy_sc = (PFN_vkDestroySwapchainKHR)g(dev, "vkDestroySwapchainKHR");
|
||||
S.images = (PFN_vkGetSwapchainImagesKHR)g(dev, "vkGetSwapchainImagesKHR");
|
||||
S.acquire = (PFN_vkAcquireNextImageKHR)g(dev, "vkAcquireNextImageKHR");
|
||||
S.present = (PFN_vkQueuePresentKHR)g(dev, "vkQueuePresentKHR");
|
||||
S.active = S.create_sc && S.destroy_sc && S.images && S.acquire && S.present;
|
||||
rt_log("dlssg: %s", S.active ? "DLSS Frame Generation on the swapchain" : "the swapchain hooks are missing: off");
|
||||
}
|
||||
|
||||
int vk_dlssg_active(void) { return S.active; }
|
||||
|
||||
VkResult vk_dlssg_create_swapchain(VkDevice d, const VkSwapchainCreateInfoKHR *ci, VkSwapchainKHR *sc) {
|
||||
return S.create_sc(d, ci, NULL, sc);
|
||||
}
|
||||
void vk_dlssg_destroy_swapchain(VkDevice d, VkSwapchainKHR sc) { S.destroy_sc(d, sc, NULL); }
|
||||
VkResult vk_dlssg_get_images(VkDevice d, VkSwapchainKHR sc, uint32_t *n, VkImage *img) { return S.images(d, sc, n, img); }
|
||||
VkResult vk_dlssg_acquire(VkDevice d, VkSwapchainKHR sc, uint64_t t, VkSemaphore sem, uint32_t *idx) {
|
||||
return S.acquire(d, sc, t, sem, VK_NULL_HANDLE, idx);
|
||||
}
|
||||
|
||||
VkResult vk_dlssg_present(VkQueue q, const VkPresentInfoKHR *pi) {
|
||||
S.marker(0);
|
||||
VkResult r = S.present(q, pi);
|
||||
S.marker(1);
|
||||
return r;
|
||||
}
|
||||
|
||||
void vk_dlssg_frame(VkCommandBuffer cb, VkExtent2D ext) {
|
||||
if (!S.active) return;
|
||||
VkFfxImage d, m;
|
||||
VkImageView dv, mvv;
|
||||
VkDlaaCamera c;
|
||||
float jx = 0, jy = 0;
|
||||
int reset = 0;
|
||||
int have = vk_dlaa_fg_inputs(&d, &m, &jx, &jy, &reset) && vk_dlaa_fg_views(&dv, &mvv) && vk_dlaa_fg_camera(&c);
|
||||
int on = have && !menu_is_open();
|
||||
Nx2SlImage zd = {d.image, dv, d.format, d.width, d.height, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT};
|
||||
Nx2SlImage zm = {m.image, mvv, m.format, m.width, m.height, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT};
|
||||
Nx2SlCamera k;
|
||||
memset(&k, 0, sizeof k);
|
||||
if (have) {
|
||||
memcpy(k.view_to_clip, c.view_to_clip, 64), memcpy(k.clip_to_view, c.clip_to_view, 64);
|
||||
memcpy(k.clip_to_prev_clip, c.clip_to_prev_clip, 64), memcpy(k.prev_clip_to_clip, c.prev_clip_to_clip, 64);
|
||||
memcpy(k.pos, c.pos, 12), memcpy(k.up, c.up, 12), memcpy(k.right, c.right, 12), memcpy(k.fwd, c.fwd, 12);
|
||||
k.znear = 0.1f, k.zfar = 1e8f, k.fov_y = c.fov_y, k.aspect = c.aspect; // Ocarina's projection (vk_ffx.cpp)
|
||||
k.jitter[0] = jx, k.jitter[1] = jy;
|
||||
k.mv_scale[0] = 1.0f / (float)d.width, k.mv_scale[1] = 1.0f / (float)d.height; // render pixels
|
||||
k.reset = reset;
|
||||
}
|
||||
S.frame(cb, have ? &zd : NULL, have ? &zm : NULL, NULL, have ? &k : NULL, ext.width, ext.height, on);
|
||||
}
|
||||
|
||||
void vk_dlssg_release(void) {
|
||||
if (S.loaded > 0 && S.shutdown) S.shutdown();
|
||||
S.active = 0;
|
||||
}
|
||||
20
runtime/vk_dlssg.h
Normal file
20
runtime/vk_dlssg.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// DLSS Frame Generation through NVIDIA Streamline (vk_dlssg.c, nx2navi_dlssg.dll).
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
|
||||
int vk_dlssg_wanted(void); // NX2NAVI_FRAMEGEN = dlss with DLSS or FSR 3.1 upscaling on (read once)
|
||||
int vk_dlssg_early(void); // before the instance exists: loads the DLL and Streamline; 1 when its proxies are there
|
||||
// vk_device.c: the instance and device through Streamline's proxies when vk_dlssg_early succeeded, else the loader's
|
||||
VkResult vk_dlssg_create_instance(const VkInstanceCreateInfo *ci, VkInstance *out);
|
||||
VkResult vk_dlssg_create_device(VkInstance inst, VkPhysicalDevice pd, const VkDeviceCreateInfo *ci, VkDevice *out);
|
||||
void vk_dlssg_device_ready(VkDevice dev); // after the device: checks DLSS-G and takes the swapchain hooks
|
||||
int vk_dlssg_active(void);
|
||||
// gfx_present.c's swapchain calls while active
|
||||
VkResult vk_dlssg_create_swapchain(VkDevice d, const VkSwapchainCreateInfoKHR *ci, VkSwapchainKHR *sc);
|
||||
void vk_dlssg_destroy_swapchain(VkDevice d, VkSwapchainKHR sc);
|
||||
VkResult vk_dlssg_get_images(VkDevice d, VkSwapchainKHR sc, uint32_t *n, VkImage *img);
|
||||
VkResult vk_dlssg_acquire(VkDevice d, VkSwapchainKHR sc, uint64_t timeout, VkSemaphore sem, uint32_t *idx);
|
||||
VkResult vk_dlssg_present(VkQueue q, const VkPresentInfoKHR *pi);
|
||||
void vk_dlssg_frame(VkCommandBuffer cb, VkExtent2D ext); // each present, before its command buffer is submitted
|
||||
void vk_dlssg_release(void);
|
||||
@@ -42,7 +42,7 @@ struct {
|
||||
ffxQueryDescSwapchainReplacementFunctionsVK fn = {};
|
||||
uint64_t frame = 0;
|
||||
int failed = 0;
|
||||
uint64_t generated = 0, paused = 0, no_inputs = 0;
|
||||
uint64_t generated = 0, paused = 0, no_inputs = 0, hudless = 0;
|
||||
} F;
|
||||
|
||||
VkResult submit_game(uint32_t n, const VkSubmitInfo *si, VkFence fence) {
|
||||
@@ -141,8 +141,15 @@ extern "C" VkResult vk_fg_present(const VkPresentInfoKHR *pi) {
|
||||
return F.fn.pOutQueuePresentKHR(vk_ctx()->gfx_queue, pi);
|
||||
}
|
||||
|
||||
extern "C" void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt, VkExtent2D ext) {
|
||||
extern "C" uint64_t vk_fg_presents(VkSwapchainKHR sc) {
|
||||
return vk_fg_active() && F.fn.pOutGetLastPresentCountFFXAPI && sc ? F.fn.pOutGetLastPresentCountFFXAPI(sc) : 0;
|
||||
}
|
||||
|
||||
extern "C" void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt, VkExtent2D ext, VkImage hudless,
|
||||
VkImageView hudless_view) {
|
||||
(void)hudless_view;
|
||||
if (!vk_fg_active()) 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();
|
||||
if (F.fg && (F.fg_ext.width != ext.width || F.fg_ext.height != ext.height || F.fg_fmt != fmt)) {
|
||||
vkDeviceWaitIdle(c->device); // a new window size: the context is made for its display size
|
||||
@@ -155,9 +162,14 @@ extern "C" void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt,
|
||||
be.vkDevice = c->device;
|
||||
be.vkPhysicalDevice = c->phys;
|
||||
be.vkDeviceProcAddr = vkGetDeviceProcAddr;
|
||||
// the HUD-less picture has the swapchain's format (gfx_present.c hudless_record)
|
||||
ffxCreateContextDescFrameGenerationHudless hl = {};
|
||||
hl.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION_HUDLESS;
|
||||
hl.header.pNext = &be.header;
|
||||
hl.hudlessBackBufferFormat = ffxApiGetSurfaceFormatVK(fmt);
|
||||
ffxCreateContextDescFrameGeneration d = {};
|
||||
d.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION;
|
||||
d.header.pNext = &be.header;
|
||||
d.header.pNext = &hl.header;
|
||||
d.flags = FFX_FRAMEGENERATION_ENABLE_DEPTH_INFINITE;
|
||||
d.displaySize = {ext.width, ext.height};
|
||||
d.maxRenderSize = {ext.width, ext.height};
|
||||
@@ -183,6 +195,11 @@ extern "C" void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt,
|
||||
cfg.frameGenerationEnabled = on != 0;
|
||||
cfg.allowAsyncWorkloads = false;
|
||||
cfg.generationRect = {0, 0, (int32_t)ext.width, (int32_t)ext.height};
|
||||
if (on && hudless) {
|
||||
VkFfxImage h = {hudless, fmt, ext.width, ext.height, 1};
|
||||
cfg.HUDLessColor = image(&h);
|
||||
F.hudless++;
|
||||
}
|
||||
cfg.frameID = F.frame;
|
||||
ffxReturnCode_t r = vk_ffx_configure(&F.fg, &cfg.header);
|
||||
if (r != FFX_API_RETURN_OK) return fail("frame generation configuration", r);
|
||||
@@ -208,9 +225,9 @@ extern "C" void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt,
|
||||
r = vk_ffx_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("fg: %llu frames presented, %llu generated; paused %llu (menu), %llu without depth and motion",
|
||||
(unsigned long long)F.frame, (unsigned long long)F.generated, (unsigned long long)F.paused,
|
||||
(unsigned long long)F.no_inputs);
|
||||
rt_log("fg: %llu frames presented, %llu generated (%llu with the HUD kept out); paused %llu (menu), %llu without depth "
|
||||
"and motion", (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);
|
||||
}
|
||||
|
||||
extern "C" void vk_fg_release(void) {
|
||||
|
||||
@@ -16,5 +16,8 @@ VkResult vk_fg_acquire(VkSwapchainKHR sc, uint64_t timeout, VkSemaphore sem, uin
|
||||
VkResult vk_fg_present(const VkPresentInfoKHR *pi); // on the game's queue, its lock held by the caller
|
||||
// Each present, before its command buffer is submitted: configures generation for this frame and records its
|
||||
// preparation (the frame's depth and motion vectors, vk_dlaa_fg_inputs) into cb.
|
||||
void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt, VkExtent2D ext);
|
||||
// hudless: this frame's picture without the HUD at the swapchain's size and format, in GENERAL (VK_NULL_HANDLE: none),
|
||||
// from which generation tells the HUD apart and keeps it out of the generated frames.
|
||||
void vk_fg_frame(VkCommandBuffer cb, VkSwapchainKHR sc, VkFormat fmt, VkExtent2D ext, VkImage hudless, VkImageView hudless_view);
|
||||
uint64_t vk_fg_presents(VkSwapchainKHR sc); // frames the swapchain presented, generated ones included (0: off)
|
||||
void vk_fg_release(void);
|
||||
|
||||
65
tools/build_dlssg.py
Normal file
65
tools/build_dlssg.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""Build nx2navi_dlssg.dll (runtime/dlssg/nx2_sl.cpp, DLSS Frame Generation through NVIDIA Streamline) with MSVC.
|
||||
|
||||
python tools/build_dlssg.py --sl <Streamline SDK folder with include/> [--out build/dlssg]
|
||||
|
||||
Streamline's C++ types have MSVC's ABI, so this one DLL is built with Visual Studio's compiler (the Build Tools are
|
||||
enough; vswhere finds them), against the SDK's headers, which stay where they are. The Vulkan headers are MSYS2's.
|
||||
The build kit ships the DLL ready-made beside Streamline's own runtime DLLs (tools/make_kit.py --dlssg).
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
VSWHERE = Path(os.environ.get('ProgramFiles(x86)', r'C:\Program Files (x86)')) / 'Microsoft Visual Studio' / 'Installer' / 'vswhere.exe'
|
||||
|
||||
|
||||
def vcvars():
|
||||
if not VSWHERE.is_file():
|
||||
sys.exit('Visual Studio (or its Build Tools) is not installed: vswhere.exe is missing')
|
||||
path = subprocess.run([str(VSWHERE), '-latest', '-products', '*', '-requires',
|
||||
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', '-property', 'installationPath'],
|
||||
capture_output=True, text=True).stdout.strip()
|
||||
bat = Path(path) / 'VC' / 'Auxiliary' / 'Build' / 'vcvars64.bat'
|
||||
if not bat.is_file():
|
||||
sys.exit(f'no vcvars64.bat under {path or "(no Visual Studio with the C++ tools)"}')
|
||||
return bat
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
ap.add_argument('--sl', type=Path, required=True, help='the Streamline SDK folder (holds include/sl.h)')
|
||||
ap.add_argument('--vulkan', type=Path, default=Path(r'C:\msys64\ucrt64\include'), help='folder holding vulkan/vulkan.h')
|
||||
ap.add_argument('--out', type=Path, default=ROOT / 'build' / 'dlssg')
|
||||
a = ap.parse_args()
|
||||
if not (a.sl / 'include' / 'sl.h').is_file():
|
||||
sys.exit(f'{a.sl / "include" / "sl.h"} is missing')
|
||||
a.out.mkdir(parents=True, exist_ok=True)
|
||||
# only the Vulkan headers: MSYS2's own C library headers must not shadow MSVC's
|
||||
inc = a.out / 'include'
|
||||
for d in ('vulkan', 'vk_video'):
|
||||
if (a.vulkan / d).is_dir():
|
||||
shutil.copytree(a.vulkan / d, inc / d, dirs_exist_ok=True)
|
||||
src = ROOT / 'runtime' / 'dlssg' / 'nx2_sl.cpp'
|
||||
cmd = (f'call "{vcvars()}" >nul && cl /nologo /LD /O2 /EHsc /std:c++17 /W3 /DNOMINMAX /DWIN32_LEAN_AND_MEAN '
|
||||
f'/I"{a.sl / "include"}" /I"{inc}" /I"{src.parent}" "{src}" /Fo"{a.out}\\\\" '
|
||||
f'/Fe"{a.out / "nx2navi_dlssg.dll"}" /link /NOLOGO')
|
||||
with tempfile.NamedTemporaryFile('w', suffix='.cmd', delete=False) as f:
|
||||
# vcvars64.bat runs vswhere itself and needs it on PATH
|
||||
f.write('@echo off\r\nset "PATH=%PATH%;' + str(VSWHERE.parent) + '"\r\n' + cmd + '\r\n')
|
||||
script = f.name
|
||||
try:
|
||||
r = subprocess.run(['cmd', '/c', script])
|
||||
finally:
|
||||
os.unlink(script)
|
||||
if r.returncode or not (a.out / 'nx2navi_dlssg.dll').is_file():
|
||||
sys.exit(f'the build failed ({r.returncode})')
|
||||
print(a.out / 'nx2navi_dlssg.dll')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user