Skip to content

perf(core): elide redundant raster clears - #412

Draft
lfkdsk wants to merge 1 commit into
pocket-stack:mainfrom
lfkdsk:perf/core-raster-clear-elision
Draft

perf(core): elide redundant raster clears#412
lfkdsk wants to merge 1 commit into
pocket-stack:mainfrom
lfkdsk:perf/core-raster-clear-elision

Conversation

@lfkdsk

@lfkdsk lfkdsk commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

render_scaled_impl clears the framebuffer to opaque black before every full
render. When the DrawList's first drawing command provably covers the whole target
with an opaque colour, that clear writes 130,560 pixels that the next command
immediately overwrites.

The proof stays deliberately narrow: the first command must be a RECT or
GRAD_RECT whose colour words all carry alpha 255 and whose rectangle covers
the target, optionally preceded by one full-target root SCISSOR. Transparent
first operations, partial or clipped rectangles, transforms and TRIs, empty or
truncated streams and unknown opcodes all keep the clear.

Measured on main

An 8-process microbenchmark moves the eligible path from 18,013 ns to 9,058 ns
per render (−49.7%) and leaves the fallback path at 13,722 → 13,706 ns
(−0.12%). A d1/s1 full-render hero replay goes 84.466 → 73.082 ms (−13.5%). The
wasm grows 606 bytes.

Where it applies, and where it does not

This only helps callers that enter the full raster clear: tests/golden.ts,
tools/tape.ts record/replay/--png, hosts/sim and the launcher screenshots
that consume it, the Vita software rasterizer, the Apple/Symbian ARGB
full-redraw fallback, and the ESP32-PPA RGB565 ordered fallback. PSP draws
glyphs through GE hardware and is unaffected.

It fires on all 180 frames of a full-render replay and zero times on 180
incremental-render frames, so on the already-incremental web path its
attributable benefit is 0 ms / 0%. Stacked with #411 rather than added to it,
the d2/s2 hero replay goes from 260.030 ms (glyph sparse alone) to 212.682 ms
(−18.2%).

Verification

A 522-case matrix compares against clean main byte for byte: viewports
4×3/5×3/7×5/4.9×3.1/480×272/479.7×271.4, scales 1/2/3, ~29 first-command shapes
including negative origins, dimensions at −1/0/+1 of the target, leading
full/oversize/undersize/shifted scissors, gradient directions, semitransparent
and alpha-254 colours, TRI/empty/truncated/unknown opcodes, across RGBA8, ARGB
and RGB565. Every framebuffer is seeded with nonzero bytes so a missing write
cannot hide behind zero initialisation; 216 of the 522 cases take the elision
branch and none leak.

The coverage predicate and the rasterizer's rect fill share the same integer
xy()/wh() helpers, so there is no second rounding rule that could leave a
stale edge pixel; a rectangle one pixel short in any direction keeps the clear.

Suite behaviour matches clean main exactly: the same five unit failures and the
same frame-0 divergence of the committed hero-main tape hashes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant