Skip to content

Fill: a tile draws only the cells it overlaps, not the ones right of and below it too - #20

Merged
pathanin merged 2 commits into
masterfrom
claude/vibrant-brahmagupta-d1o5m1
Oct 20, 2026
Merged

pathanin merged 2 commits into
masterfrom
claude/vibrant-brahmagupta-d1o5m1

Conversation

@pathanin

@pathanin pathanin commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to pathanin/paint#19, found while measuring it in WebKit.

The bug

drawCells (assets.js) found the fill cells a tile meets with an inclusive far edge. A tile that ends exactly where a cell starts therefore drew that cell too. So a fill that hadn't moved drew up to four cells per tile instead of one.

  • Pixels were unaffected: the extra cells are clipped away.
  • The work was up to 4×. The new objects.js section 32b measures it: repainting a 2048 × 1024 fill (8 tiles) called fill() 21 times before this fix, and 8 after.

The fix makes the far edges exclusive. A tile that partly overlaps a cell, as with a moved or scaled fill, still draws it.

A/B for PR 19 + this fix

Pre-PR master (2315e42) against after, at 4000², on a software GPU. Run in-page with the same script in Playwright's Chromium and in WebKitGTK 2.52's MiniBrowser. MiniBrowser came from Ubuntu's webkit2gtk-driver, run under Xvfb and driven over WebDriver, because Playwright's WebKit download is blocked in that environment.

Pencil scribble (300 frames, 4 moves each), main-thread ms per frame:

Before: first 50 → last 50 After: first 50 → last 50
Chromium 0.97 → 2.30 0.93 → 0.79
WebKit 5.4 → 27.2, worst 66 3.7 → 3.5, worst 11

Chromium's wall time for the scribble dropped from 99 s to 37 s.

Background fill around 12 rings, medians of 3 runs:

Before After
Chromium: total 1909 ms 1348 ms
Chromium: longest frame gap 611 ms 236 ms
Chromium: heap growth 131 MB 3 MB
WebKit: total 1035 ms 920 ms
WebKit: pause when the fill lands 144–172 ms 80–94 ms

Before this fix, PR 19 alone measured 1023 ms total in WebKit.

Picker: about 1–2 ms in both engines before and after, when the GPU is idle.

WebKit functional pass (WebKitGTK 2.52)

These all check out:

  • Fills: Transparent, soft-edged over white, soft-edged over transparency, and dense (noise). All are paths except the noise fill's one dense cell, which is pixels.
  • Pencil, brush and eraser strokes.
  • The cache equals a fresh render.
  • Undo and redo are pixel-exact through every step.
  • Save and reopen brings the objects back.

Save and reopen in WebKit, compared with the picture before saving:

Pre-PR master After
Differing pixels 385,387 (the Transparent fill came back uncleared, the bug PR 19 fixed) 3,757
Largest channel difference 255 63

All 3,757 are translucent pixels, changed by WebKit's premultiplied-alpha rounding. That rounding predates this work and also shows on master; Chrome's round trip is exact. task.md lists it as open.

Testing

  • Unit tests: 87 pass.
  • e2e, locally in Playwright's Chromium on a software GPU: objects 200/0, including the new 32b; smoke 313/0; alpha 105/0.
  • Same failures as master: libs and filledge each fail one check, identically on master (2315e42) here, and both passed in CI on the merged PR 19.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bjaj6ac9XGsjJWsPrMECjo

…and below it too

drawCells found a tile's cells with an inclusive far edge, so a tile that ends
exactly where a cell starts drew that cell as well: a fill that hadn't moved drew
up to four cells per tile instead of one (21 fills for 8 tiles in objects.js's
new section 32b). The pixels were right, since the extra cells are clipped away;
only the work was up to 4x. The far edges are exclusive now.

task.md records the A/B measured after PR 19, in Chromium and in WebKitGTK 2.52,
pre-PR master vs after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bjaj6ac9XGsjJWsPrMECjo
@pathanin
pathanin marked this pull request as ready for review October 20, 2026 21:05
@pathanin
pathanin merged commit 99fddc2 into master Oct 20, 2026
3 checks passed
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.

2 participants