Add Native (100%) to the Upscaling row: the scene drawn at the picture's own size with no upscaler.

The row writes scene_share = 1 with dlaa = 0, which the start already sized the render-target heap for; widescreen.c now
reads the share live, says when the heap needs a restart for it (the row's note and the Restart count), and logs the
scene size, the picture size and how the scene is scaled once the fixed share takes over and at each change. The
stand-in copies a scene of the picture's size texel for texel with a nearest sampler, and the performance overlay shows
Ocarina's real scene size instead of the other builds' placeholders. scene_share gets its settings row and its ini
text; the row's texts are in all four menu languages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 00:42:38 -05:00
parent 9e1d5962c4
commit c6f2484ef9
9 changed files with 216 additions and 41 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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);

View File

@@ -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] = "窗口比例",

View File

@@ -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

View File

@@ -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]},

View File

@@ -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