From edce57ce5daab80a34016db29e886afe86685471 Mon Sep 17 00:00:00 2001 From: chrg6 Date: Sat, 7 Nov 2026 10:56:45 -0500 Subject: [PATCH] Render DLSS's quality modes at their own scale by fixing the game's dynamic resolution. The game sizes its scene each frame from a GPU-time controller. The new patch set res_fixed takes the controller's fixed path and reads the scale from the patch page, so DLSS Quality, Balanced, Performance and Ultra Performance render at 67, 58, 50 and 33 percent of the picture and DLAA at all of it. The set goes on once the controller has run for a few frames, and the menu's Upscaling choice lists the modes and switches them live. Co-Authored-By: Claude Opus 5.5 --- recomp/patches-v111.txt | 9 ++++++++ runtime/cfg.c | 2 +- runtime/host.h | 3 ++- runtime/nvn_api_obj.c | 1 + runtime/ui/menu.cpp | 32 ++++++++++++++++++--------- runtime/ui_text.c | 11 ++++----- runtime/widescreen.c | 49 ++++++++++++++++++++++++++++++++++++----- 7 files changed, 84 insertions(+), 23 deletions(-) diff --git a/recomp/patches-v111.txt b/recomp/patches-v111.txt index 1c79d75b..679210d7 100644 --- a/recomp/patches-v111.txt +++ b/recomp/patches-v111.txt @@ -15,6 +15,7 @@ set wide_scene NX2NAVI_PATCH_WIDE_SCENE 0 set wide_aspect NX2NAVI_PATCH_WIDE_ASPECT 0 set wide_hud NX2NAVI_PATCH_WIDE_HUD 0 set wide_movie NX2NAVI_PATCH_WIDE_MOVIE 0 +set res_fixed NX2NAVI_PATCH_RES_FIXED 0 set ostep NX2NAVI_OCARINA_REALTIME 1 # Real-time step: the framework's per-frame update (main+0x6e6be0) gives the time manager **[main+0x4c7e890] its @@ -39,6 +40,14 @@ site wide_out main 31f39f8 f2c0b408 f9403108 # movk x8, #0x5a0, lsl #32 -> ldr site wide_scene main 31f3a00 d280f008 @page:x8 # mov x8, #0x780 -> adrp x8, page site wide_scene main 31f3a04 f2c08708 f9403508 # movk x8, #0x438, lsl #32 -> ldr x8, [x8, #104] +# Render scale: the game sizes its 3D scene by a dynamic-resolution controller that follows its GPU-time reports (3/4 +# of the picture here). Bit 0 of an engine config word at +0x94 picks it (main+0x805bd0); the other path takes a fixed +# scale of 1.0 (main+0x806420). res_fixed always takes the fixed path and reads its scale from +240 (a float, written by +# runtime/widescreen.c from the DLSS mode). It is switched on only once the controller has run for a while, as its +# fields would otherwise stay unset and the scene stay black. +site res_fixed main 805bd0 360041e8 1400020f # tbz w8, #0, #0x80640c -> b #0x80640c +site res_fixed main 806420 52a7f009 @page:x9 b940f129 # mov w9, #0x3f800000 -> adrp x9, page; ldr w9, [x9, #240] + # Camera aspect ratio: the camera update (main+0x4c48c0, the same code as the camera update site in # recomp/patches-v143.txt) copies the camera's near, far, vertical angle and ratio (camera +0x134..+0x140) into the # sead perspective projection the renderer reads (+0x98..+0xb0; the projection's own matrix update, main+0x3485cc0, does diff --git a/runtime/cfg.c b/runtime/cfg.c index a688e68d..c815a09a 100644 --- a/runtime/cfg.c +++ b/runtime/cfg.c @@ -48,7 +48,7 @@ static const char *const known[] = { "MENU_CURSOR", "MENU_CURSOR_LOG", "MENU_TEST_DIR", "MENU_TEST_SET", "MINIMAL_LAYOUT", "MIRROR_GAP", "MODS_DIR", "MODS_DISABLED", "MODS_ORDER", "MODS_REBUILD", "MOTION", "MOUSE_MODE", "MOUSE_DIRECT_PATCH", "MOUSE_SENS", "MOVIE_HOST", "MOVIE_PACE", "MVOBJ_ALL", "MVOBJ_BANKS", "MVOBJ_EQUAL", "MVOBJ_EXACT", "MVOBJ_EXEC", "MVOBJ_NOC4", "MVOBJ_ONLY", "MVOBJ_TIMES", "MVPREV_CHECK", "MVPREV_GROW", "MVPREV_SKIP", "MVPREV_TRUST", "MV_CHECK", "MV_SURVEY", - "ONE_PER_CORE", "OPERATION_MODE", "OUTPUT_RES", "PAD_LAYOUT", "PACE_HZ", "PASS_CRUMBS", "PATCH_OUTPUT", "PATCH_SCENE", "PATCH_WIDE_ASPECT", "PATCH_WIDE_HUD", "PATCH_WIDE_MOVIE", "PATCH_WIDE_OUT", "PATCH_WIDE_SCENE", "PCSS", "PCSS_K", "PERF_OVERLAY", "PERF_SLOW_MS", "PIPELINE_CACHE", "PIPE_MEMO", "PIPE_PREBUILD", "PIPE_SKIP", "PIPE_SYNC", "PIPE_WAIT", + "ONE_PER_CORE", "OPERATION_MODE", "OUTPUT_RES", "PAD_LAYOUT", "PACE_HZ", "PASS_CRUMBS", "PATCH_OUTPUT", "PATCH_RES_FIXED", "PATCH_SCENE", "PATCH_WIDE_ASPECT", "PATCH_WIDE_HUD", "PATCH_WIDE_MOVIE", "PATCH_WIDE_OUT", "PATCH_WIDE_SCENE", "PCSS", "PCSS_K", "PERF_OVERLAY", "PERF_SLOW_MS", "PIPELINE_CACHE", "PIPE_MEMO", "PIPE_PREBUILD", "PIPE_SKIP", "PIPE_SYNC", "PIPE_WAIT", "PREWARM_WAIT_MS", "PREWARM_XLATE", "PROBE_MOVIE", "PROBE_MOVIE_SHOT", "PROFILE", "PROFILE_CHAINS", "PROFILE_DIR", "PROFILE_FRAMES", "PROFILE_MAIN", "PROFILE_RENDER", "PROMPTS", "PUSH_SKIP", "PUSH_STAGE", "RDOC", "RDOC_DIR", "RDOC_DLL", "REALTIME", "REC_END", "REEXEC", "RENDER_SCALE", "REPLAY_ASYNC", diff --git a/runtime/host.h b/runtime/host.h index b96ae30e..f8f79bab 100644 --- a/runtime/host.h +++ b/runtime/host.h @@ -265,7 +265,8 @@ uint64_t res_heap_extra_kib(void); // added to the HeapD int res_heapdef_build(const void *in, size_t n, void **out, size_t *out_n); // the HeapDef table (.byml.zs) with // the grown heaps, malloc'd; 0 = cannot (logged) void res_heapdef_served(void); // romfs.c serves the grown table -void wide_init(void); // after rt_patches_init: widescreen.c, NX2NAVI_GAME_ASPECT +void wide_init(void); +void wide_present(void); // each present: DLSS's render scale (res_fixed) // after rt_patches_init: widescreen.c, NX2NAVI_GAME_ASPECT int wide_available(void); // the generated C has the widescreen sets void res_init(void); // after rt_patches_init: the patch page and the sets float res_dlss_lod_bias(void); // NX2NAVI_DLSS upscaling: texture LOD bias (<= 0) diff --git a/runtime/nvn_api_obj.c b/runtime/nvn_api_obj.c index df33d597..bb2e3546 100644 --- a/runtime/nvn_api_obj.c +++ b/runtime/nvn_api_obj.c @@ -834,6 +834,7 @@ NVN_API(nvnQueuePresentTexture) { int idx = (int)arg_x(c, 2); movie_tick(c); // ov_movie.c: hand over decoded movie frames that are due; needs the core, so before the wait res_present(c); // resolution.c: a DLSS mode change sets the game's render size again, between frames + wide_present(); // widescreen.c: Ocarina's DLSS render scale NVN_BLOCKING(); if (o->magic != NVN_WIN_MAGIC || idx < 0 || idx >= o->ntex || idx >= 4 || !o->tex[idx]) rt_fatal("nvn: QueuePresentTexture of image %d on window %p", idx, (void *)o); diff --git a/runtime/ui/menu.cpp b/runtime/ui/menu.cpp index 5ba076d7..32e5ced2 100644 --- a/runtime/ui/menu.cpp +++ b/runtime/ui/menu.cpp @@ -724,19 +724,31 @@ static const char *present_note(void) { // ---- the tabs -// One upscaling choice over two settings: NX2NAVI_UPSCALER (how the picture is fitted to the window) and NX2NAVI_DLAA -// (DLSS on). DLSS runs at the game's own render scale (widescreen.c: the scene stays 3/4 of the picture, so DLSS's -// quality modes would all render the same) and fits the picture with bilinear; its model is NVIDIA's own for the -// scale (vk_dlaa.c evaluate, dlss_preset at its default). +// One upscaling choice over three settings: NX2NAVI_UPSCALER (how the picture is fitted to the window), NX2NAVI_DLAA +// (DLSS on) and NX2NAVI_DLSS (its mode, the render scale: widescreen.c wide_present). A DLSS mode fits the picture with +// bilinear, DLSS having made it; the model is NVIDIA's own for the mode (vk_dlaa.c evaluate, dlss_preset at its default). static void row_upscaling(void) { - const char *names[3] = {menu_text(UI_MENU_UPSCALER_BILINEAR), menu_text(UI_MENU_UPSCALER_FSR), "NVIDIA DLSS"}; - int cur = cfg_bool("NX2NAVI_DLAA") ? 2 : strcmp(cfg_value("NX2NAVI_UPSCALER"), "fsr") ? 0 : 1; - int nv = opt_choice_index("NX2NAVI_UPSCALER", UI_MENU_UPSCALING, names, 3, cur, 1, UI_MENU_UPSCALING_DESC, - cur == 2 ? menu_text(UI_MENU_UPSCALING_DLSS_NOTE) : nullptr); + static const char *const modes[] = {"dlaa", "quality", "balanced", "performance", "ultra_performance"}; + static const UiTextId mode_names[] = {UI_MENU_DLSS_DLAA, UI_MENU_DLSS_QUALITY, UI_MENU_DLSS_BALANCED, + UI_MENU_DLSS_PERFORMANCE, UI_MENU_DLSS_ULTRA}; + static char dl[5][96]; + const char *names[7] = {menu_text(UI_MENU_UPSCALER_BILINEAR), menu_text(UI_MENU_UPSCALER_FSR)}; + for (int i = 0; i < 5; i++) { + snprintf(dl[i], sizeof dl[i], i ? "DLSS %s" : "NVIDIA %s", menu_text(mode_names[i])); + names[2 + i] = dl[i]; + } + int cur = strcmp(cfg_value("NX2NAVI_UPSCALER"), "fsr") ? 0 : 1; + if (cfg_bool("NX2NAVI_DLAA")) { + cur = 2; + for (int i = 0; i < 5; i++) + if (!strcmp(cfg_value("NX2NAVI_DLSS"), modes[i])) cur = 2 + i; + } + int nv = opt_choice_index("NX2NAVI_UPSCALER", UI_MENU_UPSCALING, names, 7, cur, 1, UI_MENU_UPSCALING_DESC, + cur >= 2 ? menu_text(UI_MENU_UPSCALING_DLSS_NOTE) : nullptr); if (nv < 0) return; opt_apply("NX2NAVI_UPSCALER", nv == 1 ? "fsr" : "bilinear"); - opt_apply("NX2NAVI_DLAA", nv == 2 ? "1" : "0"); - if (nv == 2) opt_apply("NX2NAVI_DLSS", "dlaa"); + opt_apply("NX2NAVI_DLAA", nv >= 2 ? "1" : "0"); + if (nv >= 2) opt_apply("NX2NAVI_DLSS", modes[nv - 2]); } // The picture's height (widescreen.c picture_height): the game's 1440 or more. diff --git a/runtime/ui_text.c b/runtime/ui_text.c index a12a9174..c5585417 100644 --- a/runtime/ui_text.c +++ b/runtime/ui_text.c @@ -505,11 +505,12 @@ static const char *const text_en[UI_TEXT_COUNT] = { [UI_MENU_UPSCALING] = "Upscaling", [UI_MENU_UPSCALING_DESC] = "How the picture is made smooth and fitted to the window. Bilinear: plain scaling. AMD FSR 1: " "sharper edges and detail when the window is larger than the picture (strength below). " - "NVIDIA DLSS (RTX GPUs; " DLSS_FILE " next to the game's .exe): anti-aliasing of the 3D " - "scene at the game's own render scale, with NVIDIA's newest model for it; the HUD is not " - "touched. DLSS still works on each frame on its own for now, without motion history. " - "Turning DLSS on or off takes effect after a restart.", - [UI_MENU_UPSCALING_DLSS_NOTE] = "DLSS: after restart", + "NVIDIA DLSS (RTX GPUs; " DLSS_FILE " next to the game's .exe) renders the 3D scene at " + "its mode's share of the picture and scales it up with NVIDIA's newest model for the mode: " + "DLAA 100 %, Quality 67 %, Balanced 58 %, Performance 50 %, Ultra Performance 33 %. The " + "HUD is not touched. Turning DLSS on or off takes effect after a restart; the mode changes " + "at once.", + [UI_MENU_UPSCALING_DLSS_NOTE] = "DLSS on or off: after restart", [UI_MENU_GAME_RES] = "Resolution", [UI_MENU_GAME_RES_DESC] = "The height of the game's picture: 1440p is the game's own; higher draws a sharper picture " "and its scene in proportion, at a matching cost on the graphics card. Native: the " diff --git a/runtime/widescreen.c b/runtime/widescreen.c index 9362f435..29078e66 100644 --- a/runtime/widescreen.c +++ b/runtime/widescreen.c @@ -68,15 +68,52 @@ static unsigned picture_height(void) { +// ---- DLSS's render scale (recomp/patches-v111.txt, set res_fixed) +// While DLSS is on, the scene is the DLSS mode's share of the picture (NVIDIA's ratios: DLAA all of it, Quality 2/3, +// Balanced 0.58, Performance 1/2, Ultra Performance 1/3) instead of what the game's dynamic-resolution controller +// picks. The share is written to +240 at every change of the setting (the menu switches the mode live); the set goes on +// RES_FIXED_AFTER presents after the first, once the controller has filled the fields the fixed path keeps. +#define RES_FIXED_AFTER 120 +static double dlss_share(void) { + if (!cfg_bool("NX2NAVI_DLAA")) return 0; + const char *m = cfg_value("NX2NAVI_DLSS"); + return !strcmp(m, "quality") ? 2.0 / 3 : !strcmp(m, "balanced") ? 0.58 : !strcmp(m, "performance") ? 0.5 + : !strcmp(m, "ultra_performance") ? 1.0 / 3 : 1.0; +} + +void wide_present(void) { + static int set = -2; + static uint64_t presents; + static unsigned seen = ~0u; + if (set == -2) set = rt_patch_set("res_fixed"); + if (set < 0) return; + unsigned v = cfg_version(); + if (v != seen) { + seen = v; + float s = (float)dlss_share(); + if (s > 0) memcpy((uint8_t *)rt_patch_page() + 240, &s, 4); + if (s <= 0 && rt_patch_enabled(set)) { + rt_patch_switch(set, 0); + rt_log("widescreen: DLSS off: the game's own render scale again"); + } else if (s > 0 && rt_patch_enabled(set)) { + rt_log("widescreen: DLSS render scale %.0f%% of the picture", s * 100); + } + } + if (++presents == RES_FIXED_AFTER && dlss_share() > 0) { + rt_patch_switch(set, 1); + rt_log("widescreen: DLSS render scale %.0f%% of the picture (the game's dynamic resolution off)", dlss_share() * 100); + } else if (presents > RES_FIXED_AFTER && dlss_share() > 0 && !rt_patch_enabled(set)) { + rt_patch_switch(set, 1); // DLSS switched on later in the run + } +} + void wide_init(void) { int so = rt_patch_set("wide_out"), ss = rt_patch_set("wide_scene"), sa = rt_patch_set("wide_aspect"), sh = rt_patch_set("wide_hud"), sm = rt_patch_set("wide_movie"); - // The scene keeps the game's 3/4 of the picture, a share its renderer applies itself: writing another size into - // wide_scene's constant (main+0x31f3a00) or into the 1920x1080 / 1280x720 render sizes at main+0x108b688 left the - // light target at 3/4. Found so far (for DLSS's quality modes, which need a smaller scene): the scene targets come - // from the view objects' render-target descriptors ({u16 w, u16 h, ...} at +0x3ab0, made by main+0x2507b0 through - // main+0x3ed00), which main+0x6590d0 copies in from a float rectangle at +0x3a10 of another object (width - // [+0x3a18] - [+0x3a10]); where that rectangle's 2562 comes from is still open. + // The scene's share of the picture is not a size in this set: the game's GPU-time dynamic resolution sets it each + // frame (3/4 of the picture at most), and writing other sizes into wide_scene's constant or the 1920x1080 render + // sizes left the light target where the controller put it. DLSS's modes fix the share through res_fixed instead + // (wide_present). double r = wanted_ratio(), game = (double)GAME_W / GAME_H, f = 0.75; unsigned oh = picture_height(); if (r <= 0 || r < game * 1.01) {