diff --git a/docs/architecture.md b/docs/architecture.md index 8ca20045..a65fd3bb 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -209,7 +209,9 @@ keeps per-core data without locks and relies on that, so the host models it (`ru - The game's tensor-core upscaler is replaced by a decode-and-scale pass (`vk_tonemap.c`), which enlarges the scene with FSR 1's edge-adaptive filter (`NX2NAVI_STANDIN_FILTER`, `bilinear` for the plain one) unless DLSS or FSR 3.1 scales it; `NX2NAVI_NATIVE_TENSOR=1` runs the game's own network programs instead (`vk_exec_draw.c` `native_tensor`, - off by default). `vk_fsr.c` runs AMD FSR 1 (EASU, RCAS) on the present when the window is larger than the picture. + off by default). Without DLSS or FSR the scene is `scene_share` of the picture (`widescreen.c` `plain_share`, the + game's own 3/4 by default); at 1, the Upscaling row's Native, it is the picture's size and the pass only decodes it, + texel for texel. `vk_fsr.c` runs AMD FSR 1 (EASU, RCAS) on the present when the window is larger than the picture. - Anti-aliasing (`vk_aa.c`, `NX2NAVI_AA`): the game anti-aliases with its own SMAA, three compute passes after the bloom composite (edges, blending weights, composition into the scene target as square-rooted RGB9E5 words). `off`, `fxaa` and `smaa` leave out the first two and replace the composition with `shaders/aa_post.comp` or SMAA at its Ultra preset diff --git a/nx2navi.ini.example b/nx2navi.ini.example index 59bb6c79..1ae72fbe 100644 --- a/nx2navi.ini.example +++ b/nx2navi.ini.example @@ -553,6 +553,14 @@ # is softer and costs a little less GPU time. DLSS and FSR 3.1 (dlaa = 1) scale the scene themselves, so this changes # nothing there, and it does not touch the movies. Changes while the game runs take effect at once. # standin_filter = easu +# The 3D scene's share of the game resolution without DLSS or FSR 3.1 (dlaa = 0), Ocarina of Time: 0.75, the default, +# is the game's own most (1920x1080 in its 2560x1440 picture), which the scaling above enlarges; 1 draws the scene at +# the picture's full size and nothing enlarges it (the menu's Upscaling row calls it Native (100%)); 0 leaves the size +# to the game's dynamic resolution, down to a third of the picture under load. A larger scene needs more of the +# memory the game keeps for its render targets, which is set aside at the start: 1 takes 238 MiB more than 0.75 at +# 2560x1440, 533 MiB more at 3840x2160 and 712 MiB more at 5120x2160, as dlss = dlaa does. Changes while the game runs +# take effect at once up to the share the game started with, a larger one at the next start. +# scene_share = 0.75 # Picture shape when the window's shape differs from the game resolution's (a 16:9 picture in a 21:9 fullscreen # window): fit, the default, keeps the shape, with black bars at the sides or above and below, so a 16:9 game in a wider # window is no longer stretched as it used to be; stretch fills the window. Changes while the game runs take effect at diff --git a/runtime/cfg.c b/runtime/cfg.c index 98ced02d..1c85be90 100644 --- a/runtime/cfg.c +++ b/runtime/cfg.c @@ -105,7 +105,7 @@ static const struct { const char *key, *section; } sections[] = { {"PHYSICS_STEPS", "frame rate"}, {"PICKUP_COUNT", "frame rate"}, {"MOVIE_PTS", "frame rate"}, {"STEP_STEADY", "frame rate"}, {"FULLSCREEN", "display"}, {"WINDOW_SIZE", "display"}, {"PERF_OVERLAY", "display"}, {"VK_PRESENT", "display"}, {"GPU_INDEX", "display"}, {"MONITOR", "display"}, {"OUTPUT_RES", "display"}, {"GAME_ASPECT", "display"}, {"GAME_RES", "display"}, {"RENDER_SCALE", "display"}, {"UPSCALER", "display"}, - {"SHARPNESS", "display"}, {"STANDIN_FILTER", "display"}, {"ASPECT", "display"}, {"FOV", "display"}, {"ANISO", "display"}, + {"SHARPNESS", "display"}, {"STANDIN_FILTER", "display"}, {"SCENE_SHARE", "display"}, {"ASPECT", "display"}, {"FOV", "display"}, {"ANISO", "display"}, {"TEXTURE_DETAIL", "display"}, {"VRAM_USE", "display"}, {"COLOR_PRESET", "display"}, {"COLOR_BRIGHTNESS", "display"}, {"COLOR_CONTRAST", "display"}, {"COLOR_SATURATION", "display"}, @@ -190,6 +190,9 @@ static const CfgMeta metas[] = { {"OUTPUT_RES", "1920x1080", NULL, RESTART}, {"RENDER_SCALE", "game", "game|50|67|75|100|125|150|200", RESTART}, {"UPSCALER", "fsr", "fsr|bilinear", LIVE}, {"SHARPNESS", "80", "0..100", LIVE}, {"STANDIN_FILTER", "easu", "easu|bilinear", LIVE}, // vk_tonemap.c vk_tonemap_record: how the scene is scaled to the picture + // widescreen.c plain_share: the scene's share of the picture without DLSS or FSR; 1 is the Upscaling row's Native, + // 0 the game's dynamic resolution. Live within the render-target heap the start made for it (heap_share). + {"SCENE_SHARE", "0.75", "0..1", LIVE}, {"DLAA", "0", "0|1", LIVE}, {"DLSS", "dlaa", "dlaa|quality|balanced|performance|ultra_performance", LIVE}, {"DLSS_PRESET", "default", "default|k|j", LIVE}, {"DLSS_IDLE_FREE", "1", "0|1|2", RESTART}, {"TAA", "dlss", "dlss|fsr3", LIVE}, // vk_dlaa.c fsr3 {"DLSS_RANGE", "0", "0|1", RESTART}, // vk_dlaa.c range_on: one DLSS feature for the render sizes of a mode diff --git a/runtime/host.h b/runtime/host.h index 33ca3fe3..6b444980 100644 --- a/runtime/host.h +++ b/runtime/host.h @@ -367,6 +367,10 @@ 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 wide_picture(unsigned *w, unsigned *h); // the picture the game draws (its scene is never larger) +int wide_scene_settable(void); // the generated C has res_fixed: the scene's share can be set +double wide_scene_share(void); // the scene's share of the picture now (0: the game's own) +double wide_scene_wanted(void); // ... what the settings ask for, before the heap's limit +int wide_scene_restart(void); // a restart gives the larger scene the settings ask for uint64_t wide_heap_extra_kib(void); // the texture heap's growth for the widescreen picture // The most res_heap_extra_kib may be (memory.c): with more the application pool passes the heap region and the // game's own SetHeapSize is refused. A whole number of 2 MiB. diff --git a/runtime/ui/menu.cpp b/runtime/ui/menu.cpp index 04e6c27b..bae7c379 100644 --- a/runtime/ui/menu.cpp +++ b/runtime/ui/menu.cpp @@ -67,6 +67,11 @@ void vk_queue_unlock(void); void res_sizes(unsigned out[2], unsigned scene[2]); int res_adjustable(void); int wide_available(void); +int wide_scene_settable(void); +void wide_picture(unsigned *w, unsigned *h); +double wide_scene_share(void); +double wide_scene_wanted(void); +int wide_scene_restart(void); const char *const *vk_color_preset(const char *name); int vk_hdr_wanted(void); int vk_hdr_active(void); @@ -939,6 +944,14 @@ static const char *fsr4_label(void) { return s; } +// Native (the Upscaling row): the scene drawn at the picture's own size, which the upscaler's stand-in then only +// decodes 1:1 (vk_tonemap.c), with no DLSS or FSR. It is scene_share = 1 (widescreen.c plain_share) with dlaa = 0; a +// scene_share of 1 or more set by hand in nx2navi.ini reads the same. +static bool share_native(void) { + const char *s = cfg_get("NX2NAVI_SCENE_SHARE"); + return s && *s && atof(s) >= 1; +} + // The Upscaling row's name of what is set now, for the performance overlay. static void upscaling_name(char *out, size_t size) { static const char *const modes[] = {"dlaa", "quality", "balanced", "performance", "ultra_performance"}; @@ -946,7 +959,8 @@ static void upscaling_name(char *out, size_t size) { static const UiTextId fsr3[] = {UI_MENU_FSR3_NATIVE, UI_MENU_DLSS_QUALITY, UI_MENU_DLSS_BALANCED, UI_MENU_DLSS_PERFORMANCE, UI_MENU_DLSS_ULTRA}; // FSR 3.1 set where it cannot load runs nothing (vk_dlaa.c vk_dlaa_fsr3): the game scales the picture as the rest says if (!cfg_bool("NX2NAVI_DLAA") || (!strcmp(cfg_value("NX2NAVI_TAA"), "fsr3") && !vk_ffx_available())) { - if (strcmp(cfg_value("NX2NAVI_UPSCALER"), "fsr")) snprintf(out, size, "%s", menu_text(UI_MENU_UPSCALER_BILINEAR)); + if (share_native() && wide_scene_settable()) snprintf(out, size, "%s", menu_text(UI_MENU_UPSCALING_NATIVE)); + else if (strcmp(cfg_value("NX2NAVI_UPSCALER"), "fsr")) snprintf(out, size, "%s", menu_text(UI_MENU_UPSCALER_BILINEAR)); else snprintf(out, size, menu_text(UI_MENU_OV_FMT_FSR), cfg_int("NX2NAVI_SHARPNESS")); return; } @@ -985,8 +999,9 @@ static void row_upscaling(void) { static const UiTextId fsr_names[] = {UI_MENU_FSR3_NATIVE, UI_MENU_DLSS_QUALITY, UI_MENU_DLSS_BALANCED, UI_MENU_DLSS_PERFORMANCE, UI_MENU_DLSS_ULTRA}; // FSR 4 (vk_ffx4.cpp) is the same setting as FSR 3.1 with NX2NAVI_FSR4 on, and takes the same modes: listed where - // it runs on this device, after FSR 3.1's - enum { BILINEAR, FSR1, DLSS, FSR3, FSR4 }; + // it runs on this device, after FSR 3.1's. Native (share_native) is listed where the scene's share can be set + // (widescreen.c wide_scene_settable), after the two entries that enlarge the game's smaller scene. + enum { BILINEAR, FSR1, NATIVE, DLSS, FSR3, FSR4 }; struct Entry { int kind, mode; }; static char text[15][96]; Entry list[18]; @@ -996,6 +1011,7 @@ static void row_upscaling(void) { list[n] = {kind, mode}; if (kind == BILINEAR) names[n] = menu_text(UI_MENU_UPSCALER_BILINEAR); else if (kind == FSR1) names[n] = menu_text(UI_MENU_UPSCALER_FSR); + else if (kind == NATIVE) names[n] = menu_text(UI_MENU_UPSCALING_NATIVE); else { char *t = text[(kind == FSR4 ? 10 : kind == FSR3 ? 5 : 0) + mode]; if (kind == DLSS) snprintf(t, sizeof text[0], mode ? "DLSS %s" : "NVIDIA %s", menu_text(mode_names[mode])); @@ -1005,7 +1021,7 @@ static void row_upscaling(void) { n++; }; // what is set now - Entry now = {strcmp(cfg_value("NX2NAVI_UPSCALER"), "fsr") ? BILINEAR : FSR1, 0}; + Entry now = {share_native() ? NATIVE : strcmp(cfg_value("NX2NAVI_UPSCALER"), "fsr") ? BILINEAR : FSR1, 0}; if (cfg_bool("NX2NAVI_DLAA")) { now = {strcmp(cfg_value("NX2NAVI_TAA"), "fsr3") ? DLSS : vk_ffx_fsr4() ? FSR4 : FSR3, 0}; // set where it does for (int i = 0; i < 5; i++) // not run: FSR 3.1 does @@ -1014,6 +1030,7 @@ static void row_upscaling(void) { const bool dlss = dlss_offered(), fsr = vk_ffx_available(), fsr4 = fsr && vk_ffx4_available(); add(BILINEAR, 0); add(FSR1, 0); + if (wide_scene_settable() || now.kind == NATIVE) add(NATIVE, 0); for (int i = 0; dlss && i < 5; i++) add(DLSS, i); for (int i = 0; fsr && i < 5; i++) add(FSR3, i); for (int i = 0; fsr4 && i < 5; i++) add(FSR4, i); @@ -1022,10 +1039,21 @@ static void row_upscaling(void) { int cur = 0; for (int i = 0; i < n; i++) if (list[i].kind == now.kind && list[i].mode == now.mode) cur = i; - // FSR 3.1 still set where it cannot load: nothing upscales, so the row says so under its value + // FSR 3.1 still set where it cannot load: nothing upscales, so the row says so under its value. A scene larger than + // the render-target heap the start made (Native, or DLAA chosen in a run that started with a smaller scene) waits + // for a restart; at the heap's limit for this picture it stays smaller, and the note says how large it is + // (widescreen.c wide_scene_restart, heap_share) const char *note = nullptr; + static char in_use[64]; + const double share = wide_scene_settable() ? wide_scene_share() : 0; if (!fsr && now.kind == FSR3) note = menu_text(UI_MENU_FSR3_OFF); - else if (upscaling_restart()) note = menu_text(UI_MENU_AFTER_RESTART); + else if (upscaling_restart() || (share > 0 && wide_scene_restart())) note = menu_text(UI_MENU_AFTER_RESTART); + else if (share > 0 && wide_scene_wanted() > share + 0.001) { + char pct[16]; + snprintf(pct, sizeof pct, "%.0f %%", share * 100); + snprintf(in_use, sizeof in_use, menu_text(UI_MENU_PRESENT_IN_USE), pct); + note = in_use; + } int nv = opt_choice_index("NX2NAVI_UPSCALER", UI_MENU_UPSCALING, names, n, cur, 1, !fsr ? UI_MENU_UPSCALING_DESC_NO_FSR : dlss ? UI_MENU_UPSCALING_DESC : UI_MENU_UPSCALING_DESC_FSR, note); @@ -1034,6 +1062,10 @@ static void row_upscaling(void) { opt_apply("NX2NAVI_UPSCALER", e.kind == FSR1 ? "fsr" : "bilinear"); opt_apply("NX2NAVI_DLAA", e.kind >= DLSS ? "1" : "0"); if (e.kind < DLSS) opt_apply("NX2NAVI_FRAMEGEN", "off"); // it needs DLSS's or FSR 3.1's motion vectors (row_framegen) + // Native is scene_share = 1. Leaving it unsets the key (the game's own share, widescreen.c PLAIN_SHARE); a share of + // the whole picture set by hand beside DLSS or FSR stays while one of their modes is chosen + if (e.kind == NATIVE) opt_apply("NX2NAVI_SCENE_SHARE", "1"); + else if (share_native() && (e.kind < DLSS || now.kind == NATIVE)) opt_apply("NX2NAVI_SCENE_SHARE", ""); if (e.kind >= DLSS) opt_apply("NX2NAVI_DLSS", modes[e.mode]); if (e.kind >= DLSS) opt_apply("NX2NAVI_TAA", e.kind >= FSR3 ? "fsr3" : "dlss"); if (e.kind >= FSR3) opt_apply("NX2NAVI_FSR4", e.kind == FSR4 ? "1" : "0"); @@ -1585,6 +1617,7 @@ static int restart_pending(void) { bool prompts; prompts_note(&prompts); return cfg_pending_count() + upscaling_restart() + framegen_restart() + (vk_hdr_needs_restart() != 0) + + (wide_scene_settable() && wide_scene_share() > 0 && wide_scene_restart() && !upscaling_restart()) + (prompts && !cfg_pending("NX2NAVI_PROMPTS")); } @@ -2053,6 +2086,14 @@ static void perf_tick(uint64_t now, const MenuTarget *t) { static void perf_picture(const MenuTarget *t, PerfPanel *p) { unsigned out[2], scene[2]; res_sizes(out, scene); + // Ocarina's build has no output and scene patch sets, so res_sizes holds the other builds' placeholders there: the + // picture and the scene are widescreen.c's, the scene at the share it writes for the game (wide_present; in the + // first two seconds of a run the game's own dynamic resolution still sizes it) + const double share = !res_adjustable() && wide_scene_settable() ? wide_scene_share() : 0; + if (share > 0) { + wide_picture(&out[0], &out[1]); + scene[0] = (unsigned)lround(out[0] * share), scene[1] = (unsigned)lround(out[1] * share); + } char (*v)[64] = p->picture; snprintf(v[0], sizeof v[0], menu_text(UI_MENU_OV_FMT_SIZE), t->width, t->height); snprintf(v[1], sizeof v[1], menu_text(UI_MENU_OV_FMT_SIZE), (unsigned)t->image_w, (unsigned)t->image_h); diff --git a/runtime/ui_text.c b/runtime/ui_text.c index 71f132b1..6ec1df44 100644 --- a/runtime/ui_text.c +++ b/runtime/ui_text.c @@ -82,6 +82,7 @@ static const char *const text_en[UI_TEXT_COUNT] = { "sharper than bilinear. Smaller window (Ocarina draws 2560 x 1440): the picture is shrunk " "and FSR sharpens it by the strength below.", [UI_MENU_UPSCALER_FSR] = "AMD FSR 1", [UI_MENU_UPSCALER_BILINEAR] = "Bilinear", + [UI_MENU_UPSCALING_NATIVE] = "Native (100%)", [UI_MENU_GAME_ASPECT] = "Widescreen", [UI_MENU_GAME_ASPECT_DESC] = "Widens the view. HUD and menus stay 16:9.", [UI_MENU_GAME_ASPECT_16_9] = "16:9 (the game's)", [UI_MENU_GAME_ASPECT_WINDOW] = "Window shape", @@ -824,8 +825,10 @@ static const char *const text_en[UI_TEXT_COUNT] = { [UI_BOOT_STARTING] = "Starting", [UI_BOOT_SHADERS] = "Shaders: %lld of %lld", [UI_BOOT_PIPELINES] = "Pipelines: %lld of %lld", [UI_BOOT_COMPUTE] = "Compute pipelines: %lld of %lld", [UI_BOOT_DRIVER_CACHE] = "Driver cache: %lld of %lld", [UI_MENU_UPSCALING] = "Upscaling", - [UI_MENU_UPSCALING_DESC] = "How the picture is smoothed and scaled to your window.", - [UI_MENU_UPSCALING_DESC_FSR] = "How the picture is smoothed and scaled to your window.", + [UI_MENU_UPSCALING_DESC] = "How the picture is smoothed and scaled to your window. Native draws the 3D scene at " + "the picture's full size with no upscaler: the sharpest, and the most GPU work.", + [UI_MENU_UPSCALING_DESC_FSR] = "How the picture is smoothed and scaled to your window. Native draws the 3D scene " + "at the picture's full size with no upscaler: the sharpest, and the most GPU work.", [UI_MENU_FRAMEGEN] = "Frame generation", [UI_MENU_FRAMEGEN_DESC] = "Adds generated frames for smoother motion. Needs DLSS or FSR 3.1.", [UI_MENU_FRAMEGEN_OFF] = "Off", @@ -835,8 +838,10 @@ static const char *const text_en[UI_TEXT_COUNT] = { [UI_MENU_FRAMEGEN_NEEDS_FSR] = "choose an FSR 3.1 mode under Upscaling first", [UI_MENU_FRAMEGEN_NOTE_FSR] = "needs FSR 3.1", [UI_MENU_FRAMEGEN_DESC_FSR] = "Adds generated frames for smoother motion. Needs FSR 3.1.", - [UI_MENU_UPSCALING_DESC_NO_FSR] = "How the picture is smoothed and scaled to your window. AMD FSR 3.1 is not offered " - "here: it needs amd_fidelityfx_vk.dll, which cannot be loaded.", + [UI_MENU_UPSCALING_DESC_NO_FSR] = "How the picture is smoothed and scaled to your window. Native draws the 3D scene " + "at the picture's full size with no upscaler: the sharpest, and the most GPU " + "work. AMD FSR 3.1 is not offered here: it needs amd_fidelityfx_vk.dll, which " + "cannot be loaded.", [UI_MENU_FSR3_OFF] = "in use: off, FSR 3.1 is not available", [UI_MENU_FRAMEGEN_DESC_DLSS] = "Adds generated frames for smoother motion. Needs DLSS.", [UI_MENU_FRAMEGEN_NOTE_DLSS] = "needs DLSS", @@ -922,6 +927,17 @@ static const char *const text_ru[UI_TEXT_COUNT] = { "билинейного. Окно меньше (Ocarina рисует 2560 × 1440): картинка уменьшается, и FSR " "повышает её резкость на величину ниже.", [UI_MENU_UPSCALER_FSR] = "AMD FSR 1", [UI_MENU_UPSCALER_BILINEAR] = "Билинейное", + [UI_MENU_UPSCALING_NATIVE] = "Родное (100%)", + [UI_MENU_UPSCALING_DESC] = "Как картинка сглаживается и масштабируется под окно. «Родное» рисует 3D-сцену в " + "полном размере картинки без апскейлера: самая чёткая картинка и самая большая " + "нагрузка на видеокарту.", + [UI_MENU_UPSCALING_DESC_FSR] = "Как картинка сглаживается и масштабируется под окно. «Родное» рисует 3D-сцену в " + "полном размере картинки без апскейлера: самая чёткая картинка и самая большая " + "нагрузка на видеокарту.", + [UI_MENU_UPSCALING_DESC_NO_FSR] = "Как картинка сглаживается и масштабируется под окно. «Родное» рисует 3D-сцену " + "в полном размере картинки без апскейлера: самая чёткая картинка и самая " + "большая нагрузка на видеокарту. AMD FSR 3.1 здесь не предлагается: ему нужен " + "amd_fidelityfx_vk.dll, который не удаётся загрузить.", [UI_MENU_GAME_ASPECT] = "Широкий экран", [UI_MENU_GAME_ASPECT_DESC] = "Расширяет обзор. Интерфейс и меню остаются 16:9.", [UI_MENU_GAME_ASPECT_16_9] = "16:9 (как в игре)", [UI_MENU_GAME_ASPECT_WINDOW] = "По окну", @@ -1635,6 +1651,15 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = { "mais nítidos que o bilinear. Janela menor (Ocarina desenha 2560 x 1440): a imagem é " "reduzida e o FSR aumenta a nitidez com a intensidade abaixo.", [UI_MENU_UPSCALER_FSR] = "AMD FSR 1", [UI_MENU_UPSCALER_BILINEAR] = "Bilinear", + [UI_MENU_UPSCALING_NATIVE] = "Nativo (100%)", + [UI_MENU_UPSCALING_DESC] = "Como a imagem é suavizada e ajustada à janela. Nativo desenha a cena 3D no tamanho " + "total da imagem, sem upscaler: a mais nítida, e a que mais exige da GPU.", + [UI_MENU_UPSCALING_DESC_FSR] = "Como a imagem é suavizada e ajustada à janela. Nativo desenha a cena 3D no " + "tamanho total da imagem, sem upscaler: a mais nítida, e a que mais exige da GPU.", + [UI_MENU_UPSCALING_DESC_NO_FSR] = "Como a imagem é suavizada e ajustada à janela. Nativo desenha a cena 3D no " + "tamanho total da imagem, sem upscaler: a mais nítida, e a que mais exige da " + "GPU. O AMD FSR 3.1 não é oferecido aqui: ele precisa do amd_fidelityfx_vk.dll, " + "que não pode ser carregado.", [UI_MENU_GAME_ASPECT] = "Tela larga", [UI_MENU_GAME_ASPECT_DESC] = "Amplia a visão. A interface e os menus continuam em 16:9.", [UI_MENU_GAME_ASPECT_16_9] = "16:9 (o do jogo)", [UI_MENU_GAME_ASPECT_WINDOW] = "Formato da janela", @@ -2353,6 +2378,14 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = { "窗口更小时(Ocarina 绘制 2560 × 1440):画面被缩小,FSR 按下方强度锐化。", [UI_MENU_UPSCALER_FSR] = "AMD FSR 1", [UI_MENU_UPSCALER_BILINEAR] = "双线性", + [UI_MENU_UPSCALING_NATIVE] = "原生(100%)", + [UI_MENU_UPSCALING_DESC] = "画面如何平滑并缩放到窗口。原生:以画面的完整尺寸绘制 3D 场景,不使用超分辨率:" + "最清晰,GPU 负担也最大。", + [UI_MENU_UPSCALING_DESC_FSR] = "画面如何平滑并缩放到窗口。原生:以画面的完整尺寸绘制 3D 场景,不使用超分辨率:" + "最清晰,GPU 负担也最大。", + [UI_MENU_UPSCALING_DESC_NO_FSR] = "画面如何平滑并缩放到窗口。原生:以画面的完整尺寸绘制 3D 场景,不使用超分辨率:" + "最清晰,GPU 负担也最大。此处不提供 AMD FSR 3.1:它需要 " + "amd_fidelityfx_vk.dll,但无法加载。", [UI_MENU_GAME_ASPECT] = "宽屏", [UI_MENU_GAME_ASPECT_DESC] = "扩大视野。界面和菜单保持 16:9。", [UI_MENU_GAME_ASPECT_16_9] = "16:9(游戏原生)", [UI_MENU_GAME_ASPECT_WINDOW] = "窗口比例", diff --git a/runtime/ui_text.h b/runtime/ui_text.h index 27801fd9..95029dc7 100644 --- a/runtime/ui_text.h +++ b/runtime/ui_text.h @@ -338,6 +338,7 @@ typedef enum { // and on a build where FSR 3.1 does not load (vk_ffx_available): the menu leaves it out there, and these texts say so UI_MENU_UPSCALING_DESC_NO_FSR, UI_MENU_FSR3_OFF, // FSR 3.1 is still set but not loadable: "in use: off" and why + UI_MENU_UPSCALING_NATIVE, // the scene at the picture's own size, no upscaler (ui/menu.cpp share_native) UI_MENU_FRAMEGEN_DESC_DLSS, UI_MENU_FRAMEGEN_NOTE_DLSS, UI_MENU_FRAMEGEN_NEEDS_DLSS, // DLSS only UI_MENU_FRAMEGEN_DESC_NONE, UI_MENU_FRAMEGEN_NEEDS_NONE, // neither runs // DLSS frame generation (ui/menu.cpp row_framegen, row_framegen_mult): the multiplier row, its notes when this GPU diff --git a/runtime/vk_tonemap.c b/runtime/vk_tonemap.c index 697e024a..0ca1b728 100644 --- a/runtime/vk_tonemap.c +++ b/runtime/vk_tonemap.c @@ -24,8 +24,10 @@ // (vk_fsr.c), built here with its SQUARE constant set: it filters the same stored values, which are the perceptual // encoding that filter is made for, and squares the result as the linear pass does, so only how the scaled sample is // taken differs. bilinear keeps the linear pass. The linear pass also stays where nothing is enlarged (the filter only -// enlarges, and at the same size the linear pass copies each texel exactly) and where the caller says another -// upscaler's picture replaces this one (vk_exec_draw.c untranslated_dispatch_fallback: DLSS or FSR 3.1, vk_dlaa.c). +// enlarges) and where the caller says another upscaler's picture replaces this one (vk_exec_draw.c +// untranslated_dispatch_fallback: DLSS or FSR 3.1, vk_dlaa.c). A scene as large as the picture (scene_share = 1, the +// menu's Native: widescreen.c plain_share) is not scaled at all: the linear pass samples it with a nearest-texel +// sampler, so each output texel is its source texel squared, whatever the filtering hardware's sub-texel precision. // The movie's frames do not come through here: the caller blits those (RGBA8) itself. static struct { int tried, ok; @@ -36,8 +38,9 @@ static struct { VkPipeline easu; // scale with the edge-adaptive filter, then square (shaders/fsr_easu.comp) int easu_tried; int filter_logs; // lines tm_filter_log has written - int filter_last; // what its last line named: 1 the linear filter, 2 the edge-adaptive one + int filter_last; // what its last line named: 1 the linear filter, 2 the edge-adaptive one, 3 a copy 1:1 VkSampler sampler; + VkSampler nearest; // the same with nearest-texel filtering: a source of the output's size (copy 1:1) struct { VkImage img; VkFormat f; VkImageView view; } views[16]; int nviews; VkImage out; // the pass's own output (RGBA16F, the target's size); the caller blits it in @@ -101,8 +104,11 @@ static int tm_init(void) { int ok = TM.push && vkCreateDescriptorSetLayout(c->device, &dci, NULL, &TM.dsl) == VK_SUCCESS; VkPipelineLayoutCreateInfo li = {.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &TM.dsl, .pushConstantRangeCount = 1, .pPushConstantRanges = &pr}; + VkSamplerCreateInfo ni = si; + ni.magFilter = ni.minFilter = VK_FILTER_NEAREST; ok = ok && vkCreatePipelineLayout(c->device, &li, NULL, &TM.layout) == VK_SUCCESS && vkCreateSampler(c->device, &si, NULL, &TM.sampler) == VK_SUCCESS && + vkCreateSampler(c->device, &ni, NULL, &TM.nearest) == VK_SUCCESS && vkCreateShaderModule(c->device, &mi, NULL, &m) == VK_SUCCESS; if (ok) { VkComputePipelineCreateInfo ci = {.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, @@ -147,16 +153,19 @@ static VkPipeline tm_easu(void) { // One log line when the filter the pass scales with changes (the setting, DLSS or FSR switched on or off, a scene as // large as the picture), 8 at most: a scene whose size changes from frame to frame can cross the picture's size often. static void tm_filter_log(int easu, int edge, uint32_t sw, uint32_t sh, uint32_t dw, uint32_t dh) { - int now = easu ? 2 : 1; + int same = sw == dw && sh == dh, now = easu ? 2 : same && edge ? 3 : 1; if (TM.filter_last == now || TM.filter_logs >= 8) return; TM.filter_last = now, TM.filter_logs++; if (easu) rt_log("vk_exec: upscaler stand-in scales %ux%u to %ux%u with the edge-adaptive filter " "(NX2NAVI_STANDIN_FILTER=bilinear: the linear one)", sw, sh, dw, dh); + else if (now == 3) + rt_log("vk_exec: upscaler stand-in copies %ux%u 1:1, decoded and not scaled: the scene is the picture's size", sw, + sh); else rt_log("vk_exec: upscaler stand-in scales %ux%u to %ux%u with the linear filter: %s", sw, sh, dw, dh, !edge ? "DLSS or FSR scales the scene and its picture goes over this one" - : dw < sw || dh < sh || (dw == sw && dh == sh) ? "nothing to enlarge" + : dw < sw || dh < sh ? "nothing to enlarge" : strcmp(cfg_value("NX2NAVI_STANDIN_FILTER"), "easu") ? "NX2NAVI_STANDIN_FILTER asks for it" : "the edge-adaptive pass is not there"); } @@ -202,7 +211,9 @@ VkImage vk_tonemap_record(VkCommandBuffer cb, VkImage src, VkFormat sf, uint32_t VkDependencyInfo dep = {.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .memoryBarrierCount = 1, .pMemoryBarriers = &mb}; vkCmdPipelineBarrier2(cb, &dep); vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_COMPUTE, easu ? easu : TM.pipe); - VkDescriptorImageInfo ii[2] = {{TM.sampler, sv, VK_IMAGE_LAYOUT_GENERAL}, {VK_NULL_HANDLE, dv, VK_IMAGE_LAYOUT_GENERAL}}; + // the same size both ways: each output texel takes its own source texel (see the top of the file) + VkSampler smp = !easu && dw == sw && dh == sh ? TM.nearest : TM.sampler; + VkDescriptorImageInfo ii[2] = {{smp, sv, VK_IMAGE_LAYOUT_GENERAL}, {VK_NULL_HANDLE, dv, VK_IMAGE_LAYOUT_GENERAL}}; VkWriteDescriptorSet w[2] = { {.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstBinding = 0, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &ii[0]}, diff --git a/runtime/widescreen.c b/runtime/widescreen.c index 14b95c7c..65aab1e7 100644 --- a/runtime/widescreen.c +++ b/runtime/widescreen.c @@ -79,7 +79,9 @@ static unsigned picture_height(void) { // 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. +// RES_FIXED_AFTER presents after the first, once the controller has filled the fields the fixed path keeps. Without +// DLSS or FSR the share is NX2NAVI_SCENE_SHARE's (plain_share): the game's own most unless set, all of the picture +// for the menu's Native. #define RES_FIXED_AFTER 120 // The share follows the upscaler that runs, not the setting: DLSS starts only after a restart and only where NGX came up // (vk_dlaa_available), and FSR 3.1 only where its library loads (vk_dlaa_fsr3), so a mode picked in the menu, or one @@ -114,8 +116,13 @@ static double mode_share(void) { : !strcmp(m, "ultra_performance") ? 1.0 / 3 : 1.0; } +// DLSS or FSR 3.1 scales the scene in this run: the setting is on and the upscaler came up. +static int upscaler_runs(void) { + return vk_dlaa_wanted() && cfg_bool("NX2NAVI_DLAA") && (vk_dlaa_fsr3() || vk_dlaa_available()); +} + static double dlss_share(void) { - if (!vk_dlaa_wanted() || !cfg_bool("NX2NAVI_DLAA") || (!vk_dlaa_fsr3() && !vk_dlaa_available())) return 0; + if (!upscaler_runs()) return 0; double s = mode_share(); // Never more than the texture heap the game has was made for (fit_share). A mode chosen in the menu that renders // more than the start's did gets its size after a restart; a heap at its limit never holds the whole picture. @@ -132,16 +139,26 @@ static double dlss_share(void) { return s; } +// The share without DLSS or FSR 3.1 (NX2NAVI_SCENE_SHARE, 1 at most): PLAIN_SHARE unset, 1 for the menu's Native +// (ui/menu.cpp row_upscaling: the scene at the picture's size, which the stand-in then only decodes, vk_tonemap.c), +// 0 for the game's dynamic resolution, as with no share at all. Read at every call, so the menu's choice applies at +// once within the heap the start made (fit_share). +static double plain_share(void) { + const char *e = cfg_get("NX2NAVI_SCENE_SHARE"); + double s = e && *e ? atof(e) : PLAIN_SHARE; + return s > 1 ? 1 : s; +} + static double scene_share(void) { - static double plain = -1; - if (plain < 0) { - const char *e = cfg_get("NX2NAVI_SCENE_SHARE"); // 0: the game's dynamic resolution, as with no share at all - plain = e && *e ? atof(e) : PLAIN_SHARE; - } - double s = dlss_share(); + double s = dlss_share(), plain = plain_share(); return s > 0 ? s : plain > fit_share() ? fit_share() : plain; // NX2NAVI_SCENE_SHARE above the heap the game has } +// The share the settings ask for, before the heap's limit: the upscaler's mode while one runs, else plain_share. +static double wanted_share(void) { + return upscaler_runs() ? mode_share() : plain_share(); +} + static int picture_size(unsigned *ow, unsigned *oh, int quiet); // The picture's pixels over the game's own 2560x1440. @@ -151,6 +168,14 @@ static double picture_ratio(void) { return (double)ow * oh / ((double)GAME_W * GAME_H); } +// The most share of the picture the application pool has room for at the picture the settings ask for (the heap +// grows with the share's square, see heap_share). Computed anew at each call, so the menu can tell whether a restart +// would give a larger scene (wide_scene_restart). +static double share_most(void) { + double room = (double)mem_heap_extra_max_kib() / GAME_DTA_KIB + 1; // the heap's most over the shipped one + return sqrt(room / picture_ratio()) * PLAIN_SHARE; +} + // The largest share of the picture the scene can have in this run, which the texture heap is made for // (wide_heap_extra_kib). Decided once, at the first call, which is the start (memory.c sizes the application pool // from it before any device exists): the game's own most, PLAIN_SHARE, or more where the settings at the start ask @@ -164,13 +189,10 @@ static double picture_ratio(void) { static double heap_share(void) { static double share = -1; if (share < 0) { - const char *e = cfg_get("NX2NAVI_SCENE_SHARE"); - double s = e && *e ? atof(e) : PLAIN_SHARE; + double s = plain_share(); if (s < PLAIN_SHARE) s = PLAIN_SHARE; if (rt_patch_set("res_fixed") >= 0 && cfg_bool("NX2NAVI_DLAA") && mode_share() > s) s = mode_share(); - if (s > 1) s = 1; - double room = (double)mem_heap_extra_max_kib() / GAME_DTA_KIB + 1; // the heap's most over the shipped one - double most = sqrt(room / picture_ratio()) * PLAIN_SHARE; + double most = share_most(); if (s > most) { if (most < PLAIN_SHARE) most = PLAIN_SHARE; // wide_heap_extra_kib says what a picture this large lacks rt_log("widescreen: the scene can be %.0f%% of the picture at most, not %.0f%%: the game's render-target " @@ -189,6 +211,36 @@ static double heap_share(void) { // 810-line scene, three quarters of the picture), so there the share goes in as it is: wide_init sets the factor. static double scale_k = 1.0; +// One line for the scene in effect: its size, the picture's and how the scene reaches the picture's size. Written when +// the fixed share first takes over from the game's dynamic resolution and again when the share or the way it is scaled +// changes (a setting changed in the menu), not at every settings change. The scene's size is the share of the +// picture's as the game sizes it (a 50% scene of 5120x2160 is 2560x1080). +static void scene_log(double s, int first) { + static double last_s = -1; + static int last_how = -1; + // 1 DLSS, 2 FSR, 3 not scaled (the stand-in decodes 1:1), 4 the stand-in's edge-adaptive filter, 5 its linear one + int how = dlss_share() > 0 ? (vk_dlaa_fsr3() ? 2 : 1) : s >= 1 ? 3 + : !strcmp(cfg_value("NX2NAVI_STANDIN_FILTER"), "easu") ? 4 : 5; + if (!first && how == last_how && fabs(s - last_s) < 0.001) return; + last_s = s, last_how = how; + static const char *const hows[] = { + "", "DLSS scales it to the picture", "AMD FSR scales it to the picture", + "native: no upscaling, the upscaler's stand-in only decodes it 1:1", + "the upscaler's stand-in enlarges it with the edge-adaptive filter (no DLSS or FSR)", + "the upscaler's stand-in enlarges it with the linear filter (no DLSS or FSR)"}; + unsigned pw, ph; + wide_picture(&pw, &ph); + rt_log("widescreen: scene %ux%u, picture %ux%u (%.0f%% of it): %s%s", (unsigned)lround(pw * s), + (unsigned)lround(ph * s), pw, ph, s * 100, hows[how], first ? " (the game's dynamic resolution off)" : ""); + // The menu's Native, or another share, larger than the heap the start made: say why the scene is smaller + double want = wanted_share(); + if (how >= 3 && want > s + 0.001) + rt_log("widescreen: scene_share asks for %.0f%% of the picture; the game's render-target heap %s", want * 100, + heap_unserved ? "is the shipped one (the start's log says why the grown one was not served)" + : wide_scene_restart() ? "was sized at the start for less: the next start gives it" + : "is at its limit for this picture (the start's log says so)"); +} + void wide_present(void) { static int set = -2; static uint64_t presents; @@ -196,17 +248,37 @@ void wide_present(void) { 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)scene_share(), page_s = (float)(s * scale_k); - if (s > 0) memcpy((uint8_t *)rt_patch_page() + 240, &page_s, 4); - if (s <= 0 && rt_patch_enabled(set)) rt_patch_switch(set, 0); - else if (rt_patch_enabled(set)) rt_log("widescreen: render scale %.0f%% of the picture", s * 100); - } - if (++presents == RES_FIXED_AFTER && scene_share() > 0) { - rt_patch_switch(set, 1); - rt_log("widescreen: render scale %.0f%% of the picture (the game's dynamic resolution off)", scene_share() * 100); + int at = ++presents == RES_FIXED_AFTER, due = presents >= RES_FIXED_AFTER; + if (v == seen && !at) return; + seen = v; + double s = scene_share(); + float page_s = (float)(s * scale_k); + if (s > 0) memcpy((uint8_t *)rt_patch_page() + 240, &page_s, 4); + int on = rt_patch_enabled(set); + if (s <= 0) { + if (on) rt_patch_switch(set, 0); + return; } + // On at RES_FIXED_AFTER, once the controller has filled its fields; later too, after a share of 0 (the game's + // dynamic resolution) gave way to a fixed one + if (!due) return; + if (!on) rt_patch_switch(set, 1); + scene_log(s, !on); +} + +int wide_scene_settable(void) { return rt_patch_set("res_fixed") >= 0; } + +double wide_scene_share(void) { return scene_share(); } + +double wide_scene_wanted(void) { return wanted_share(); } + +// The settings ask for a larger scene than the heap the start made holds, and the next start's heap holds more: the +// share it asks for within what the application pool has room for at the picture the settings ask for (share_most). +// A heap the game was not served (heap_unserved) stays the shipped one at the next start too, as far as is known here. +int wide_scene_restart(void) { + double want = wanted_share(), now = fit_share(), next = share_most(); + if (want > 1) want = 1; + return !heap_unserved && want > now + 0.001 && next > now + 0.001; } // The picture the settings ask for, *ow x *oh; 0 when it is the game's own 2560x1440 (every set stays off). Read at the