Keep #594's DLSS work off for players without DLSS

The scene patch, the heap sized for the largest DLSS scene and the function-entry hook that catches the game's render
size setup were on for every 1.4.3 player; they are on now only when DLSS can run (dlaa = 1, nvngx_dlss.dll there),
and the hook gives up after 1800 presents if the setup is never seen. Everyone else keeps the game's own scene size
and dynamic resolution, as before #594.

The uniform snapshot's second copy in host memory and the per-object motion's last-frame blocks are made only while
DLSS is available: without it nothing reads them, and the snapshot goes into the device copy in one pass as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Valiantsin DzerakhandClaude Opus 5.5 committed 2026-11-03 19:39:49 +03:00
1 parent 80349df1a6
commit 13b602f28f
3 files changed
+37 -13

No files matched your search

+19 -6
View File
@@ -56,6 +56,7 @@ static struct {
double dlss_k; // TOTK_DLSS quality/balanced/performance/ultra_performance: the scene's share of the output, else 0
double base_k; // the scene's share without a DLSS mode (render_scale, or the game's 5/6)
unsigned hw, hh; // the largest scene a DLSS mode change can ask for: the heap is sized for it
int live; // 1.4.3 with DLSS able to run: the DLSS mode can change while the game runs
} R;
// The scene size is decided before the device exists, so NGX cannot be asked yet. NGX loads DLSS from nvngx_dlss.dll
@@ -130,15 +131,20 @@ static void decide(void) {
rt_log("resolution: TOTK_RENDER_SCALE=%s is not game or a percentage 25..400; game", s);
}
}
// A DLSS mode other than dlaa sets the scene size instead, and can change while the game runs (res_dlss_mode): the
// scene patch is always on, and the heap is sized for the largest scene the modes ask for.
// A DLSS mode other than dlaa sets the scene size instead. On 1.4.3 with DLSS able to run (DLAA on, the DLL there)
// the mode can change while the game runs (res_dlss_mode): the scene patch is then always on and the heap is sized
// for the largest scene the modes ask for. Everyone else keeps the game's own scene size and its dynamic resolution
// unless render_scale or a DLSS mode asks for another, and a heap sized for that scene alone.
R.base_k = k;
R.scene_set = 1;
R.live = RT_V143 && cfg_bool("TOTK_DLAA") && dlss_dll();
const char *m = cfg_value("TOTK_DLSS");
R.dlss_k = dlss_share(m);
if (R.dlss_k) rt_log("resolution: DLSS %s: the scene at %.0f %% of the output (render_scale ignored)", m, R.dlss_k * 100);
scene_for(R.dlss_k ? R.dlss_k : k, &R.sw, &R.sh);
scene_for(fmax(k, 2.0 / 3.0), &R.hw, &R.hh);
double sk = R.dlss_k ? R.dlss_k : k;
if (R.live || R.dlss_k || R.ow * sk > SCENE_MAX || R.oh * sk > SCENE_MAX) R.scene_set = 1;
scene_for(sk, &R.sw, &R.sh);
if (R.live) scene_for(fmax(k, 2.0 / 3.0), &R.hw, &R.hh);
else R.hw = R.sw, R.hh = R.sh;
const char *sr = cfg_value("TOTK_SHADOW_RES");
if (strcmp(sr, "game")) {
unsigned n = (unsigned)atoi(sr);
@@ -326,7 +332,7 @@ void res_init(void) {
rt_patch_switch(output, R.ow != BASE_W || R.oh != BASE_H);
rt_patch_switch(scene, scene_on);
R.on = 1;
if (RT_V143) g_fnprof = 1; // until the game sets its render size: res_fn_hit
if (R.live) g_fnprof = 1; // until the game sets its render size: res_fn_hit, or res_present gives up
// Another shape than 16:9 (more than 1% off): the camera takes the output's ratio (recomp/patches-v121.txt and
// patches-v143.txt, set aspect), so a 21:9 output shows more of the world at the sides instead of a stretched 16:9 view. The HUD's layouts
// for that shape come from romfs-patches-<w>x<h> (romfs.c overlay_init, tools/make_romfs_patches.py --aspect).
@@ -415,6 +421,13 @@ void res_fn_hit(uint64_t addr, uint64_t x0) {
}
void res_present(Cpu *c) {
// The hook runs on every guest function entry while it waits: should the game set its render size before
// res_init or never, it stops after 1800 presents (half a minute at 60 fps) and modes change at the next start.
static unsigned waited;
if (g_fnprof && !renderer && ++waited == 1800) {
g_fnprof = 0;
rt_log("resolution: the game's render size setup was not seen; DLSS modes change at the next start");
}
if (!size_pending || !renderer) return;
size_pending = 0;
Cpu keep = *c; // the guest's registers as at the present call: the setup runs as if it never happened
+2
View File
@@ -172,6 +172,8 @@ static void mvprev_draw_(VkExec *ex) {
}
void vk_mvprev_frame(void) {
// without DLSS (no NGX, no RTX GPU, dlaa = 0) nothing reads last frame's blocks: P.on stays 0, no tables
if (!vk_dlaa_available()) return;
if (P.knob < 0) {
P.knob = rt_knob("DLAA_OBJMV", "1");
if (P.knob) {
+16 -7
View File
@@ -22,6 +22,7 @@
#include "host.h"
#include "nvn_api.h"
#include "vk.h"
#include "vk_dlaa.h"
#include "platform/platform.h"
#define ALIGN 256u
@@ -34,8 +35,9 @@ typedef struct Arena {
VkBuffer buf;
VmaAllocation alloc;
uint8_t *map;
uint8_t *cpu; // the same bytes in host memory, for the CPU's reads (vk_usnap_bytes): map is often device memory
// through the BAR, written fast but read back at about a microsecond a cache line
uint8_t *cpu; // with DLSS (vk_dlaa_available), the same bytes in host memory for the CPU's reads (vk_usnap_bytes):
// map is often device memory through the BAR, written fast but read back at about a microsecond a
// cache line. Without DLSS nothing reads them back, and the bytes go into map in one copy.
uint64_t size;
} Arena;
@@ -98,7 +100,6 @@ static Arena *arena_get(uint64_t need) {
return NULL;
}
a->map = info.pMappedData;
if (!(a->cpu = malloc(a->size))) rt_fatal("vk_usnap: out of memory");
rt_log("vk_usnap: new arena of %.1f MiB", a->size / 1048576.0);
return a;
}
@@ -283,9 +284,14 @@ void vk_usnap_begin(const uint64_t *handles, int n, uint64_t fence) {
}
if (!U.nreg) return;
if (!(U.arena = arena_get(total))) return;
uint8_t *dst = U.arena->map;
if (vk_dlaa_available()) {
if (!U.arena->cpu && !(U.arena->cpu = malloc(U.arena->size))) rt_fatal("vk_usnap: out of memory");
dst = U.arena->cpu;
}
for (int i = 0; i < U.nreg; i++)
memcpy(U.arena->cpu + U.reg[i].off, (const void *)(uintptr_t)U.reg[i].lo, U.reg[i].hi - U.reg[i].lo);
memcpy(U.arena->map, U.arena->cpu, total); // one sequential write into the device copy
memcpy(dst + U.reg[i].off, (const void *)(uintptr_t)U.reg[i].lo, U.reg[i].hi - U.reg[i].lo);
if (dst != U.arena->map) memcpy(U.arena->map, dst, total); // one sequential write into the device copy
vmaFlushAllocation(vk_ctx()->vma, U.arena->alloc, 0, total); // a no-op on coherent memory
U.on = 1;
U.subs++, U.regions += (uint64_t)U.nreg, U.bytes += total, U.copy_ns += kern_now_ns() - t0;
@@ -331,11 +337,14 @@ void vk_usnap_drop(uint64_t addr, uint64_t n) {
uint64_t vk_usnap_gen(void) { return U.gen; }
const uint8_t *vk_usnap_map(void) { return U.arena ? U.arena->cpu : NULL; }
// the host copy where there is one (DLSS), else the device copy
static uint8_t *host_copy(void) { return U.arena->cpu ? U.arena->cpu : U.arena->map; }
const uint8_t *vk_usnap_map(void) { return U.arena ? host_copy() : NULL; }
const uint8_t *vk_usnap_bytes(uint64_t addr, uint64_t n) {
VkDescriptorBufferInfo bi;
return vk_usnap_find(addr, n, 1, &bi) ? U.arena->cpu + bi.offset : NULL;
return vk_usnap_find(addr, n, 1, &bi) ? host_copy() + bi.offset : NULL;
}
// The arenas back on the free list (vk_exec_draw_shutdown: the device is idle, every completion ran).