Skip to content

[CP-beta][web] Make resize and render atomic on the shared offscreen surface - #195839

Merged
auto-submit[bot] merged 1 commit into
flutter:flutter-3.50-candidate.0from
flutteractionsbot:cp-beta-5ff0c16a7892cd2a72912c77307b242ff52f17dc
Nov 3, 2026
Merged

auto-submit[bot] merged 1 commit into
flutter:flutter-3.50-candidate.0from
flutteractionsbot:cp-beta-5ff0c16a7892cd2a72912c77307b242ff52f17dc

Conversation

@flutteractionsbot

@flutteractionsbot flutteractionsbot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

This pull request is created by automatic cherry pick workflow

Cherry-pick of #194401.

Issue Link:

#185034

Impact Description:

In multi-view Flutter Web apps, views rendered at the same time race on a shared offscreen surface and end up with the wrong canvas size, clipping or overflowing their host HTML containers. Skwasm's Image.toByteData uses the same surface and has the same problem.

This is a regression since Flutter 3.41 (#179473) that affects deployed multi-view web apps on both CanvasKit and Skwasm and has been blocking production apps from upgrading past 3.38.

Changelog Description:

flutter/185034: When rendering multiple views on the web, canvases may fail to match their host container dimensions.

Workaround:

None.

Risk:

What is the risk level of this cherry-pick?

  • Low
  • Medium
  • High

Test Coverage:

Are you confident that your fix is well-tested by automated tests?

  • Yes
  • No

Validation Steps:

Follow the repro steps in #185034, or run the new tests in engine/src/flutter/lib/web_ui/test/ui/multi_view_sizing_test.dart.

…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
@flutteractionsbot flutteractionsbot added the cp: review Cherry-picks in the review queue label Nov 2, 2026
@flutteractionsbot

Copy link
Copy Markdown
Contributor Author

@mdebbar please fill out the PR description above, afterwards the release team will review this request.

@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Nov 2, 2026
@flutter-dashboard

Copy link
Copy Markdown

This pull request was opened from and to a release candidate branch. This should only be done as part of the official Flutter release process. If you are attempting to make a regular contribution to the Flutter project, please close this PR and follow the instructions at Tree Hygiene for detailed instructions on contributing to Flutter.

Reviewers: Use caution before merging pull requests to release branches. Ensure the proper procedure has been followed.

@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 refactors offscreen surface rasterization to ensure that resizing, rendering, and capturing are atomic operations, preventing race conditions when sharing a single offscreen surface across multiple views. It updates both CanvasKit and Skwasm implementations to accept dimensions directly during rasterization and adds a comprehensive multi-view sizing test suite. The review feedback identifies critical issues in surface.cc where ResizeSurface is invoked before establishing the active GL context via Skwasm::makeCurrent, which could lead to crashes or undefined behavior. Additionally, a potential out-of-bounds RangeError is noted in offscreen_canvas_rasterizer.dart if the number of pictures exceeds the available display canvases.

Comment on lines +229 to 230
ResizeSurface(width, height);
Skwasm::makeCurrent(gl_context_);

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.

high

The surface resizing operation ResizeSurface(width, height) is called before making the GL context current with Skwasm::makeCurrent(gl_context_). Since resizing the surface involves OpenGL/WebGL state changes and resource allocation, it must be executed while the correct GL context is active on the thread. Calling it before makeCurrent can lead to undefined behavior, GL errors, or crashes if the context is not current or if a different context is active.

  Skwasm::makeCurrent(gl_context_);
  ResizeSurface(width, height);

Comment on lines +272 to 273
ResizeSurface(image->width(), image->height());
Skwasm::makeCurrent(gl_context_);

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.

high

Similarly, ResizeSurface(image->width(), image->height()) is called before Skwasm::makeCurrent(gl_context_). It should be called after making the GL context current to ensure all OpenGL operations are executed on the correct context.

  Skwasm::makeCurrent(gl_context_);
  ResizeSurface(image->width(), image->height());

Comment on lines +94 to 98
(displayCanvases[index] as RenderCanvas).renderWithNoBitmapSupport(
source,
currentFrameSize.height,
currentFrameSize,
);

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.

medium

If pictures.length is greater than displayCanvases.length, accessing displayCanvases[index] will throw a RangeError and crash the rendering pipeline. To prevent this, add a defensive bounds check to ensure index is within the bounds of displayCanvases.

        if (index < displayCanvases.length) {
          (displayCanvases[index] as RenderCanvas).renderWithNoBitmapSupport(
            source,
            currentFrameSize.height,
            currentFrameSize,
          );
        }

@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 Nov 2, 2026
@reidbaker reidbaker added the autosubmit Merge PR when tree becomes green via auto submit App label Nov 3, 2026
@auto-submit
auto-submit Bot merged commit 1b9ee1a into flutter:flutter-3.50-candidate.0 Nov 3, 2026
10 checks passed
@flutteractionsbot
flutteractionsbot deleted the cp-beta-5ff0c16a7892cd2a72912c77307b242ff52f17dc branch November 3, 2026 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosubmit Merge PR when tree becomes green via auto submit App CICD Run CI/CD cp: review Cherry-picks in the review queue 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.

3 participants