Skip to content

Add opt-in Skwasm multi-surface rasterizer#188628

Draft
MarlonJD wants to merge 3 commits into
flutter:masterfrom
MarlonJD:skwasm-safari-retained-presentation
Draft

Add opt-in Skwasm multi-surface rasterizer#188628
MarlonJD wants to merge 3 commits into
flutter:masterfrom
MarlonJD:skwasm-safari-retained-presentation

Conversation

@MarlonJD

@MarlonJD MarlonJD commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Related issue: #187660

Related PRs: #188614 (Skwasm artifact-variant test support), #188623 (cross-browser macrobench runner)

Motivation

The Safari/Firefox problem is not only that Skwasm is currently unsupported by policy. On
non-Chrome browsers, the existing full-frame ImageBitmap presentation path can be expensive and
brittle. This PR does not change the default renderer policy: the production gate
defaultWasmSupport = { blink: true, gecko: false, webkit: false }
(lib/web_ui/flutter_js/src/browser_environment.js, consumed at flutter_js/src/loader.js:82)
keeps non-Chromium on CanvasKit by default and is left untouched. This PR adds a measurable
opt-in direct/multi-surface presentation path so browser behavior can be validated separately
from any policy decision.

What Changed

  • Added a skwasmForceMultiSurfaceRasterizer JS config / environment flag.
  • Kept Skwasm on OffscreenCanvasRasterizer by default.
  • Added capability-gated MultiSurfaceRasterizer selection for Skwasm when the flag is enabled.
  • Added an onscreen SkwasmSurface.
  • Uses transferred OffscreenCanvas for multi-threaded Skwasm when HTMLCanvasElement.transferControlToOffscreen() is available.
  • Uses direct HTMLCanvasElement for single-threaded Skwasm.
  • Falls back to the existing OffscreenCanvas/ImageBitmap path when the opt-in path is not safe.
  • Added a captureImageBitmaps parameter to the native render path so onscreen/direct presentation does not capture unnecessary ImageBitmaps.

Static correctness (source-grounded, re-verifiable)

Verified read-only against the PR's multi-surface diff (unchanged by the master merge):

  • No UA sniffing — selection is feature-detected via browserSupportsTransferControlToOffscreen (HTMLCanvasElement.prototype.transferControlToOffscreen != null), never navigator.userAgent.
  • skwasmForceMultiSurfaceRasterizer defaults to false (bool.fromEnvironment('FLUTTER_WEB_SKWASM_FORCE_MULTI_SURFACE_RASTERIZER')).
  • Default behavior unchanged — with the flag off, every path resolves to the existing OffscreenCanvasRasterizer on Chrome, Firefox, and Safari.
  • Multi-threaded opt-in selects MultiSurfaceRasterizer only when transferControlToOffscreen exists, and hands skwasm a transferred OffscreenCanvas — never a non-transferable HTMLCanvasElement.
  • Single-threaded opt-in uses a direct HTMLCanvasElement.
  • Fallback intact — flag-off and capability-missing both fall back to the existing OffscreenCanvas/ImageBitmap path.

7/7 opt-in invariants pass in source.

Non-goals

  • Does not enable Skwasm by default on Safari or Firefox.
  • Does not force Safari or Firefox into single-threaded Skwasm.
  • Does not implement a full retained/tiled dirty-region cache.
  • Does not make a browser policy decision.

Evidence — re-verifiable local benchmark (2026-06-28)

Status remains candidate-only; default enablement remains NOT_READY. The numbers below are
freshly re-collected and re-verifiable (real GPU, this machine), superseding earlier
exploratory runs whose raw artifacts were lost.

Method. A complete same-engine web SDK (CanvasKit + skwasm + skwasm_heavy) already
existed in the local build output, so no engine rebuild was needed — it was served as-is. A
small harness serves the pre-built macrobenchmarks bundle with COOP/COEP (so skwasm_heavy gets
crossOriginIsolated / SharedArrayBuffer), forces the renderer via the loader config, drives
headful Chrome / Firefox / Safari (isolated temp profiles, real GPU), and collects the
app-reported drawFrameDuration.average. No DevTools tracing — Chrome-only trace metrics are
intentionally excluded. Every run records the actual artifact loaded (artifact proof).

3-pass mean drawFrameDuration.average (µs, lower is better):

Browser CanvasKit (card / lazy / wrap) Skwasm (card / lazy / wrap) artifact Skwasm speedup
Chrome 1908 / 3294 / 1324 1132 / 2262 / 689 skwasm 1.5–1.9×
Firefox 1756 / 2763 / 752 1280 / 1872 / 588 skwasm_heavy 1.3–1.5×
Safari 1423 / 2188 / 1020 631 / 1247 / 398 skwasm_heavy 1.8–2.6×
  • Skwasm beats CanvasKit on every browser and every benchmark, across all 3 passes (the direction never flips).
  • Safari shows the largest win (skwasm_heavy ~1.8–2.6×). This comes from the existing OffscreenCanvas/ImageBitmap path + the skwasm_heavy artifact — confirmed by artifact proof — not from the multi-surface code this PR adds. The loader's auto heuristic already picks skwasm_heavy for non-Chromium (no Chromium break iterators).
  • Run-to-run variance lives mostly in the CanvasKit baseline (e.g. Firefox bench_card ranged 1611–2025 across passes); Skwasm paths are stable (Safari skwasm bench_card ≈0.6% spread).
  • Controlled normal-vs-heavy A/B: forcing normal Skwasm (artifact proof confirms skwasm.wasm loaded) crashes on both Safari and FirefoxUnimplementedError: v8BreakIterator is not supported during layout (normal Skwasm depends on Chromium-only Intl.v8BreakIterator). skwasm_heavy bundles its own break-iterator data and runs. So on non-Chromium browsers skwasm_heavy is required for correctness, not merely faster — which is exactly what the loader's auto heuristic already selects.

Important boundaries:

  • These numbers do not change default policy. The production gate (defaultWasmSupport gecko/webkit false) is untouched.
  • Absolute numbers are machine-specific; the ratios / direction are the portable signal.
  • The opt-in multi-surface path is still not ready: Chrome, Safari, and Firefox hit InvalidStateError when resizing an HTMLCanvasElement after transferControlToOffscreen(). Classified capability exists but behavior insufficientnot a WebKit bug.

Verification

  • Live PR head: 1f6c738f1fadc6a41157b778f8060d4662cb5f84 (master-merge resolving the conflict; multi-surface diff unchanged).
  • git diff --check clean; the Static-correctness section above is re-runnable read-only against the PR diff.
  • Benchmark: reconstructed harness over the pre-built macrobenchmarks bundle + the existing same-engine SDK; headful Chrome 149 / Firefox / Safari (safaridriver); isolated temp profiles; 3 passes; artifact proof every run; drawFrameDuration.average only.
  • Browser hygiene after automation: no test-owned browser / driver / harness processes left running.

Next Plan

  • The opt-in multi-surface resize InvalidStateError is pinned to surface.dart setSize() -> _canvasProvider.resizeCanvas(_canvas, size): on the transferred-canvas path, _canvas is the HTMLCanvasElement whose control was already moved via transferControlToOffscreen(), so resizing its backing store throws. Fix direction: when _useTransferredCanvas, apply only CSS size and leave backing-store sizing to surfaceSetSize(...); add a unit test with a fake that throws on the width/height setter. Keep behind the opt-in/debug flag.
  • Keep browser policy / default enablement blocked until the opt-in path is behaviorally safe and the test-support pieces ([web] Add Skwasm artifact variant test support #188614, Support cross-browser web macrobenchmark runs #188623) can stand on their own.

Source-fix update (2026-06-30)

The resize issue called out in the previous Next Plan is now addressed in this PR by:

  • adding OnscreenCanvasProvider.resizeCanvasCss(...), which updates only the visible CSS size;
  • using that CSS-only resize path for transferred onscreen Skwasm canvases, leaving backing-store sizing to surfaceSetSize(...);
  • adding test/engine/compositing/canvas_provider_test.dart coverage for both full backing-store resize and CSS-only resize at the evidence-sized 1206 x 2142 @ DPR 3 case.

New commit on this PR head:

  • 2edd50a01813cdb588d219b71eb0baf7b567f569fix(web): keep transferred skwasm canvas CSS size current

Additional local validation for this source-fix commit:

  • dart format ... for the three changed files.
  • felt test --gcs-prod --browser chrome --compiler dart2js --renderer canvaskit --fail-early test/engine/compositing/canvas_provider_test.dart: chrome-dart2js-canvaskit-engine, 2/2, All tests passed!
  • felt test --gcs-prod --browser chrome --compiler dart2js --renderer canvaskit --fail-early test/engine/compositing/render_canvas_test.dart: chrome-dart2js-canvaskit-engine, 2/2, All tests passed!

MobileSafari evidence remains local and narrow: the source-fix candidate reached final connected canvas 402 x 714 with backing attrs 1206 x 2142 in one Simulator benchmark and two physical iPhone Skwasm benchmarks, plus a physical iPhone CanvasKit control for the same candidate bundle. This does not change the default WebKit/Safari policy; default enablement remains out of scope for this PR.

@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team labels Jun 26, 2026
Resolve conflict in skwasm_impl/surface.dart: keep the PR's generalized CanvasProvider<DomEventTarget> / DomEventTarget canvas types for the onscreen SkwasmSurface, and incorporate master's new supportsPngEncoding => false override (now required by the Surface interface).
@MarlonJD
MarlonJD force-pushed the skwasm-safari-retained-presentation branch from 3c7da4b to 1f6c738 Compare June 27, 2026 16:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant