Grow the render-target heap for the scene's share only where the start's mode needs it, and never past the pool.

The heap grew by the full-picture factor whenever the upscaler setting was on, in every mode, and without a limit:
915 MiB at 3840x2160 where the modes below three quarters need 382, and at 10240x2880 an application pool of 13.5 GiB,
past the 12 GiB heap region, where the game's SetHeapSize is refused and nothing starts.

The share is now the start's own mode: all of the picture for dlaa, the game's own most for the modes below it. The
growth stops at what the pool can take (2,478 MiB): the scene's share is lowered to what fits, with a log line, and a
picture too large even for the game's own scene gets the most there is and a warning. A mode chosen in the menu that
renders more than the start's keeps the start's share until a restart, as the log and nx2navi.ini.example now say.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-12 22:08:44 -05:00
parent cd0b1a387d
commit 7ff1a21ef2
4 changed files with 73 additions and 20 deletions

View File

@@ -518,7 +518,9 @@
# What DLSS works from: dlaa (the 3D scene at full size), quality, balanced, performance or ultra_performance (the
# scene at 67, 58, 50 or 33 % of the output, scaled up by DLSS; these replace render_scale and sharpen texture filtering
# to match). They apply only with dlaa = 1 and the DLSS model next to the executable; otherwise the scene follows
# render_scale. Changes while the game runs take effect at once.
# render_scale. Changes while the game runs take effect at once, with one limit: the game's memory for render targets
# is sized at the start for the mode set then, so dlaa chosen in the menu of a run that started with a smaller mode
# (or with dlaa = 0) renders at 75 % until the next start.
# dlss = dlaa
# AMD FSR in DLSS's place, on any GPU (Windows): with taa = fsr3 and dlaa = 1 the scene is scaled by AMD FSR 3.1 in the
# mode dlss names (dlaa is FSR's Native AA). The menu's Upscaling row sets all of these. Changes while the game runs

View File

@@ -364,6 +364,9 @@ void wide_present(void); // each present: DLS
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)
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.
uint64_t mem_heap_extra_max_kib(void);
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)
int res_dlss_mode(const char *mode); // NX2NAVI_DLSS changed while running: new scene size

View File

@@ -1306,6 +1306,12 @@ static int inside(uint64_t a, uint64_t n, RtRange r) { return a >= r.base && n <
static uint32_t map_gen;
uint32_t mem_map_gen(void) { return __atomic_load_n(&map_gen, __ATOMIC_ACQUIRE); }
// The application pool is the title's own plus res_heap_extra_bytes (mem_init), and the game asks for a heap of
// nearly all of it: past HEAP_CAP the request below is refused, so this is the most that may be added.
uint64_t mem_heap_extra_max_kib(void) {
return HEAP_CAP > DEFAULT_POOL ? ((HEAP_CAP - DEFAULT_POOL) & ~0x1fffffull) >> 10 : 0;
}
// ---- 0x01 SetHeapSize
uint32_t mem_set_heap_size(uint64_t size, uint64_t *addr) {
uint64_t t0 = perf_mark_begin(); // NX2NAVI_TRACE=perf: the commit or decommit, the wait for the lock included

View File

@@ -90,18 +90,24 @@ double wide_dlss_share(void) { return dlss_share(); } // vk_dlaa.c: the mode's
static double heap_share(void);
// The share of the picture the upscaler's mode renders (NX2NAVI_DLSS; NVIDIA's ratios).
static double mode_share(void) {
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;
}
static double dlss_share(void) {
if (!vk_dlaa_wanted() || !cfg_bool("NX2NAVI_DLAA") || (!vk_dlaa_fsr3() && !vk_dlaa_available())) return 0;
const char *m = cfg_value("NX2NAVI_DLSS");
double s = !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;
// Never more than the texture heap was made for at the start (heap_share): only an upscaler switched on in the
// menu of a run that began without one can ask for more, and it gets the whole picture after a restart.
double s = mode_share();
// Never more than the texture heap was made for at the start (heap_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.
if (s > heap_share()) {
static int said;
if (!said++)
rt_log("widescreen: the scene stays at %.0f%% of the picture until a restart: the game's render-target heap "
"was sized at the start, without an upscaler, and %.0f%% does not fit it", heap_share() * 100, s * 100);
rt_log("widescreen: the scene is %.0f%% of the picture, not the mode's %.0f%%: the game's render-target heap "
"was sized at the start for that much. A mode chosen in the menu gets its size after a restart, "
"unless the start's log says the heap is at its limit", heap_share() * 100, s * 100);
s = heap_share();
}
return s;
@@ -121,20 +127,42 @@ static double scene_share(void) {
return s > 0 ? s : plain;
}
static int picture_size(unsigned *ow, unsigned *oh, int quiet);
// The picture's pixels over the game's own 2560x1440.
static double picture_ratio(void) {
unsigned ow, oh;
if (!wide_available() || !picture_size(&ow, &oh, 1)) ow = GAME_W, oh = GAME_H;
return (double)ow * oh / ((double)GAME_W * GAME_H);
}
// 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 whole picture when the upscaler setting is on, whatever its mode, since
// the menu changes the mode in play and DLAA and FSR at native size render all of it; else the game's own most,
// PLAIN_SHARE, or NX2NAVI_SCENE_SHARE when that asks for more. From the settings, not from what runs: DLSS is known
// to run only once the device is up.
// from it before any device exists): the game's own most, PLAIN_SHARE, or more where the settings at the start ask
// for more: NX2NAVI_SCENE_SHARE, or the upscaler's mode when the upscaler is on (DLAA and FSR at native size render
// the whole picture; the modes below PLAIN_SHARE need nothing). From the settings, not from what runs: DLSS is known
// to run only once the device is up. A mode chosen in the menu later that renders more keeps the start's share until
// a restart (dlss_share).
// Never more than the application pool can take: the heap grows with the share's square (wide_heap_extra_kib), and
// past mem_heap_extra_max_kib the game's own SetHeapSize is refused and nothing starts. A 10240x2880 picture has room
// for 80% there, not for all of it, and the scene is then that much.
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;
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;
if (rt_patch_set("res_fixed") >= 0 && cfg_bool("NX2NAVI_DLAA")) 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;
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 "
"heap for more does not fit the application pool (%llu MiB of growth at most)", most * 100, s * 100,
(unsigned long long)(mem_heap_extra_max_kib() >> 10));
s = most;
}
share = s;
}
return share;
@@ -189,15 +217,29 @@ static int picture_size(unsigned *ow, unsigned *oh, int quiet) {
// is all of the picture, 2560x1440 where the game never goes past 1920x1080, and a player's game ended in that same
// function, reading address 0x48, each time a dialogue closed in Castle Town (0.8.0, 1440 lines, dlss = dlaa).
// The heap and its parent grow by the shipped size times the picture's pixels over 2560x1440 and times the scene's
// pixels over the game's most (heap_share: its targets are the picture's size or the scene's), in whole MiB;
// resolution.c rewrites the table (res_heapdef_build) and memory.c adds the amount to the application pool, so no
// other heap shrinks. 0 for the game's picture and scene, and for a larger picture without the widescreen sets.
// pixels over the game's most (heap_share: its targets are the picture's size or the scene's, so this is the most
// they can need), in whole MiB; resolution.c rewrites the table (res_heapdef_build) and memory.c adds the amount to
// the application pool, so no other heap shrinks. 0 for the game's picture and scene, and for a larger picture
// without the widescreen sets. In MiB, with the scene at the game's own most and with all of the picture (DLAA):
// 2560x1440 0 and 238, 3440x1440 105 and 424, 3840x2160 382 and 915, 5120x2160 610 and 1,322, 7680x2160 1,068 and
// 2,135. The heap is committed as the game first writes it (memory.c, NX2NAVI_HEAP_LAZY), so the room a run does not
// use costs address space and no memory.
// At most mem_heap_extra_max_kib: heap_share has taken the scene's share down to what fits, and a picture too large
// even with the game's own scene (above 10240x2880) gets the most there is, with a line that says so.
uint64_t wide_heap_extra_kib(void) {
unsigned ow, oh;
if (!wide_available() || !picture_size(&ow, &oh, 1)) ow = GAME_W, oh = GAME_H;
double k = heap_share() / PLAIN_SHARE;
double grow = (double)ow * oh / ((double)GAME_W * GAME_H) * k * k - 1;
return grow > 0 ? (uint64_t)ceil(GAME_DTA_KIB * grow / 1024) * 1024 : 0;
double grow = picture_ratio() * k * k - 1;
uint64_t kib = grow > 0 ? (uint64_t)ceil(GAME_DTA_KIB * grow / 1024) * 1024 : 0, most = mem_heap_extra_max_kib();
if (kib > most) {
static int said;
if (!said++)
rt_log("widescreen: WARNING: a picture of %.1f times the game's own needs %llu MiB more render-target heap "
"and the application pool has room for %llu: the game may end when a screen needs more targets "
"than fit. A smaller game_res or a narrower game_aspect fits", picture_ratio(),
(unsigned long long)(kib >> 10), (unsigned long long)(most >> 10));
kib = most;
}
return kib;
}
void wide_init(void) {