Skip to content

feat(canvas): replace Canvas 2D with a native recording-backed architecture - #414

Open
BibekPathak wants to merge 11 commits into
lexmount:mainfrom
BibekPathak:canvas_2D_moli
Open

feat(canvas): replace Canvas 2D with a native recording-backed architecture#414
BibekPathak wants to merge 11 commits into
lexmount:mainfrom
BibekPathak:canvas_2D_moli

Conversation

@BibekPathak

Copy link
Copy Markdown
Contributor

Deliver the complete Chromium-style Canvas 2D re-architecture for Moli,
replacing the scattered pixel-ownership model with a native, recording-backed
core in moli-canvas. This is the full M0–M6 migration: baseline, geometry
extraction, surface/backend, single-owner integration, ordered recording,
observation/invalidation, and removal of the old implementation.

Crate architecture

  • moli-canvas becomes the browser-independent native core: drawing state,
    current-path geometry, ordered recording, persistent premultiplied-RGBA8
    surface, region readback, and encoding/export helpers. It depends only on
    AnyRender/Vello-CPU/peniko/kurbo/mol-image; it never touches V8, the DOM,
    layout, or page paint.
  • moli-renderer-v8 remains the browser adapter (WebIDL, JS identity,
    invalidation, page-image publication, lifecycle). moli-paint responsibility
    for page painting is unchanged.

Milestones

  • baseline cost model (per-draw full-plane copy is O(canvas area)) and the
    routing/ownership inventory used as the final-review checklist.
  • native CanvasPath + per-context path state in moli-canvas; renderer
    adapter reduced.
  • CanvasSurface (premultiplied RGBA8, lazy materialization, reusable
    Vello CPU backend, cached straight-alpha snapshot, region readback,
    reset/resize) with native surface/backend tests.
  • a single weak-keyed native surface replaces the mutable V8
    Uint8ClampedArray backing store; GC/isolation reclamation, resize, and all
    115 canvas JS regressions pass.
  • ordered DrawRecording captures every ordinary draw op (path fill/stroke,
    rect fill/stroke, clear, drawImage, text, putImageData) with frozen inputs,
    batching contiguous scene ops into one backend submission while preserving
    order through direct-step segments for clears/blits/writes. Integrated into
    the renderer; dead rasterize/composite/color helpers removed.
  • consistent flush-before-read on all pixel observations, snapshot-cache
    invalidation on every write path, and VisualResourceGeneration bumped on
    every draw record (not just at flush) so screenshots/screencast see current
    content and geometry-only ops neither flush nor invalidate.
  • remove the full-surface unpremultiply/premultiply round-trip from all
    four direct ops by compositing in premultiplied space (O(operation area) not
    O(canvas area)); delete CanvasSurface::with_straight_pixels_mut; verify
    same-size reset; declare STUBs out of scope.

Correctness hardening

  • putImageData now does a true raw overwrite (premultiplied straight-alpha
    replacement) instead of an accidental source-over composite, matching the
    spec even for translucent ImageData over existing content.
  • Add a pending-recording resource budget: after a draw, flush early when
    recorded bytes (including pinned source images) exceed the limit, mirroring
    Chromium's FlushIfRecordingLimitExceeded.

Checks

  • cargo fmt --all, clippy (moli-canvas, moli-renderer-v8) with -D warnings, and
    the moli-canvas native suite (45 tests) pass.

@BibekPathak

Copy link
Copy Markdown
Contributor Author

Hey @ldm0 , look at this!!

@ldm0
ldm0 self-requested a review September 7, 2026 13:04
apply_global_alpha(rgba, context_global_alpha(scope, context))
}

fn apply_global_alpha(rgba: [u8; 4], global_alpha: f64) -> [u8; 4] {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apply globalAlpha only to the alpha channel of this straight-RGBA color. Scaling RGB here causes Vello to premultiply already-darkened channels. A red path with globalAlpha = 0.5 reads back as [128,0,0,128], whereas its straight RGB should remain red, approximately [255,0,0,128]. This affects fill, stroke, and strokeRect. Please add a regression that checks RGB as well as alpha.

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