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) {