Repository navigation
[energy] GPU: opaque compositing, batch runs, prerender pills - #398
Merged
Merged
Conversation
gregoreesmaa
added a commit
that referenced
this pull request
Oct 3, 2026
…ples never matched fresh renders (PR #398 residue) Root cause: #389 routed 1x headless glyphs through the 2x white-mask blit (downsample 2:1). Incremental drag phases carry a damage clip (CGContextClipToRect over old+new selection union); CoreGraphics resamples masked blits under a clip up to 1 LSB differently than unclipped, so every incremental repaint differed from the fresh render - extend phase and drag-back alike, across all gestures. The Zig/ObjC damage-union logic was already correct (old+new bounds everywhere); only the glyph pixels diverged. Fix: blit_tinted_mask_batched declines below 1.5x scale (clip-parity gate) and platform_draw_text passes rasterize=0 on 1x, restoring the pre-#389 legacy direct path pixel-exactly on 1x. 2x keeps the full GPU win (textured quads + batching + rasterize). Shaping cache still serves measure/hit-test on all scales. Batching (primed fill groups), opaque compositing, pill prerender, font cache untouched. Tests: new blitScaleParityOk unit test pins the 1.5x gate (glyph_cache.zig, 12/12 pass on Windows host). Fuzz oracles untouched. Static screenshots byte-identical by construction (1x path now executes main's legacy pixels verbatim).
gregoreesmaa
added a commit
that referenced
this pull request
Oct 3, 2026
… (PR #398 gestures 5/12/13) Root cause: paint_selection_highlight filled char-range wash rects at raw fractional coords (x1/x2 from CoreText advances, view_y = doc_y - scroll). Fresh renders paint those rects unclipped; incremental drag phases paint them under a CGContextClipToRect damage rect. CoreGraphics blends a translucent (0.32 alpha) fractional-edge fill differently clipped vs unclipped - up to 1 LSB on the AA edge pixels - so extend-phase fringe and drag-back residue survived round 1 on exactly the fractional-coord gestures (5: A=805.3,891.2... / 12: A=311.0,557.7... collapse-to-empty B=0,0,0,0 / 13: A=349.7,621.2...). Gesture 12's collapse takes the same path: its extend phase (A) already differs, B only needs the old-box repaint. Fix: floor/ceil the two wash fills outward once at paint time (char-range fill + inter-word gap bridge), the same rule selection_bounds_expanded already applies to damage boxes - every consumer agrees, clipped and fresh land on identical device pixels. Tests: no Zig touched (macos.m only); oracles untouched; static screenshots take the same path (1x legacy text, wash fills now land on the same integral edges the damage boxes already used).
gregoreesmaa
added a commit
that referenced
this pull request
Oct 4, 2026
…d-fringe residue (PR #398 gestures 5/12/13)
gregoreesmaa
added a commit
that referenced
this pull request
Oct 4, 2026
…stures rendered at scroll 0 (PR #398 gestures 5/12/13)
gregoreesmaa
added a commit
that referenced
this pull request
Oct 4, 2026
…-record trace (PR #398 gestures 5/12/13)
gregoreesmaa
added a commit
that referenced
this pull request
Oct 4, 2026
gregoreesmaa
added a commit
that referenced
this pull request
Oct 4, 2026
- Opaque compositing: ReadView -isOpaque YES, window.opaque=YES, view wantsLayer=NO; single opaque bg fill (no whole-window composite). - Fill-color batching: consecutive same-color glyph runs share one primed SetFillColor/Interpolation (draw_run_batched); legacy fallbacks and pass ends close via platform_batch_end (new FFI, idempotent). - 1x atlas path: white coverage masks tint via ClipToMask+FillRect on every scale (no per-run CTLineCreate/CGColorSpace on 1x); NSFont cache per (style, quarter-point size) kills 2-7 fontWithName: probes per miss. - Prerendered pills: inline-code rounded rects rasterize once per unique (geometry+colors) into an 8-slot cache at EOF (SIZE NOTE honored); copy button damage-culls before NSBezierPath/NSString work and caches its 11pt label metrics. - Overdraw: selection wash damage-gated + per-record culled; link hover invalidates old+new run boxes (was full redraw); help overlay and remote indicator per-command damage-gated with record-only fallback; math blit uses None when snapped 1:1, Default only when downsampling. - Tests: ColorGroup/batchCount model + 3 tests in glyph_cache.zig (11/11 pass); strict benchmarks untouched. Platform-blocked on this Windows host: macOS build, screenshots, size_gate.sh, damage_parity.sh must run on macOS CI/host.
…ples never matched fresh renders (PR #398 residue) Root cause: #389 routed 1x headless glyphs through the 2x white-mask blit (downsample 2:1). Incremental drag phases carry a damage clip (CGContextClipToRect over old+new selection union); CoreGraphics resamples masked blits under a clip up to 1 LSB differently than unclipped, so every incremental repaint differed from the fresh render - extend phase and drag-back alike, across all gestures. The Zig/ObjC damage-union logic was already correct (old+new bounds everywhere); only the glyph pixels diverged. Fix: blit_tinted_mask_batched declines below 1.5x scale (clip-parity gate) and platform_draw_text passes rasterize=0 on 1x, restoring the pre-#389 legacy direct path pixel-exactly on 1x. 2x keeps the full GPU win (textured quads + batching + rasterize). Shaping cache still serves measure/hit-test on all scales. Batching (primed fill groups), opaque compositing, pill prerender, font cache untouched. Tests: new blitScaleParityOk unit test pins the 1.5x gate (glyph_cache.zig, 12/12 pass on Windows host). Fuzz oracles untouched. Static screenshots byte-identical by construction (1x path now executes main's legacy pixels verbatim).
… (PR #398 gestures 5/12/13) Root cause: paint_selection_highlight filled char-range wash rects at raw fractional coords (x1/x2 from CoreText advances, view_y = doc_y - scroll). Fresh renders paint those rects unclipped; incremental drag phases paint them under a CGContextClipToRect damage rect. CoreGraphics blends a translucent (0.32 alpha) fractional-edge fill differently clipped vs unclipped - up to 1 LSB on the AA edge pixels - so extend-phase fringe and drag-back residue survived round 1 on exactly the fractional-coord gestures (5: A=805.3,891.2... / 12: A=311.0,557.7... collapse-to-empty B=0,0,0,0 / 13: A=349.7,621.2...). Gesture 12's collapse takes the same path: its extend phase (A) already differs, B only needs the old-box repaint. Fix: floor/ceil the two wash fills outward once at paint time (char-range fill + inter-word gap bridge), the same rule selection_bounds_expanded already applies to damage boxes - every consumer agrees, clipped and fresh land on identical device pixels. Tests: no Zig touched (macos.m only); oracles untouched; static screenshots take the same path (1x legacy text, wash fills now land on the same integral edges the damage boxes already used).
…d-fringe residue (PR #398 gestures 5/12/13)
…stures rendered at scroll 0 (PR #398 gestures 5/12/13)
…-record trace (PR #398 gestures 5/12/13)
gregoreesmaa
force-pushed
the
energy/389-gpu-compositing
branch
from
October 4, 2026 19:00
ddb815c to
3aa06d4
Compare
gregoreesmaa
added a commit
that referenced
this pull request
Oct 4, 2026
damage_parity cmp says WHICH gesture mismatches, not WHERE. fringe_localize.py (stdlib PNG decode, same pattern as copy_button_damage.sh) prints diff bbox + count + max delta + sample pixels per failing phase so the fringe maps onto --dump-commands text_run rects. Diagnostic only: || true at call sites, strict cmp oracles unchanged. Remove before merge.
gregoreesmaa
added a commit
that referenced
this pull request
Oct 4, 2026
damage_parity cmp says WHICH gesture mismatches, not WHERE. fringe_localize.py (stdlib PNG decode, same pattern as copy_button_damage.sh) prints diff bbox + count + max delta + sample pixels per failing phase so the fringe maps onto --dump-commands text_run rects. Diagnostic only: || true at call sites, strict cmp oracles unchanged. Remove before merge.
gregoreesmaa
force-pushed
the
energy/389-gpu-compositing
branch
from
October 4, 2026 19:05
3aa06d4 to
d2d3f43
Compare
damage_parity cmp says WHICH gesture mismatches, not WHERE. fringe_localize.py (stdlib PNG decode, same pattern as copy_button_damage.sh) prints diff bbox + count + max delta + sample pixels per failing phase so the fringe maps onto --dump-commands text_run rects. Diagnostic only: || true at call sites, strict cmp oracles unchanged. Remove before merge. Also restores font_resolve_uncached dropped by the rebase conflict resolution (linker break caught by CI run 37226572120); the branch EOF duplicate stays deleted.
gregoreesmaa
force-pushed
the
energy/389-gpu-compositing
branch
from
October 4, 2026 19:06
d2d3f43 to
6b9b956
Compare
DRAGDMG stderr lines expose per-phase damage; the sweep replays phase-1 damage on a fresh bitmap via --damage and reports which arm fresh+damage matches (cross-phase state vs damage-path splitter). Diagnostic only: strict cmp oracles unchanged. Remove before merge.
Whole-image fresh+damage cmp proved nothing (outside-damage zeros vs phase-0 content differ by construction). Mask all bisect comparisons to within the phase-1 damage rect; add bisA-vs-fresh path-consistency check; print DRAGDMG lines. Strict oracles unchanged. Remove before merge.
DRAGDMG field scan started at phase= token; start at the x= token. Strict oracles unchanged. Remove before merge.
Dump command stream at failing gesture scrolls (full + under phase damage): lists inline_code_bg rects and damage-culled text runs at the fringe site. Read-only; strict oracles unchanged. Remove before merge.
…/13/18) Root cause: the pill cache holds 2x rasters; on 1x dests the cached blit downsamples (InterpolationQuality None) under the damage clip and never matches a fresh direct render pixel-exactly. Incremental drag phases blit (warm cache) while fresh renders draw direct (cold cache): pill fringe rows differ (ROWPROBE: inline_code_bg exactly at all three fringe bboxes; text runs unculled; bisA-vs-fresh identical proving the damage path consistent). Same clip-parity rule as the 1x text revert (2740b05): gate cache use+fill on g_output_scale >= 1.5. 2x live path unchanged; 1x is now exactly the pre-#389 direct path, so static screenshots stay byte-identical.
The fringe localizer, damage-replay bisect, row-content probe, and DRAGDMG harness prints served their purpose: they identified the pill-cache 1x blit as the residue source and the 1x-direct fix turned the sweep green. Remove all of it; the strict cmp oracles stay as the permanent regression pin.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #389.
What changed (all in worktree, branch energy/389-gpu-compositing):
Evidence: