Repository navigation
Responsiveness: fills cut along the scene's tiles in the worker, pencil strokes drawn a piece at a time - #19
Merged
Conversation
…il strokes drawn a piece at a time Fill - The fill worker sends back its result cut along the scene's 512² tiles (Core.fillCells): per tile, the region as merged rects and the soft edge pixels, or a dense tile's pixels. It no longer sends the pixels and the undo data back (800 MB at 100 MP; fillPlan drops `before`). An older page still gets the old reply. - The page only wraps the cells in an asset (cellAsset, assets.js), and builds a tile's paths the first time a tile repaint draws it. Building the paths on the main thread took ~60 ms at 16 MP. A tile repaint draws only the cells it meets, one for a fill that hasn't moved; the 256-row bands spanned the fill's whole width. - Fills over transparency and Transparent fills are paths too. Pixels that aren't opaque are cleared first, then drawn as rgba() rects, which give what putImageData gives. These fills were two box-sized bitmaps, each with a CPU copy. A dense tile is a canvas of at most 1 MB, rebuilt from its pixels after a context loss. - A fill that clears or replaces soft pixels saves its mask in the paNt chunk. Without it, a Transparent fill reopened uncleared. Strokes - Pencil and eraser stamps are one rect per run of a line's row or column (Core.stampRects), with the same pixels. - A live pencil or eraser stroke draws each new piece onto a canvas per tile it is in, then copies that part in. The tile is no longer composed again (base, objects, every earlier piece) each frame. Whole-pixel rects come out the same whatever they are batched with, so the cache still equals the scene drawn fresh. A brush stays one path per tile. Also - The picker reads its pixel through a fresh 1 px GPU canvas (pixelAt). The willReadFrequently one read all of #image back on every click. - The group selection's box is worked out once per selection, not two or three times a frame. - Image assets keep no CPU copy where a 2D context can't be lost (no oncontextlost: Safari). The copy doubled their memory and was read back at idle. Measured with one-off CPU profiles in headless Chromium on a software GPU, at 4000 x 4000: - one fill: main-thread time 80 -> 24 ms, heap growth 130 -> 3 MB; - a 400-move pencil scribble: 715 -> 354 ms, and it no longer grows with the stroke's length. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bjaj6ac9XGsjJWsPrMECjo
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.
Makes the slowest everyday operations cheaper in Chrome and Safari. Fills and long pencil strokes do much less work on the main thread and use far less memory. The full bench was not run (your call). I measured with one-off CPU profiles instead.
Fill
The fill stays an object. Writing it into the base as pixels would mean uploading the whole box from the CPU (hundreds of ms at 100 MP) and keeping undo pixels. Drawing it from paths is cheaper.
Core.fillCells). Each tile holds the region as merged rects plus the soft edge pixels. A dense tile (noise-like masks) holds its pixels instead. The worker no longer returns the pixels and the undo data, which were 800 MB at 100 MP.fillPlanno longer computesbefore. An older page still gets the old reply.cellAsset,assets.js). A cell's paths are built the first time a tile repaint draws it. Before, building the paths on the main thread took ~60 ms at 16 MP. A tile repaint now draws only the cells it meets: one, for a fill that hasn't moved. The old 256-row bands spanned the fill's whole width.rgba()rects. Checked in Chromium:rgba()rects give whatputImageDatagives, 0 of 16,384 translucent values differ.master, a PNG with a Transparent fill reopens with the cleared area white again. The saved bitmap can't tell a cleared pixel from an untouched one. A fill that clears or replaces soft pixels now also saves its mask in thepaNtchunk. Checked: master's reopened picture differs, this branch's is pixel-identical.Strokes
Core.stampRects). The pixels are the same; a unit test checks against stamping pixel by pixel.Also
pixelAt). It used awillReadFrequentlycanvas, which reads all of#imageback on every click (~95 ms at 10000², per CLAUDE.md).oncontextlost. That is Safari: Chrome has it since 99, Firefox since 125. There the copy only doubled their memory and was read back at idle.Measured
One-off CPU profiles in headless Chromium on a software GPU, at 4000 × 4000 (16 MP):
The old stroke cost grew with the stroke's length; the new one doesn't.
Testing
master, including new tests forfillCellsandstampRects.master, from the software GPU: filledge "batches still equal one path", libs "truecolor + alpha" and history "undo of pencil".cdn.playwright.dev,playwright.download.prss.microsoft.com) is blocked by that environment's network policy.Generated by Claude Code