From 1dbecd464f014c788efc97c1e236d3f8f08fc30b Mon Sep 17 00:00:00 2001 From: epicmeme546 <178368684+epicmeme546@users.noreply.github.com> Date: Fri, 13 Nov 2026 11:34:19 -0500 Subject: [PATCH] Hand an overwritten render target's image on after 8 presents while the card is past its pressure share, and free the ones no texture took after 32. At 3840x2160 the targets moved in 16 presents hold 3.1 GiB, which a 16 GiB card does not have beside the 10 GiB of a town. A mark reaches its entry within three frames of the draw (four read-back copies, one every four submissions), so eight presents without a stamp are enough there. The early drop waits four times that: at ten presents it took the images the reuse was about to hand on (7,301 dropped, 7,672 made, 5,226 reused in one run); at 32 the same run made 3,338 and reused 8,004. The walk from the bug capture at 3840x2160 with the budget taken as 13.2 GiB, a 16 GiB card's: 10.9 to 11.4 GiB in use, at most 1.6 GiB of released targets, no image that could not be made and none in system memory. A player's log from such a card at that size had 12.2 GiB of released targets and 22.2 GiB in use at 2 frames a second. Without the budget, at 3416x1440: 10.0 to 10.7 GiB, as before this change. Co-Authored-By: Claude Fable 5.2 --- docs/releases/v0.10.2.md | 27 ++++++++++++++------------- runtime/vk_texture.c | 22 ++++++++++++++++------ 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/docs/releases/v0.10.2.md b/docs/releases/v0.10.2.md index 3ecb08af..dc1fbe47 100644 --- a/docs/releases/v0.10.2.md +++ b/docs/releases/v0.10.2.md @@ -1,15 +1,16 @@ ## Fixed -- **Video memory no longer fills up while the camera moves** (by epicmeme546). The game lays its render targets out - anew in every frame, one behind the other in one block of memory. One of them, a shadow map, changes its size with - the view (1141x1141 to 1917x1917 in Kokiri Forest, a texel or two a frame), and every target behind it then lies at - a new address in each frame. A moved target should take over the image of the one it replaces, but that image - counted as in use for as long as the game had not recycled the old target's texture object, so every frame got about - a dozen new images and each old one stayed for 120 frames or more: 6.7 GiB of them at 3416x1440 and 12.2 GiB at - 3840x2160, on top of the 10 GiB the area needs. A 20 GiB card ran full twice in five minutes of Kokiri Forest and a - 16 GiB card in Castle Town (images that could not be made or went to system memory, 2 to 4 frames a second, then - the game ended). An image that another target was drawn over now goes to the next target of its size once nothing - has drawn with it or sampled it for 16 frames, and past 80 % of the card's memory such images are freed at once - instead of ten seconds later. Walking 28 metres from a save in Kokiri Forest and then around it at 3416x1440: 15.9 GiB - of video memory in use before, 14 GiB again on each lap; 9.7 to 10.6 GiB now, with at most 2.2 GiB of released - targets where there were 6.9 GiB, and 3,609 images made in the run where there were 6,970. +- **Video memory no longer fills up while the camera moves** (by epicmeme546). The game lays its render targets out anew + in every frame, one behind the other in one block of memory. One of them, a shadow map, changes its size with the view + (1141x1141 to 1917x1917 in Kokiri Forest, a texel or two a frame), and every target behind it then lies at a new + address in each frame. A moved target should take over the image of the one it replaces, but that image counted as in + use for as long as the game had not recycled the old target's texture object, so every frame got about a dozen new + images and each old one stayed for 120 frames or more: 6.7 GiB of them at 3416x1440 and 12.2 GiB at 3840x2160, on top + of the 10 GiB the area needs. A 20 GiB card ran full twice in five minutes of Kokiri Forest (images that could not be + made, 4 frames a second, then the game ended) and a 16 GiB card in Castle Town (images in system memory, 2 frames a + second). An image that another target was drawn over now goes to the next target of its size once nothing has drawn + with it or sampled it for 16 frames, or for 8 once the card is past 80 % of its memory, where such images are also + freed after 32 frames instead of ten seconds. Walking 28 metres from a save in Kokiri Forest and then around it at + 3416x1440: 15.9 GiB of video memory in use before and 14 GiB again on each lap; 9.7 to 10.7 GiB now, with at most 2.2 + GiB of released targets where there were 6.9 GiB, and 3,609 images made in the run where there were 6,970. The same + walk at 3840x2160 with the memory budget of a 16 GiB card: 10.9 to 11.4 GiB, at most 1.6 GiB of them released. diff --git a/runtime/vk_texture.c b/runtime/vk_texture.c index 716b9a8b..989efab7 100644 --- a/runtime/vk_texture.c +++ b/runtime/vk_texture.c @@ -718,6 +718,13 @@ static int marks_tell(void); // The presents a released entry must have gone unused before its image may go to another texture (create_tex): the // pool's marks reach an entry a few presents after the draw that sampled it. #define REUSE_UNUSED 16 +// The same for an overwritten entry while the card is past the VRAM pressure's share of its budget (over_share): what +// the marks need and no more. A mark is read at most MARKS_RB copies of MARKS_EVERY submissions after its draw +// (vk_exec_draw.c vk_exec_pool_marks), three frames of six submissions, so after eight presents without a stamp none +// is on its way. At 3840x2160 the targets moved in 16 presents hold 3.1 GiB, which a 16 GiB card does not have beside +// the 10 GiB of a town. +#define REUSE_UNUSED_FULL 8 +static int over_share; // sweep_pressure: the driver's figure of the memory in use is past that share // The released entries of one parameter set create_tex looks at, newest release first. While the game's layout moves // in every frame, each frame releases every target of a set and none of them is unused yet for REUSE_UNUSED presents: // with 128 looked at, eight targets of one set a frame hid every older entry behind the young ones and each texture @@ -1512,7 +1519,8 @@ static Tex *create_tex(const tex_t *t, const TexKey *k, int defer_init) { if (q->rel_epoch == submit_epoch || now - q->rel_frame < 2) { n_reuse_young++; continue; } int over = q->overwritten && marks; // still drawn with or sampled: its content is asked for - if (over ? sampled_lately(q, now, REUSE_UNUSED) : used_lately(q, now, REUSE_UNUSED)) { + if (over ? sampled_lately(q, now, over_share ? REUSE_UNUSED_FULL : REUSE_UNUSED) + : used_lately(q, now, REUSE_UNUSED)) { n_reuse_used++; continue; } @@ -2705,10 +2713,11 @@ static int sampled_lately(const Tex *e, uint64_t now, uint64_t within) { return // drop makes it again that way, so an entry something still uses stays; so does one the image reuse would take // (create_tex, REUSE_UNUSED). // full: the card is past the VRAM pressure's share of its budget by the driver's own figure (sweep_pressure). The -// wait is then OVERWRITTEN_FULL presents, the time the image reuse leaves an entry alone: an image no texture took in -// that time holds nothing the game can ask for, and the memory it keeps for ten seconds more is what the next image -// fails for. -#define OVERWRITTEN_FULL (REUSE_UNUSED + 2) +// wait is then OVERWRITTEN_FULL presents, four times what the image reuse leaves an entry alone for there: an image no +// texture took in that time holds nothing the game can ask for, and the memory it keeps for ten seconds more is what +// the next image fails for. Not sooner: at ten presents this took the images the reuse was about to hand on, 7,301 in +// a run at 3840x2160 that then made 7,672 images and reused 5,226. +#define OVERWRITTEN_FULL (REUSE_UNUSED_FULL * 4) static uint64_t n_over_full; // since the last perf line: of n_over_drops, dropped with the card that full static void drop_overwritten(int full) { static int used_too = -1; @@ -3365,7 +3374,8 @@ static void sweep_pressure(void) { // tex_lock held // at 100 % of its budget by this figure was at 81 % by the other, 3.1 GiB of it holes, and nothing gave way. uint64_t cap = released_cap_bytes(), keep = cap, vused = 0, vbudget = 0; int vexact, vknown = vk_mem_vram(&vused, &vbudget, &vexact); - drop_overwritten(vknown && vram_pressure && vused > vbudget / 100 * (uint64_t)vram_pressure); + over_share = vknown && vram_pressure && vused > vbudget / 100 * (uint64_t)vram_pressure; + drop_overwritten(over_share); drop_aged(); // the bound, or more of it while the hold empties the released entries within minutes and the card and the // system have room (vk_tex_release.h trl_keep): a young entry dropped there is one the game may still take back