Add a Video memory use row: Low, Balanced or High.

Balanced, the default, is what the earlier commits made the default. High keeps what DLSS, frame generation's
HUD-less picture, FSR 1 and SMAA made after they are switched off, as before them. Low also sets, for the keys the
player leaves unset: released textures keep at most 1 GiB (tex_released_mb), textures give way from 70% of the video
memory budget (tex_vram_pressure) and the shared pipeline list is built at first draw (pipe_seed_boot = 0). Unsampled
live textures are not dropped in any of them: that eviction left the ground see-through for good. The values come
after the device profile and the low-end mode (cfg.c VRAMLOW); the row is in the Screen tab under Texture detail and
takes effect after a restart. The architecture notes describe the new memory report lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-12 23:20:35 -05:00
parent a4310491de
commit ef6efba7db
10 changed files with 92 additions and 6 deletions

View File

@@ -260,6 +260,13 @@ keeps per-core data without locks and relies on that, so the host models it (`ru
the holes of fuller ones, a few per frame and only in frames with time to spare; which blocks is decided from a
table of what lies in them (`vk_tex_compact.h`), and the old image of a moved texture stays until no descriptor
set and no submission can still name it (`vk_texture.c`, "the pass").
- Memory report: beside each `mem: holes` line, `mem: by owner` splits the device-local memory the driver counts into
VMA's allocations by the file that made them (`vk_vma_budget.c`, "owners"; the textures further by the texture
cache's census), the free space inside VMA's blocks and what VMA never allocated (pipelines, descriptor memory, the
swapchain, DLSS and frame generation features); `mem: host` gives the host-visible allocations, the process's
commit and working set and the game's heap; `vk: released textures` how often released textures came back and how
old they were, and how many were made again after a drop (`vk_memory.c` `owner_log`). A figure that only climbs over
a session is a leak. `vram_use` (`cfg.c` `VRAMLOW`) trades these caches for work done again.
- Allocation: Vulkan Memory Allocator. Debugging: Vulkan validation layers, per-pass frame dumps (`NX2NAVI_DUMP`,
`vk_dump.c`), a sampling profiler (`NX2NAVI_PROFILE`, `tools/prof_report.py`) and a flight recorder
(`NX2NAVI_FLIGHT`). RenderDoc cannot capture, because it hides `VK_EXT_external_memory_host` from the application.

View File

@@ -540,6 +540,14 @@
# cpu or astc_bc = 0) and the budget is under 20480 MiB, auto goes one step lower, as they take four times the memory.
# The log says what was picked ("texture detail:"). Read at start.
# texture_detail = auto
# Video memory use (the Screen tab's row): low, balanced or high. balanced, the default, frees what DLSS, frame
# generation's HUD-less picture, FSR 1 and SMAA held once they have been switched off for a while (about 0.9 GiB with
# DLSS off at 5120x2142); high keeps it, so switching them on again makes nothing. low also sets, unless set here:
# tex_released_mb = 1024 (released textures keep at most 1 GiB instead of a quarter of the video memory budget; one
# the game takes back after its drop is made and uploaded again and shows its stand-in for a frame),
# tex_vram_pressure = 70 (textures give way from 70% of the budget) and pipe_seed_boot = 0 (0.3-0.6 GiB of video
# memory and 2-4 GB of commit less; a first visit to an area can miss a few objects for a frame). Read at start.
# vram_use = balanced
# Sun shadow map size: game (the game's own, which changes with the view and drops to about 400 near the ground, so
# leaf shadows look blocky and jump) or a fixed 256, 512, 768, 1024, 1536 or 2048. Above 960 the texture heap grows with
# it. 256 is coarser than the game's own and is for integrated GPUs short of memory bandwidth. Read at start.

View File

@@ -79,7 +79,7 @@ static const char *const known[] = {
"UPLOAD_BATCH_MB", "VRAM_HEAP_LIMIT", "VRAM_RESERVE_MB", "TEX_CLOBBER", "LOW_END_GPU",
"ZSTD_CHECK", "ZSTD_NATIVE",
"BIND_REUSE", "DYN_DELTA", "STG_ARENA", "SUBMIT_BATCH", "SUBMIT_THREAD", "ASTC_GPU_DESWIZZLE", "ASTC_SCRATCH_KEEP", "ASTC_ARENA", "ASTC_BC1",
"TEXUP_LOG_MS", "DYN_RES", "DYN_RES_TARGET", "DYN_RES_STEP", "TEXTURE_DETAIL",
"TEXUP_LOG_MS", "DYN_RES", "DYN_RES_TARGET", "DYN_RES_STEP", "TEXTURE_DETAIL", "VRAM_USE",
"RECORDER_CORE", "RECORDER_PRIO", "REPLAY_TIME", "LOCK_TIME", "POOL_CREATE_BUDGET", "POOL_CREATE_MS", "POOL_FINI_IDLE", "POOL_LOAD_BACKLOG", "POOL_LOAD_MS",
"VMA_BLOCK_MB", "SYNC_WAKE_ALL", "LOG_ASYNC", "DEVICE_LOST_AT", "STAGING_FAIL_AT", "VRAM_BALLAST_MB",
"TEX_COMPACT", "TEX_COMPACT_MIN_MB", "TEX_COMPACT_FRAME_MB", // texture compaction (vk_texture.c); no menu row
@@ -106,7 +106,7 @@ static const struct { const char *key, *section; } sections[] = {
{"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"},
{"TEXTURE_DETAIL", "display"},
{"TEXTURE_DETAIL", "display"}, {"VRAM_USE", "display"},
{"COLOR_PRESET", "display"},
{"COLOR_BRIGHTNESS", "display"}, {"COLOR_CONTRAST", "display"}, {"COLOR_SATURATION", "display"},
{"COLOR_GAMMA", "display"}, {"HDR", "display"}, {"HDR_LIVE", "display"}, {"HDR_PEAK_NITS", "display"}, {"HDR_GAME_NITS", "display"},
@@ -208,6 +208,10 @@ static const CfgMeta metas[] = {
{"ASPECT", "fit", "fit|stretch", LIVE}, {"FOV", "100", "70..140", LIVE},
{"ANISO", "16", "game|2|4|8|16", LIVE}, // vk_texture.c sampler_features, vk_sampler_aniso_live; 16: the game asks 1x or 2x
{"TEXTURE_DETAIL", "auto", "auto|full|reduced|low", RESTART}, // vk_texture.c "texture detail", read at the first texture
// video memory use: low sets the VRAMLOW defaults below (vk_texture.c released_cap_bytes and sweep_pressure,
// vk_pipeline.c seed_boot); high keeps what features switched off hold (cfg_vram_keep: vk_dlaa.c idle_release,
// vk_fsr.c idle_note, vk_aa.c work_idle)
{"VRAM_USE", "balanced", "low|balanced|high", RESTART},
// colour adjustment (vk_color.c); the preset is the menu's, the four values are what the present uses
{"COLOR_PRESET", "default", "default|vivid|clarity|custom", LIVE}, {"COLOR_BRIGHTNESS", "0", "-30..30", LIVE},
{"COLOR_CONTRAST", "100", "70..150", LIVE}, {"COLOR_SATURATION", "100", "50..200", LIVE},
@@ -642,6 +646,33 @@ static const char *lowend_value(const char *key) {
return NULL;
}
// vram_use = low (NX2NAVI_VRAM_USE, the Screen tab's Video memory use row): defaults for the keys the player does not
// set, after the device profile and the low-end mode. Each trades memory for work the game then does again:
// - TEX_RELEASED_MB 1024: released textures keep at most 1 GiB (by default a quarter of the video memory budget, 4 GiB
// on a 32 GB card, 1.9 GiB on an 8 GB one). One the game takes back after its drop is made and uploaded or decoded
// again, and its first frame shows the stand-in (vk_texture.c released_cap_bytes).
// - TEX_VRAM_PRESSURE 70: textures that come back intact give way from 70% of the budget on, not 80% (sweep_pressure).
// - PIPE_SEED_BOOT 0: the shared list's pipelines this install never drew are made at their first draw, not at the
// start, which leaves out 0.3-0.6 GiB of video memory and 2-4 GB of commit; a first visit to an area can miss a few
// objects for a frame or two while they are made (vk_pipeline.c seed_boot).
// Unsampled live textures are not dropped in any of the three (tex_disuse_evict stays 0): that eviction left the
// ground flat and see-through for good (vk_texture.c disuse_evict_on). high keeps what a switched-off feature holds
// (cfg_vram_keep), as before balanced, the default, frees it.
static const struct { const char *key, *value; } VRAMLOW[] = {
{"TEX_RELEASED_MB", "1024"}, {"TEX_VRAM_PRESSURE", "70"}, {"PIPE_SEED_BOOT", "0"},
};
static const char *vram_use_value(const char *key) {
const char *set = raw_get(PREFIX "VRAM_USE");
if (!set || strcmp(set, "low")) return NULL;
for (size_t i = 0; i < sizeof VRAMLOW / sizeof *VRAMLOW; i++)
if (!strcasecmp(VRAMLOW[i].key, key)) return VRAMLOW[i].value;
return NULL;
}
int cfg_vram_keep(void) {
const char *set = cfg_get(PREFIX "VRAM_USE");
return set && !strcmp(set, "high");
}
static int profile_of(void) {
int p = __atomic_load_n(&profile, __ATOMIC_ACQUIRE);
if (p >= 0) return p;
@@ -672,7 +703,8 @@ static const char *cfg_get_now(const char *name) {
if (profile_of())
for (size_t i = 0; i < sizeof DECK / sizeof *DECK; i++)
if (!strcasecmp(DECK[i].key, name + PREFIX_LEN)) return DECK[i].value;
return lowend_value(name + PREFIX_LEN);
const char *low = lowend_value(name + PREFIX_LEN);
return low ? low : vram_use_value(name + PREFIX_LEN);
}
// Whether name's value comes from the player (the environment or nx2navi.ini), not a profile or the table.
@@ -774,6 +806,7 @@ const char *cfg_default(const char *key) {
if (!strcasecmp(DECK[i].key, name)) return DECK[i].value;
const char *low = lowend_value(name);
if (low) return low;
if ((low = vram_use_value(name))) return low;
const CfgMeta *m = cfg_meta(key);
return m ? m->def : NULL;
}

View File

@@ -34,6 +34,8 @@ int cfg_ini_read(const char *path, const char *key, char *out, size_t cap); //
// 1 when the player set name (the environment or nx2navi.ini), 0 when cfg_get gives a device profile's default or NULL.
int cfg_is_set(const char *name);
int cfg_low_end_active(void); // the low-end GPU mode was on when the game started (cfg.c LOWEND)
// vram_use = high: what a feature switched off holds stays for the run (cfg.c VRAMLOW, the comment above it)
int cfg_vram_keep(void);
// The keys of the file's lines that are no settings of this version, lower-case with ", " between them ("" when there
// are none; a list too long to keep ends early), and how many there are. Such a line does nothing: the log says so at
// start and the settings menu shows it (ui/menu.cpp), so that nobody takes one for the cause of what they see.

View File

@@ -766,6 +766,15 @@ static void row_texture_detail(void) {
opt_choice_list("NX2NAVI_TEXTURE_DETAIL", UI_MENU_TEX_DETAIL, vals, names, 4, UI_MENU_TEX_DETAIL_DESC);
}
// Video memory use (cfg.c VRAMLOW, cfg_vram_keep): low sets smaller texture caches and builds the shared pipeline list
// at first use, high keeps what switched-off features made. Read at the start, so the row notes the restart.
static void row_vram_use(void) {
static const char *const vals[] = {"low", "balanced", "high"};
const char *names[] = {menu_text(UI_MENU_VRAM_USE_LOW), menu_text(UI_MENU_VRAM_USE_BALANCED),
menu_text(UI_MENU_VRAM_USE_HIGH)};
opt_choice_list("NX2NAVI_VRAM_USE", UI_MENU_VRAM_USE, vals, names, 3, UI_MENU_VRAM_USE_DESC);
}
static void row_shadow_scale(void) {
static const char *const vals[] = {"1", "2", "3", "4"};
static const char *const names[] = {"960 (1x)", "1920 (2x)", "2880 (3x)", "3840 (4x)"};
@@ -1320,6 +1329,7 @@ static void tab_screen(float scale) {
const char *aniso_names[] = {menu_text(UI_MENU_ANISO_GAME), "2x", "4x", "8x", "16x"};
opt_choice_list("NX2NAVI_ANISO", UI_MENU_ANISO, aniso, aniso_names, 5, UI_MENU_ANISO_DESC);
row_texture_detail();
row_vram_use();
opt_end();
}

View File

@@ -98,6 +98,13 @@ static const char *const text_en[UI_TEXT_COUNT] = {
[UI_MENU_TEX_DETAIL_DESC] = "Sharpness of large surface textures. Reduced and Low leave out their largest versions, so that an area fits the video memory of 6 and 8 GB cards; only surfaces seen up close look softer. Automatic picks by the card's video memory.",
[UI_MENU_TEX_DETAIL_AUTO] = "Automatic: %s",
[UI_MENU_TEX_DETAIL_FULL] = "Full", [UI_MENU_TEX_DETAIL_REDUCED] = "Reduced", [UI_MENU_TEX_DETAIL_LOW] = "Low",
[UI_MENU_VRAM_USE] = "Video memory use",
[UI_MENU_VRAM_USE_DESC] = "How much video memory the game keeps for later. Low keeps fewer textures of places you "
"left and builds some graphics only when first needed: returning to a place can show "
"simpler surfaces for a moment, and a first visit can miss a few objects for a frame. High "
"also keeps what an upscaler or anti-aliasing switched off had made. Takes effect after a "
"restart.",
[UI_MENU_VRAM_USE_LOW] = "Low", [UI_MENU_VRAM_USE_BALANCED] = "Balanced", [UI_MENU_VRAM_USE_HIGH] = "High",
[UI_MENU_LOWEND_INTRO] = "The settings of the low-end GPU mode. Each can be changed here; most take effect after a restart.",
[UI_MENU_LOWEND_RESTART] = "The mode turns on after a restart. Its settings are listed here then.",
[UI_MENU_LOWEND_NOT_FOR_CARD] = "Not for this graphics card: it has mesh shaders and 8 GB of video memory or more. "
@@ -916,6 +923,13 @@ static const char *const text_ru[UI_TEXT_COUNT] = {
[UI_MENU_TEX_DETAIL_DESC] = "Чёткость больших текстур поверхностей. «Сниженная» и «Низкая» не загружают их самые крупные версии, чтобы область помещалась в видеопамять карт на 6 и 8 ГБ; мягче выглядят только поверхности вблизи. «Авто» выбирает по объёму видеопамяти.",
[UI_MENU_TEX_DETAIL_AUTO] = "Авто: %s",
[UI_MENU_TEX_DETAIL_FULL] = "Полная", [UI_MENU_TEX_DETAIL_REDUCED] = "Сниженная", [UI_MENU_TEX_DETAIL_LOW] = "Низкая",
[UI_MENU_VRAM_USE] = "Расход видеопамяти",
[UI_MENU_VRAM_USE_DESC] = "Сколько видеопамяти игра держит про запас. «Низкий» хранит меньше текстур покинутых мест и "
"создаёт часть графики только при первой надобности: при возвращении поверхности могут на "
"миг выглядеть проще, а при первом посещении на кадр могут пропасть несколько объектов. "
"«Высокий» также хранит то, что создали отключённые масштабирование или сглаживание. "
"Действует после перезапуска.",
[UI_MENU_VRAM_USE_LOW] = "Низкий", [UI_MENU_VRAM_USE_BALANCED] = "Сбалансированный", [UI_MENU_VRAM_USE_HIGH] = "Высокий",
[UI_MENU_SHARPNESS] = "Резкость FSR",
[UI_MENU_SHARPNESS_DESC] = "Насколько FSR повышает резкость. 0 отключает.",
[UI_MENU_SHARPNESS_NEEDS_FSR] = "только с AMD FSR 1",
@@ -1887,6 +1901,13 @@ static const char *const text_pt_br[UI_TEXT_COUNT] = {
"da placa.",
[UI_MENU_TEX_DETAIL_AUTO] = "Automático: %s",
[UI_MENU_TEX_DETAIL_FULL] = "Completo", [UI_MENU_TEX_DETAIL_REDUCED] = "Reduzido", [UI_MENU_TEX_DETAIL_LOW] = "Baixo",
[UI_MENU_VRAM_USE] = "Uso da memória de vídeo",
[UI_MENU_VRAM_USE_DESC] = "Quanta memória de vídeo o jogo guarda para depois. Baixo guarda menos texturas dos "
"lugares que você deixou e cria parte dos gráficos só quando são precisos pela primeira "
"vez: ao voltar a um lugar, as superfícies podem parecer mais simples por um instante, e "
"numa primeira visita alguns objetos podem faltar por um quadro. Alto também guarda o que "
"um escalonador ou antisserrilhamento desligado criou. Vale depois de reiniciar.",
[UI_MENU_VRAM_USE_LOW] = "Baixo", [UI_MENU_VRAM_USE_BALANCED] = "Equilibrado", [UI_MENU_VRAM_USE_HIGH] = "Alto",
[UI_MENU_COLOR] = "Cor",
[UI_MENU_COLOR_PRESET] = "Perfil de cor",
[UI_MENU_COLOR_PRESET_DESC] = "Define os quatro valores abaixo. Vívido: mais contraste e saturação, sombras um "
@@ -2719,6 +2740,10 @@ static const char *const text_zh_hans[UI_TEXT_COUNT] = {
[UI_MENU_TEX_DETAIL_DESC] = "大型表面纹理的清晰度。“降低”和“低”不加载它们最大的版本,使一个区域能放进 6 GB 和 8 GB 显卡的显存;只有近处的表面看起来略微柔和。“自动”根据显卡的显存选择。",
[UI_MENU_TEX_DETAIL_AUTO] = "自动:%s",
[UI_MENU_TEX_DETAIL_FULL] = "完整", [UI_MENU_TEX_DETAIL_REDUCED] = "降低", [UI_MENU_TEX_DETAIL_LOW] = "低",
[UI_MENU_VRAM_USE] = "显存占用",
[UI_MENU_VRAM_USE_DESC] = "游戏为之后保留多少显存。“低”保留较少已离开地点的纹理,部分图形在首次需要时才创建:回到某地时表面可能"
"短暂显得较简单,首次到访时可能有几个物体缺失一帧。“高”还会保留已关闭的放大或抗锯齿创建的内容。重启后生效。",
[UI_MENU_VRAM_USE_LOW] = "低", [UI_MENU_VRAM_USE_BALANCED] = "平衡", [UI_MENU_VRAM_USE_HIGH] = "高",
[UI_MENU_HDR_GROUP] = "HDR",
[UI_MENU_HDR] = "HDR 输出",
[UI_MENU_HDR_DESC] = "在 HDR 显示器上呈现更亮的高光。请先在 Windows 中开启 HDR。",

View File

@@ -50,6 +50,7 @@ typedef enum {
UI_MENU_TEX_DETAIL, UI_MENU_TEX_DETAIL_DESC,
UI_MENU_TEX_DETAIL_AUTO, // "Automatic: %s", %s the level it picks on this card
UI_MENU_TEX_DETAIL_FULL, UI_MENU_TEX_DETAIL_REDUCED, UI_MENU_TEX_DETAIL_LOW,
UI_MENU_VRAM_USE, UI_MENU_VRAM_USE_DESC, UI_MENU_VRAM_USE_LOW, UI_MENU_VRAM_USE_BALANCED, UI_MENU_VRAM_USE_HIGH,
UI_MENU_SHARPNESS, UI_MENU_SHARPNESS_DESC, UI_MENU_SHARPNESS_NEEDS_FSR,
UI_MENU_COLOR, // group heading over the colour rows
UI_MENU_COLOR_PRESET, UI_MENU_COLOR_PRESET_DESC, UI_MENU_COLOR_DEFAULT, UI_MENU_COLOR_VIVID, UI_MENU_COLOR_CLARITY,

View File

@@ -149,7 +149,7 @@ static void img_free(Img *m);
// long pause or loading screen with smaa set also counts as unused, and the images are made again after it.
#define WORK_IDLE 3600
static void work_idle(uint64_t frame) {
if (!P.luma.img || frame - P.smaa_frame < WORK_IDLE) return;
if (!P.luma.img || frame - P.smaa_frame < WORK_IDLE || cfg_vram_keep()) return; // vram_use = high keeps them
rt_log("aa: SMAA's work images unused for %d frames: freed (%ux%u)", WORK_IDLE, P.w, P.h);
img_free(&P.luma), img_free(&P.edges), img_free(&P.weights);
P.w = P.h = 0;

View File

@@ -2707,7 +2707,7 @@ int vk_dlaa_resized_take(void) {
#define IDLE_FREE_PRESENTS 600
static uint64_t off_at; // D.presents when DLSS went off
static void idle_release(void) {
if (!idle_free()) return;
if (!idle_free() || cfg_vram_keep()) return; // vram_use = high keeps them
if (D.hl && D.presents - D.hl_at >= IDLE_FREE_PRESENTS && !D.hl_fresh) {
rt_log("dlaa: the HUD-less picture for frame generation went unused for %d presents: freed", IDLE_FREE_PRESENTS);
hl_destroy();

View File

@@ -51,7 +51,7 @@ static struct {
#define IDLE_FREE 600
static void free_images(VkCtx *c);
static void idle_note(unsigned slot) {
if (!F.img[0]) return;
if (!F.img[0] || cfg_vram_keep()) return; // vram_use = high keeps them
F.since |= 1u << (slot % SLOTS);
if (++F.idle < IDLE_FREE || (F.since & F.used_slots) != F.used_slots) return;
rt_log("vk: FSR's images unused for %u presents: freed (%ux%u)", F.idle, F.w, F.h);