Skip to content

[web] Fix multi-view sizing race condition (Lock approach) - #185622

Closed
mdebbar wants to merge 6 commits into
flutter:masterfrom
mdebbar:multiview_lock
Closed

mdebbar wants to merge 6 commits into
flutter:masterfrom
mdebbar:multiview_lock

Conversation

@mdebbar

@mdebbar mdebbar commented Apr 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR fixes a regression in multi-view sizing (#185034) where canvases in multiple views would fail to match their host dimensions. The root cause was the shared OffscreenSurface in OffscreenCanvasRasterizer. Because rendering is asynchronous, View B could resize the shared surface while View A was still in the middle of a draw call, leading to View A inheriting View B's dimensions.

Design

I introduced a synchronization mechanism (synchronized block) in OffscreenCanvasRasterizer to ensure that only one ViewRasterizer can use the shared surface at a time. This guarantees rendering correctness while maintaining the efficiency of a single WebGL context.

Pros:

  • Minimal memory overhead.
  • Avoids hitting browser-imposed WebGL context limits.
  • Safest fix for the regression across all usage scales.

Testing

  • Added engine/src/flutter/lib/web_ui/test/ui/multi_view_sizing_test.dart which renders two differently sized views in the same event loop turn and verifies their canvas dimensions.

Fixes #185034

@mdebbar mdebbar added the CICD Run CI/CD label Apr 27, 2026
@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. platform-web Web applications specifically labels Apr 27, 2026
@github-actions github-actions Bot added team-web Owned by Web platform team and removed CICD Run CI/CD labels Jun 15, 2026
@mdebbar mdebbar added the CICD Run CI/CD label Jun 15, 2026
@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 16, 2026
@mdebbar mdebbar added the CICD Run CI/CD label Jun 16, 2026
@mdebbar
mdebbar marked this pull request as ready for review June 16, 2026 18:02

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a synchronization mechanism in OffscreenCanvasRasterizer to prevent concurrent access to the shared offscreenSurface across multiple views, and adds a test to verify canvas sizing. The review feedback points out a race condition where synchronizing only the draw method is insufficient because the asynchronous prepareToDraw method, which resizes the shared surface, remains unsynchronized. To resolve this, the reviewer suggests synchronizing the entire rasterize method instead to ensure atomic execution of both preparation and drawing.

@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 25, 2026
@mdebbar mdebbar added the CICD Run CI/CD label Jun 25, 2026
@github-actions github-actions Bot removed the CICD Run CI/CD label Jun 25, 2026
@mdebbar mdebbar added CICD Run CI/CD and removed CICD Run CI/CD labels Jun 25, 2026
@yjbanov

yjbanov commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

This looks like a reasonable quick solution. However, I wonder if we have an unnecessary levels of concurrency happening. Animation frame scheduling happens on the main page. At the end of the frame we know all views that need to be redrawn. Can we draw by sending a batch of draw commands to be rendered as a single unit. The renderer (current thread if in ST mode, or worker) would render the whole batch serially, sizing the OffscreenCanvas accordingly.

/// that we only render one view at a time.
Future<void> _lock = Future<void>.value();

Future<T> _synchronized<T>(Future<T> Function() computation) async {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what happens if we have 3 concurrent draw calls? It seems like the first one will go ahead, but the second two will wait on the same oldLock... Maybe we need a queue here?

group('MultiView Sizing', () {
setUpUnitTests(withImplicitView: true);

test('canvases for multiple views have correct sizes', () async {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please add a test with 3 views

@mdebbar

mdebbar commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #194401

@mdebbar mdebbar closed this Oct 16, 2026
LouiseHsu pushed a commit to LouiseHsu/flutter that referenced this pull request Oct 19, 2026
…lutter#194401)

Alternative to flutter#185622.

flutter#179473 split rendering on the
shared `OffscreenSurface` into two separately awaited steps: `setSize()`
and then `rasterizeToImageBitmaps()`. When multiple views render at the
same time, one view can resize the surface between another view's resize
and render, so that view ends up with a bitmap of the wrong size.
Skwasm's `Image.toByteData` uses the same surface and has the same
problem.

Instead of using a lock (like I did
[here](flutter#185622)), the size now
travels with each render, so the surface resizes and renders as one
indivisible operation:
- Skwasm: the size is part of the same message to the raster thread (as
it was before flutter#179473). Messages
are processed in order, so no locking is needed, and there is one less
worker round trip per view per frame.
- CanvasKit: resize, draw, and `createImageBitmap()` run synchronously.

Fixes flutter#185034
hannah-hyj pushed a commit to hannah-hyj/flutter that referenced this pull request Oct 19, 2026
…lutter#194401)

Alternative to flutter#185622.

flutter#179473 split rendering on the
shared `OffscreenSurface` into two separately awaited steps: `setSize()`
and then `rasterizeToImageBitmaps()`. When multiple views render at the
same time, one view can resize the surface between another view's resize
and render, so that view ends up with a bitmap of the wrong size.
Skwasm's `Image.toByteData` uses the same surface and has the same
problem.

Instead of using a lock (like I did
[here](flutter#185622)), the size now
travels with each render, so the surface resizes and renders as one
indivisible operation:
- Skwasm: the size is part of the same message to the raster thread (as
it was before flutter#179473). Messages
are processed in order, so no locking is needed, and there is one less
worker round trip per view per frame.
- CanvasKit: resize, draw, and `createImageBitmap()` run synchronously.

Fixes flutter#185034
suryaKommana2662 pushed a commit to suryaKommana2662/flutter that referenced this pull request Oct 29, 2026
…lutter#194401)

Alternative to flutter#185622.

flutter#179473 split rendering on the
shared `OffscreenSurface` into two separately awaited steps: `setSize()`
and then `rasterizeToImageBitmaps()`. When multiple views render at the
same time, one view can resize the surface between another view's resize
and render, so that view ends up with a bitmap of the wrong size.
Skwasm's `Image.toByteData` uses the same surface and has the same
problem.

Instead of using a lock (like I did
[here](flutter#185622)), the size now
travels with each render, so the surface resizes and renders as one
indivisible operation:
- Skwasm: the size is part of the same message to the raster thread (as
it was before flutter#179473). Messages
are processed in order, so no locking is needed, and there is one less
worker round trip per view per frame.
- CanvasKit: resize, draw, and `createImageBitmap()` run synchronously.

Fixes flutter#185034
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD 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.

[web] Multi-view sizing regression: canvases fail to match host container dimensions based on last view added

3 participants