From b8cf0aa8b6f4aef915f75ae588cfb07e949beb07 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Wed, 7 Oct 2026 00:47:24 -0700 Subject: [PATCH] Keep native redraw VRAM journals bounded with an exact spill backup --- runtime/src/render_pass_plan.c | 14 ++++++++++++-- runtime/tests/test_render_pass_sandbox.c | 14 ++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/runtime/src/render_pass_plan.c b/runtime/src/render_pass_plan.c index 40fd6b14..c8a5e67c 100644 --- a/runtime/src/render_pass_plan.c +++ b/runtime/src/render_pass_plan.c @@ -279,8 +279,18 @@ int render_pass_vram_policy(const RenderPassJournal *j, int px, int py, if (*x >= j->x[i] && *y >= j->y[i] && *x + *w <= j->x[i] + j->w[i] && *y + *h <= j->y[i] + j->h[i]) return RENDER_PASS_VRAM_ALLOW; /* already covered */ - return j->n < RENDER_PASS_JOURNAL_MAX ? RENDER_PASS_VRAM_JOURNAL - : RENDER_PASS_VRAM_REFUSE; + if (j->n >= RENDER_PASS_JOURNAL_MAX) return RENDER_PASS_VRAM_REFUSE; + /* Reserve the last entry for a whole-VRAM spill. A sprite scene can + * upload more than 64 disjoint animation/CLUT rectangles in one redraw. + * Back up the current contents once, including earlier temporary writes; + * reverse-order rollback restores this spill FIRST and then restores + * those earlier rectangles to their original contents. Subsequent writes + * are covered by the spill, so capacity remains bounded without dropping + * legitimate texture uploads or disabling native interpolation. */ + if (j->n == RENDER_PASS_JOURNAL_MAX - 1) { + *x = *y = 0; *w = vram_w; *h = vram_h; + } + return RENDER_PASS_VRAM_JOURNAL; } int render_pass_journal_add(RenderPassJournal *j, const uint16_t *vram, diff --git a/runtime/tests/test_render_pass_sandbox.c b/runtime/tests/test_render_pass_sandbox.c index cf67cfb9..3a029f6a 100644 --- a/runtime/tests/test_render_pass_sandbox.c +++ b/runtime/tests/test_render_pass_sandbox.c @@ -17,8 +17,8 @@ * 3. VRAM journal (render_pass_vram_policy / _journal_add / _rollback, the * CPU side of gpu_gl_renderer.c's out-of-rect journal): writes inside the * pass rect are allowed, writes outside are backed up first and rolled - * back exactly, covered writes are not journaled twice, a full journal or - * a renderer mode that cannot journal refuses. + * back exactly, covered writes are not journaled twice, the last slot spills + * to a whole-VRAM backup, and a mode that cannot journal refuses. * * The GPU, presenter and remaining devices are mocks. Build/run: * ctest -R render_pass_sandbox_test */ @@ -648,10 +648,16 @@ static void test_journal(void) { int k = j.n; CHECK(POLICY(400 + 8 * k, 100, 4, 4, 1) == RENDER_PASS_VRAM_JOURNAL, "fill journal"); render_pass_journal_add(&j, s_vram, VW, x, y, w, h); - paint(x, y, w, h, (uint16_t)(0x8000 + k)); + /* Policy can enlarge the BACKUP, never the actual GPU write. */ + paint(400 + 8 * k, 100, 4, 4, (uint16_t)(0x8000 + k)); } CHECK(j.n == RENDER_PASS_JOURNAL_MAX && n == 5, "journal full"); - CHECK(POLICY(900, 400, 4, 4, 1) == RENDER_PASS_VRAM_REFUSE, "full journal refuses"); + CHECK(j.x[j.n-1] == 0 && j.y[j.n-1] == 0 && + j.w[j.n-1] == VW && j.h[j.n-1] == VH, "last slot protects all VRAM"); + CHECK(POLICY(900, 400, 4, 4, 1) == RENDER_PASS_VRAM_ALLOW, "spill covers later writes"); + paint(900, 400, 4, 4, 0x9999); + CHECK(POLICY(650, 4, 16, 16, 1) == RENDER_PASS_VRAM_ALLOW, "spill permits overlapping rewrites"); + paint(650, 4, 16, 16, 0xAAAA); CHECK(render_pass_journal_add(&j, s_vram, VW, 900, 400, 4, 4) == -1, "add refuses past the limit");