Repository navigation
Ocarina of Time (Switch 2): GPU-written texture ranges fix the terrain holes; opt-in texture release saves ~2.3 GB; a page on playing it - #1447
Conversation
…ssed texture the CPU wrote is taken from its storage unless the GPU wrote that storage: Ocarina of Time's terrain no longer has holes Symptom: at Kokiri Forest's pond, low ground with no model over it was missing; the sky showed through and the clover and pebbles floated over it. Cause: tex_cpu_content stopped taking an uncompressed texture's storage at the first GPU write anywhere in its pool (pool_t.gpu_written). The game keeps its TerrainGround patches (260x260 R32F heights, RG8 / RGBA8 maps; CPU-written, never flushed) in one ~200 MB CPU_CACHED | GPU_CACHED pool that also holds textures it renders into, so every patch became a GPU texture of zeros and a flat plane at its base height under the real ground. Fix: mtl_tex_gpu_written records the storage range of each texture the GPU renders, clears, copies or downsamples into, or writes as an image (a sorted set of merged ranges, dropped with the pool at finalize); tex_cpu_content and cpu_unc_drop test the texture's own range. DM_NVN_GPUW_RANGE=0 restores the per-pool rule. Checked: texraw of the heightmaps reads 0.13-0.18 instead of 0, the holes are gone in a same-binary A/B at six cameras, memory unchanged; Mario Kart World's terrain pool is never GPU-written, so it takes the same path as before. t_cpu_storage covers a GPU write elsewhere in the pool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the GPU has it, and a texture made again over that storage copies the one released there: Ocarina of Time keeps about 2.3 GB less Symptom: Ocarina of Time on a 24 GB Mac was stopped for memory pressure at gameplay entry. About 3.1 GB of guest memory was texture storage, 2.8 GB of it dirty or compressed, beside the private Metal textures that hold the same content: 1.8 GB in CPU-visible pools whose textures are never uploaded again, 1.3-2.2 GB in CPU_NO_ACCESS pools of render and depth targets. Fix (opt-in, off by default): once an upload batch has completed on the GPU, rel_run replaces the whole pages inside the storage with zero pages (nvn_dirty_discard, as guest_release does). DM_TEX_RELEASE=1: CPU_NO_ACCESS pools, and GPU-only textures made there with no fill; 2: also tracked CPU-visible uploads, only pages still write-protected since the upload, kept protected so a later write faults and re-uploads. Left alone: virtual pools, multisample textures, pools in the residency set, a pool finalized before its batch completes, and guest memory kept in files (iPhone). Releasing showed a second bug: the game registers new texture objects over boot-time storage without writing it again (Link's hat embroidery masks, BC4 512x512), and their upload read zeros. DM_TEX_REL_DONOR (on with the release) makes such a texture a GPU copy of the one released there when the description matches, its host texture is alive and the storage was not written since; a miss is a [RED] line with its reason. DM_TEX_RELEASE=probe counts without releasing; restop prints the totals. Measured: ~2.3 GB given back within 10 s of gameplay; with it and the session's other memory changes, walking runs of 260 s survived (min available 3.3 GB) where 4 of 4 had been killed at entry. Hat embroidery green again, masks' non-zero bytes as with the release off. t_dirty_discard covers the page rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…these changes (a page for the game's note once its profile lands) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks. The terrain-holes fix in the first commit is #1390 (open since Nov 5; the same seven call sites, ranges by address, played on a 16 GB M4: the path, grass and Link's shadow are back). Could you try #1390 on your pond save? If it does the same, one of the two should go. The texture release overlaps #1386's |
…ack the guest copy of uploaded textures (opt-in); a page on playing Ocarina of Time. Commit 1 (GPU-written ranges) is already here from seedds#1390 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rina of Time, with the reasons Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qteK29T18U8p1c9LsVY8g
|
Thank you for the work and testing. Main fixed the terrain holes another way (tex_flush_upload and tex_dirty_upload in the profile, landed on main in b1d7c70 and 6121fd7). I'm leaving this open because the opt-in texture release and its memory saving aren't covered. Please rebase it and say what it still saves on current main. Final checks on the rebased tree (full shader build, walks, regression) are still running, so please report anything you see. |
… of uploaded textures) over seedds#1453 and seedds#1455 pool_heap_texture's fill takes seedds#1455's again argument (0: the placement is new). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013SzykpcKm2eZPvPjGPKx1C
…red pool buffers no longer pile up) over seedds#1447, seedds#1453 and seedds#1455 alias_let_go also clears seedds#1455's lookup stamps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013SzykpcKm2eZPvPjGPKx1C
…n Kokiri Forest, about 4 GB less memory) beside this tree's own work Kept from this tree: the growing raw pool table with two-word hints (plus upstream's read-before-OR on its write log), seedds#1439's held count, seedds#1436's diagnostics, seedds#1447's texture release, seedds#1435, the idle evictions, the GPU watchdog, range-tracked GPU writes, the packaged-texture cache (g_pkg_alias), the synchronous task / mesh pipeline build with prewarm, this tree's F2IP. Taken from upstream: the pool tables (only what shaders read is made, within a budget; pool draws count as depth readers only when one was seen reading, replacing seedds#1430's rule; table reuse), sub_cut's busy rule, the indirect task / mesh draw through mdx, the empty mesh fragment, the larger GPU-write table (65,536 slots), linear textures by rows, the atomic alias on any 32-bit format, its eventfds (with this tree's select and fcntl on them), its prepo Send and audio update lock, its shader split and Ocarina profile and tables. Duplicates both sides added were removed (SDK 22 answers, F2IP, test stubs); RED_ONCE 153 split; t_pool_table (the replaced pool table code) removed; tests adapted to the taken rules. Translator rebuilt; chain 695a9393d125. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Reconcile upstream PRs (seedds#1455, seedds#1447, seedds#1394, seedds#1439, seedds#1489, seedds#1590, seedds#1663) and commits (ffebfd5, e5f1ade) against local fork - Confirm PRs seedds#1455, seedds#1447, seedds#1394, and seedds#1439 are already implemented in ad99858 - Establish that host-only renderer optimizations require zero shader rebuilds and reuse the 42,854-entry b-dc815e0f38ca.img library - Establish that native subword stores (ffebfd5) affect only 515 shaders and leverage cas.db compilation caching - Rank candidates for base M3 Normal + Handheld (>=40 FPS target, ideal 60 FPS) and identify the top three candidates - Recommend the smallest independently verifiable first optimization (host-only PSO archive epoch separation from PR seedds#1590) - Update documentation index, plan, changelog, and known issues
|
On #1641, ktbunch reports Hyrule Castle rendering pink at level 2, fixed by A read-only review of this PR's code found two more things. I reviewed it as I carry it on main
|
|
|
…h guest in files), DM_NVN_TEX_PURGE purgeable idle textures
|
Two things about
Measured with mode 1 and the fix: Ocarina of Time (
So 100 to 276 MiB less here, well under the 2.3 GB of the 24 GB Mac in the commit message; the build measured already carries other memory changes. Together with |
Two host fixes found while bringing up The Legend of Zelda: Ocarina of Time (Switch 2, 0400486024DEC000) on the Mac, plus a page on what it takes to play it. The texture release builds on the GPU-written ranges, so they come as one pull request in that order; each commit stands on its own and is switchable.
GPU-written texture storage is kept as address ranges: Ocarina of Time's terrain no longer has holes
Branch
oot-host-perf-memory, commit 1 of 3 (GPU-written texture storage is kept as address ranges ...). Independent of every open PR; offmain(c83c48f).What and why
Symptom. At Kokiri Forest's pond in The Legend of Zelda: Ocarina of Time (Switch 2, 0400486024DEC000), low ground with no model over it was missing: the sky showed through, and clover and pebbles floated over the holes (read at first as a flat turquoise "water" patch).
Cause.
tex_cpu_contenttakes an uncompressed texture the CPU wrote from its storage only while its pool has had no GPU write (pool_t.gpu_written, from #1109's rule for Mario Kart World's terrain). Ocarina of Time keeps its TerrainGround patches (260x260 R32F heights, RG8 / RGBA8 and layered RGBA8 maps; CPU-written, never flushed) in one ~200 MBCPU_CACHED | GPU_CACHEDpool (flags 0x24) that also holds textures it renders into. The first GPU write anywhere in that pool turned every patch into a GPU texture of zeros, so each patch was a flat plane at its base height under the real ground; where no model covers it, the depth prepass stayed 1 and the sky was drawn.Fix (every game).
mtl_tex_gpu_written(tex)replacesmtl_pool_gpu_written(pool)at the seven call sites (render targets, texture clears, copy / downsample destinations, image stores). It records the texture's storage range in a sorted set of merged[lo, hi)ranges (dropped with the pool at finalize,cpu_tex_drop_pool);tex_cpu_contentandcpu_unc_droptest the texture's own range instead of the pool flag. The pool flag is still set, soDM_NVN_GPUW_RANGE=0restores the old rule exactly.Measurements
DM_NVN_GPUW_RANGE=0vs default), user save at the pond, 6 cameras: holes gone, the real pond still renders water.[RED], no GPU errors; memory equal (footprint max 10.3 vs ~10.6 GB).Switches
DM_NVN_GPUW_RANGE(default 1; 0 = per-pool rule). Log line:GPU-written texture storage tracked by address range.Risks
Tests
make -C dm/gfx/tests GAME=010015100B514000 check_t_cpu_storage: 38 checks, 0 failed. The test now usesmtl_tex_gpu_writtenand adds "a GPU write elsewhere in its pool leaves its storage the content".t_cpu_tex,t_feedback,t_direct_uploadpass; Mac host builds for the three CI profiles; tool tests (test_docs,test_env_switches) pass.scripts/regress.py check --games(needs local game files; maintainer or a contributor with MKW / BotW / TotK should run it — those three share the CPU-written storage path).Docs
docs/reference/nvn-objects.md: new entry "Uncompressed storage the CPU wrote, next to storage the GPU writes";docs/agents/symptoms.mdregenerated;docs/env_switches.tsvrow. No game note yet: Ocarina of Time's note comes with #1299 (a "Found on this game" line should be added there).DM_TEX_RELEASE gives back the guest copy of an uploaded texture, and a texture made again over that storage copies the one released there
Branch
oot-host-perf-memory, commit 2 of 3 (DM_TEX_RELEASE gives back the guest copy ...). Offmain; touchesnvn_mtl.mnear commit 1's hunks only by context (it can be reviewed and taken on its own; if taken first, commit 1 rebases trivially). Opt-in: nothing changes unlessDM_TEX_RELEASEis set.What and why
Symptom. Ocarina of Time on a 24 GB Mac was stopped for memory pressure as gameplay started. Of its guest memory, ~3.1 GB is texture storage, ~2.8 GB of it dirty or compressed, sitting beside the private Metal textures that hold the same content: 1.8 GB in CPU-visible pools whose textures are never uploaded again ("re-uploads on pool content change: 0"), 1.3-2.2 GB in
CPU_NO_ACCESSpools (render and depth targets whose storage the host never reads).Fix. Once the upload batch that read a texture has completed on the GPU (
up_flushfeedback →rel_run), the whole pages inside its storage become zero pages (nvn_dirty_discard, new innvn_dirty.c: an anonymousMAP_FIXEDmapping, the methodguest_releaseuses):DM_TEX_RELEASE=1: textures inCPU_NO_ACCESSpools, and GPU-only textures made there with no initial fill;DM_TEX_RELEASE=2: also CPU-visible textures uploaded by the tracked path, only pages still write-protected since the upload; they stay protected, so a later CPU write still faults and re-uploads;DM_TEX_RELEASE=probe: counts what it would release.Left alone: virtual / sparse pools, multisample textures, a range a residency-set pool covers (its no-copy buffer keeps the old pages wired), entries of a pool finalized before the batch completes (
rel_pool_gonefrommtl_pool_del), a failed batch, and guest memory kept in files (the iPhone layout of #1315: an anonymous mapping would be charged to the footprint again; newdm_guest_in_files()inhle_svc.c, weak default 0 for tests).Second bug the release exposed (DM_TEX_REL_DONOR, on with the release). The game registers new texture objects over boot-time storage without writing it again (Link's hat embroidery: two BC4 512x512 masks, registered again at gameplay entry), so their first upload read the released zero pages and the embroidery rendered black.
rel_pushremembers per storage address the last tracked texture queued for release;pkg_upload_of a texture over released pages looks it up and, when the description matches, the host texture is alive, the storage was not written since its upload (nvn_dirty_since_span) and the Metal shape matches, makes the new texture a GPUcopyFromTextureof it (encoded inup_flushafter the buffer copies; sources held until the batch completes). A miss logs[RED] texture made again over released storage ... : <reason>; an all-zero re-read logs[RED] texture made again over released storage reads only zeros(relpg_check, with=2).Measurements (Ocarina of Time, 24 GB M5 Pro Mac, guarded 190-260 s runs)
guest copy releaseline).DM_TEX_RELEASE=0); 8+ donor copies at entry; no new[RED], no GPU error.Switches
DM_TEX_RELEASE=0(default) /1/2/probe; recommended for Ocarina of Time:2.DM_TEX_RELEASE_METHOD=free(diagnostic):madvise(MADV_FREE_REUSABLE)instead of fresh zero pages.DM_TEX_REL_DONOR(default 1; 0 = off).Risks
DM_NVN_TEX_DEDUPE, restore-on-fault, open) is the lossless alternative for CPU-visible pools; it cannot coverCPU_NO_ACCESSpools, which this does. The two could coexist (=1here + dedupe there), or this PR can be reduced to=1if the maintainer prefers.g_reldon(donor table) keeps one small entry per released address for the run; it is not dropped at pool finalize (the donor check re-validates the host texture and the storage's write state, so a stale entry can only miss).Tests
dm/gfx/tests/t_dirty_discard.c(added to the CI CPU list): partial pages kept, written-since page kept, discarded pages read zero and stay protected (a later write is seen and lands), untracked memory only withtracked = 0. 11 checks pass.t_dirty_slots,t_cpu_storage,t_cpu_tex,t_direct_uploadpass; host builds for the three CI profiles; Python tool tests pass with the tool venv (test_nsp_scanneedsdotneton PATH);t_symbols/t_nrofail to link onmaintoo.Docs
docs/reference/memory.md: new entry "The guest copy of uploaded textures given back"; symptom index regenerated; three rows indocs/env_switches.tsv.Docs
docs/games/ocarina-of-time.md: what a player needs (their own dump as a.nup, no keys), the open pull requests it depends on (#1299 for the profile, #1405 for the raw-load path), build steps, recommended settings, known issues and measurements on an M5 Pro. Once #1299 lands it can move into the game's note.🤖 Generated with Claude Code