Skip to content

fix(renderer): smooth zoomed drawing and erasing without the memory cost#39

Merged
markm39 merged 3 commits into
mainfrom
fix/zoom-tool-performance
Jul 15, 2026
Merged

fix(renderer): smooth zoomed drawing and erasing without the memory cost#39
markm39 merged 3 commits into
mainfrom
fix/zoom-tool-performance

Conversation

@markm39

@markm39 markm39 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Closes #36 (laggy zoomed drawing/erasing, highlighter segment seams).

  • Highlighter/marker render as a single constant-width stroked centerline, removing the visible spline-segment seams at high zoom and the per-segment draw cost. Live preview, stroke commit, and tile rendering share one rule (isCenterlineStrokedToolType). Trade-off: self-overlapping highlighter strokes no longer darken at self-intersections.
  • Pixel eraser defers stroke metadata, the undo delta, visibility-cache refresh, and tile invalidation to pen-up. During the drag it only applies a kClear sweep (1x) or clips the pending erase circles out of the stroke layer (zoomed), so the real background shows through on every paper type.
  • Zoomed pen/crayon/calligraphy preview anchors a fixed-size surface (engine dimensions, ~14 MB constant) to the visible viewport instead of allocating canvas x zoom pixels (up to ~330 MB at 5x, with jetsam risk and silent ink loss on allocation failure). A consumer that sets renderScale without viewport ratios degrades to the identity mapping. Strokes finished while zoomed are re-rendered from vector data at 1x.
  • Cleanups: Stroke::hasVisiblePoints() is the single visibility check; zoom-range constants hoisted to DrawingTypes.h; dead code removed (recordPixelEraseCircleAdded, appendPixelEraseCircleToDelta, drawCalligraphyPathTail, lastHalfWidth_, the constant useIncrementalActiveSurface flag).

Verification: typecheck, jest (57/57), native serialization smoke test, library build, and clang++ -fsyntax-only on all 17 C++ translation units pass; independent code review and security audit found no issues. On-device verification was not performed (no local iOS dev signing set up); merged with owner's approval on that basis.

markm39 added 3 commits May 24, 2026 15:39
- keep the active-stroke surface at engine dimensions and anchor it to
  the visible viewport when zoomed, instead of allocating logical-size
  times scale pixels (up to 25x canvas memory at 5x zoom, allocated
  mid-gesture)
- re-render finished strokes from vector data when zoomed instead of
  nearest-neighbor downsampling the magnified preview onto the 1x
  stroke surface
- invalidate only the erased region's stroke tiles after a pixel-erase
  gesture instead of clearing the whole tile cache
- refresh cachedHasVisiblePoints when committing a pixel-erase drag so
  fully-erased strokes stop responding to selection
- cache the pixel-eraser cutout path instead of rebuilding it from every
  pending circle on each frame
- remove dead recordPixelEraseCircleAdded/appendPixelEraseCircleToDelta
  and unreachable centerline preview branches
Simplify pass over the zoom performance changes:

- replace the pixel-eraser background cutout with a kDifference clip of
  the stroke layer, so every background type shows through without
  re-deriving background compositing policy (fourth copy removed)
- invalidate cachedStrokeSnapshot_ at the mutation site in
  applyPixelEraserAt instead of special-casing the eraser drag in render()
- name the highlighter/marker rule once (isCenterlineStrokedToolType) and
  share one drawCenterlineStrokePath helper between stroke rendering and
  the active preview
- merge finishStroke's duplicated render-from-geometry branches
- hoist kMinimumRenderScale/kMaximumRenderScale and the identity-scale
  threshold into DrawingTypes.h, shared by the engine and
  ActiveStrokeRenderer
- promote the stroke visibility check to Stroke::hasVisiblePoints and use
  it from erase, deserialize, and drop the dead DrawingSelection copy
- derive erase bounds from the pending eraser path, precompute per-circle
  bounds once, and use an O(1) stroke outset at pen-up
- reset pending pixel-erase state through one helper; make
  applyPixelEraserAt void and use applyPendingPixelEraseToStrokes' return
- wrap active-surface transforms in SkAutoCanvasRestore; drop the
  constant lastHalfWidth_ member and orphaned drawCalligraphyPathTail
@markm39 markm39 merged commit 94630ed into main Jul 15, 2026
1 check 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.

[Bug]: Lag when zoomed in

1 participant