Skip to content

[impeller:gles] fixes early calls to flatten scenes - #194506

Merged
auto-submit[bot] merged 6 commits into
flutter:masterfrom
gaaclarke:scene-image-before-surface
Oct 17, 2026
Merged

auto-submit[bot] merged 6 commits into
flutter:masterfrom
gaaclarke:scene-image-before-surface

Conversation

@gaaclarke

@gaaclarke gaaclarke commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

This fix delays snapshot display_list creation and creates offscreen surface on demand to support flattening scenes before the surface has been created.

This makes opengles match the behavior of vulkan and metal.

fixes #192365

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@gaaclarke gaaclarke changed the title Scene image before surface fixes early calls to flatten scenes Oct 15, 2026
@gaaclarke gaaclarke changed the title fixes early calls to flatten scenes [impeller:gles] fixes early calls to flatten scenes Oct 15, 2026
@github-actions github-actions Bot added the engine flutter/engine related. See also e: labels. label Oct 15, 2026
@gaaclarke gaaclarke added the CICD Run CI/CD label Oct 15, 2026
@gaaclarke
gaaclarke marked this pull request as ready for review October 15, 2026 23:54

@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 the snapshotting mechanism in Impeller to defer display list generation (such as flattening layer trees) until a rendering context is made current, resolving an issue where constructing an AiksContext without a current context breaks the OpenGL ES backend. The feedback suggests checking the validity of the SnapshotDisplayListProvider callback before invoking it in SnapshotControllerImpeller to prevent potential crashes from empty functions, and explicitly passing nullptr for aiks_context in the Skia path of Picture::DoRasterizeToImage to ensure consistency and avoid compilation errors.

@gaaclarke
gaaclarke requested review from andywolff and b-luk October 16, 2026 00:07
/*gr_context=*/nullptr,
aiks_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.

This provider callback does the same logic in both places where it is instantiated (here and in picture.cc). Does it need to be a callback?

Instead of passing a SnapshotDisplayListProvider (std::function<sk_sp<DisplayList>(impeller::AiksContext* aiks_context)>) through MakeImpellerSnapshot/MakeImpellerSnapshotSync/DoMakeRasterSnapshot, can we make them take content (std::variant<sk_sp<DisplayList>, std::unique_ptr<LayerTree>>) directly? Then the callback logic could just be a local helper function in snapshot_controller_impeller.cc that is called directly, rather than being passed through multiple functions as a callback. I think that makes the code more readable, and it gets rid of the duplicated callback logic.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I considered a variant, but it would be a variant between a callback and a displaylist. That complicated the code though with little benefit. The callback is necessary to defer the operation until the point where it can be executed safely. These closures are so tiny they get optimized and having a unified API seemed the best option. We could have a helper function that removes some of boilerplate you mention. It didn't seem necessary though to me.

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.

It doesn't need to be a variant between a callback and a displaylist. I was suggesting using the existing content object, which is a variant between a displaylist and a layertree.

Flattening the layertree would still be deferred until after we get an AiksContext in snapshot_controller_impeller.cc. The code to flatten it would be located in snapshot_controller_impeller.cc and be called directly as a static function in the same file, rather than as a callback that is defined separately and passed in from multiple different callers. I think that makes the code significantly easier to follow.

We would need to be able to flatten the layertree at the deferred call sites (where provider() is called in the current PR). I think that can be done without much trouble. Flattening the layertree requires bounds and texture_registry. Bounds is already available in the deferred call sites in snapshot_controller_impeller.cc. Texture_registry is from Rasterizer, which is used as SnapshotController::Delegate in snapshot_controller_impeller.cc, so it could easily be exposed and accessed in the deferred call sites.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't think so because that would start creeping flow into the snapshot controller api. I think it's more clear to have an API that is "give me a function that returns a display list", especially as a transition from "give me a display list".

I hear you that superfluous indirection can make codebases harder to follow, but apt abstractions can help understanding too. Just thinking on terms of the number of things snapshot has to know about here to function and be tested, I think this is good without mixing in flow.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done, added helper

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's kind of up in the air wether that helps for the layer tree case =T

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.

Yeah, I don't know if this is much better than the inlined lambdas version. I don't like how the displaylist vs layertree branch MakeProvider functions are in two separate places, or adding a new helper file just for one of the branches.

What about keeping the helper file, but make its helper function handle both the layertree and displaylist case? And then SnapshotDelegate::MakeDisplayListProvider goes away.

So in the helper file, instead of

SnapshotDelegate::SnapshotDisplayListProvider MakeLayerTreeSnapshotProvider(
    std::unique_ptr<LayerTree> layer_tree,
    std::shared_ptr<TextureRegistry> texture_registry);

We have

SnapshotDelegate::SnapshotDisplayListProvider MakeSnapshotDisplayListProvider(
    std::variant<sk_sp<DisplayList>, std::unique_ptr<LayerTree>> content,
    std::shared_ptr<TextureRegistry> texture_registry) {
  if (auto* display_list = std::get_if<sk_sp<DisplayList>>(&content)) {
    return [display_list = std::move(*display_list)](impeller::AiksContext*) {
      return display_list;
    };
  }
  auto& layer_tree = std::get<std::unique_ptr<LayerTree>>(content);
  FML_DCHECK(layer_tree);
  return fml::MakeCopyable([layer_tree = std::move(layer_tree),
                            texture_registry = std::move(texture_registry)](
                               impeller::AiksContext* aiks_context) mutable {
    const DlISize& size = layer_tree->frame_size();
    return layer_tree->Flatten(DlRect::MakeWH(size.width, size.height),
                               texture_registry, /*gr_context=*/nullptr,
                               aiks_context);
  });
}

And the two call sites wouldn't need to branch to call different things:

// display_list_deferred_image_gpu_impeller.cc
auto provider = MakeSnapshotProvider(std::move(content),
                                     snapshot_delegate->GetTextureRegistry());

// picture.cc
auto provider = layer_tree
    ? MakeSnapshotProvider(std::move(layer_tree), snapshot_delegate->GetTextureRegistry())
    : MakeSnapshotProvider(display_list, snapshot_delegate->GetTextureRegistry());

That looks a little bit better to me, but I'll leave it up to you if you want to change it. If you prefer the current code, or the original lambdas I'll be good with those too. I think you're right that keeping the abstraction layers separate requires a little ugliness so there's no ideal elegant solution for this. I'll approve to unblock if you think it's fine as-is, and if change it to either alternate option I'll re-approve as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I just went back to the closures.

if (pbuffer_surface) {
context_result = pbuffer_surface->MakeRenderContextCurrent();
}
ScopedSnapshotContext snapshot_context(delegate);

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.

Is the only purpose of the new ScopedSnapshotContext class to make the GetSnapshotSurfaceProducer() case reuse pbuffer_surface_->GetAiksContext() instead of using delegate.GetAiksContext()?

Could we just modify the existing code with this after the if(GetSurface())/else (GetSnapshotSurfaceProducer()):

  std::shared_ptr<impeller::AiksContext> aiks_context =
      pbuffer_surface ? pbuffer_surface->GetAiksContext()
                      : delegate.GetAiksContext();

and then just use aiks_context instead of delegate.GetAiksContext() below?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yea, it's just an abstraction that captures the concept of selecting the AiksContext and is a convenient place to make sure we reuse it. The logic could all be inlined.

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.

Should we inline it then? I feel like that would be a much smaller change. Creating this entirely ScopedSnapshotContext class that is only used in this one instance seems overkill when we can just add this one line to the existing code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Transitioned it to a function that returns a struct. I think that's more amenable.

@gaaclarke
gaaclarke requested a review from b-luk October 16, 2026 20:34
@gaaclarke
gaaclarke requested a review from b-luk October 16, 2026 21:16
@gaaclarke

Copy link
Copy Markdown
Member Author

@b-luk I can't request a review again, the last one is outstanding but made the edits we discussed in the chat

b-luk
b-luk previously approved these changes Oct 16, 2026
/*gr_context=*/nullptr,
aiks_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.

Yeah, I don't know if this is much better than the inlined lambdas version. I don't like how the displaylist vs layertree branch MakeProvider functions are in two separate places, or adding a new helper file just for one of the branches.

What about keeping the helper file, but make its helper function handle both the layertree and displaylist case? And then SnapshotDelegate::MakeDisplayListProvider goes away.

So in the helper file, instead of

SnapshotDelegate::SnapshotDisplayListProvider MakeLayerTreeSnapshotProvider(
    std::unique_ptr<LayerTree> layer_tree,
    std::shared_ptr<TextureRegistry> texture_registry);

We have

SnapshotDelegate::SnapshotDisplayListProvider MakeSnapshotDisplayListProvider(
    std::variant<sk_sp<DisplayList>, std::unique_ptr<LayerTree>> content,
    std::shared_ptr<TextureRegistry> texture_registry) {
  if (auto* display_list = std::get_if<sk_sp<DisplayList>>(&content)) {
    return [display_list = std::move(*display_list)](impeller::AiksContext*) {
      return display_list;
    };
  }
  auto& layer_tree = std::get<std::unique_ptr<LayerTree>>(content);
  FML_DCHECK(layer_tree);
  return fml::MakeCopyable([layer_tree = std::move(layer_tree),
                            texture_registry = std::move(texture_registry)](
                               impeller::AiksContext* aiks_context) mutable {
    const DlISize& size = layer_tree->frame_size();
    return layer_tree->Flatten(DlRect::MakeWH(size.width, size.height),
                               texture_registry, /*gr_context=*/nullptr,
                               aiks_context);
  });
}

And the two call sites wouldn't need to branch to call different things:

// display_list_deferred_image_gpu_impeller.cc
auto provider = MakeSnapshotProvider(std::move(content),
                                     snapshot_delegate->GetTextureRegistry());

// picture.cc
auto provider = layer_tree
    ? MakeSnapshotProvider(std::move(layer_tree), snapshot_delegate->GetTextureRegistry())
    : MakeSnapshotProvider(display_list, snapshot_delegate->GetTextureRegistry());

That looks a little bit better to me, but I'll leave it up to you if you want to change it. If you prefer the current code, or the original lambdas I'll be good with those too. I think you're right that keeping the abstraction layers separate requires a little ugliness so there's no ideal elegant solution for this. I'll approve to unblock if you think it's fine as-is, and if change it to either alternate option I'll re-approve as well.

@gaaclarke gaaclarke added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 17, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Oct 17, 2026
Merged via the queue into flutter:master with commit 84a3314 Oct 17, 2026
23 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 17, 2026
@bjrochem72

Copy link
Copy Markdown

@gaaclarke would you consider this a candidate for cherry-pick to stable (and/or beta)? I reported the original crash (#192365) and can confirm the production impact. Request details below, happy to adjust.

Impacted Users
Android users on the Impeller GLES backend, i.e. devices where Vulkan is blocklisted or unavailable (low-end Adreno/Mali hardware such as the TCL 20 SE and Galaxy A13 5G), whenever the app triggers an offscreen snapshot (toImage / RepaintBoundary.toImage / Scene.toImage). In practice it is hit by common tooling that rasterises the UI, for example Sentry error screenshots and session replay.

Impact Description
Hard native crash: SIGSEGV use-after-free in DeviceBufferGLES::OnGetContents on the raster thread. Deferred GLES reactor ops outlive the DeviceBuffer they reference and are flushed by a later snapshot, dereferencing freed memory. First surfaced from production Sentry telemetry with symbolicated traces; a deterministic repro is in #192365.

Workaround
Disable or gate the snapshot-producing feature. In my app I turned session replay off and gated error screenshots to the foreground. That stops the crash but loses the diagnostics, so it is only a stopgap.

Risk
I am the reporter, not the author, so you can assess code risk better than I can. From the PR, the change defers snapshot display-list creation and builds an offscreen surface on demand, bringing GLES in line with Vulkan and Metal.

Test Coverage
The PR adds scene_impeller_gles_unittests.cc, extends shell_unittests.cc, and merged green.

Validation Steps
On a Vulkan-blocklisted Android device, use the repro in #192365, capture a snapshot during early startup or while backgrounded, and confirm no SIGSEGV. On my side, production Sentry shows the crash signature stops once the snapshot path is gated.

@gaaclarke

Copy link
Copy Markdown
Member Author

@bjrochem72 I believe Sentry did a a workaround on their side though? Can't you workaround it by upgrading Sentry?

@bjrochem72

Copy link
Copy Markdown

@gaaclarke I'm already on the upgraded Sentry (sentry_flutter 9.30.1), so I've got #3935. It doesn't clear this one.

#3935 is a different crash: a Vulkan ~TextureVK SIGABRT (getsentry/sentry-dart#3923) where the replay scheduler released a GPU texture after the Activity surface was torn down, fixed by making Scheduler.stop() await the in-flight capture. Mine is a GLES SIGSEGV use-after-free in DeviceBufferGLES::OnGetContents (#192365): different backend, different signal, different stack, and it sits in the snapshot path rather than the replay scheduler.

#192365 also reproduces with no Sentry involved at all. The repro in the issue (and the independent one from @vib3App) is a plain Scene.toImage before the first frame, which detonates on GLES because the scene is flattened with no current context. Sentry only happens to exercise that path via replay and error screenshots, so it can stop triggering it, which is what my workaround does (replay off, screenshots gated to the foreground), but it can't fix an engine use-after-free. This PR is the actual fix, which is why I asked about a cherry-pick.

@WessimBetclic

Copy link
Copy Markdown

Supporting the cherry-pick request above with numbers from another production app, no Sentry involved.

Our app sampled the status bar area with Scene.toImage on route changes and on return to the foreground. On Flutter 3.47.3 (engine 06a2e2a1), on devices where Impeller runs on OpenGL ES (mostly MediaTek phones since #186405), that hit #192365: SIGSEGV with BlitCopyBufferToTextureCommandGLES::Encode flushed by a later snapshot, 47 of 63 decoded stacks on the builds that still use Scene.toImage.

Over the last 7 days, those builds account for 1,733 users of this crash across 5 Android apps (one app alone: 1,015). The build that replaced the Scene with a Picture (which is never flattened before the surface exists) no longer shows this shape, 2 of 37 decoded stacks, which matches what this PR changes.

So the fix is right and the exposure on current stable is real: 3.47 is the stable channel, these devices moved to GLES in 3.47, and every app that calls toImage on them is exposed, with or without Sentry. A cp: stable would be worth it.

(The Picture route is not a complete workaround either: on the same devices the readback of the Picture.toImage result then crashes in ConfigureFBO or aborts on the FML_CHECK in BlitPassGLES::EncodeCommands, filed as #195319.)

suryaKommana2662 pushed a commit to suryaKommana2662/flutter that referenced this pull request Oct 29, 2026
This fix delays snapshot display_list creation and creates offscreen
surface on demand to support flattening scenes before the surface has
been created.

This makes opengles match the behavior of vulkan and metal.

fixes flutter#192365

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

If this change needs to override an active code freeze, provide a
comment explaining why. The code freeze workflow can be overridden by
code reviewers. See pinned issues for any active code freezes with
guidance.

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
@RCVZ

RCVZ commented Nov 2, 2026

Copy link
Copy Markdown

@gaaclarke can this be cherry-picked for the current stable?

@bjrochem72

Copy link
Copy Markdown

Supporting the cherry-pick-to-stable request as the app that filed #192365.

We hit this through the Sentry SDK rather than a direct Scene.toImage call. Session replay runs a ~1 fps buffer recorder through the engine offscreen-snapshot path in every real-device session, and on-error screenshots use the same path. On low-end Adreno/Mali Android devices where Impeller runs on GLES (Vulkan-blocklisted), that reproduced #192365: a SIGSEGV in DeviceBufferGLES::OnGetContents flushed by a later snapshot.

To stop it on stable 3.47 we disabled session replay entirely (both sample rates 0.0) and foreground-gated error screenshots. That stops the crash, but it also removes both diagnostics on exactly the device class we most need them for, so our current low crash counts understate the real exposure.

The fix here looks correct (build the offscreen surface on demand, matching Vulkan/Metal), but it is master-only. The affected devices moved to GLES in 3.47 (via #186405), so stable 3.47 is where this bites, and #195319 shows the Picture.toImage route is not a clean workaround on the same devices. A cp: to the current stable would let us restore replay and error screenshots instead of shipping them off.

LoveDoLove pushed a commit to LoveDoLove-Forked-Projects/flutter that referenced this pull request Nov 3, 2026
…xt without a surface (flutter#195629)

Fixes flutter#195627. See the issue
for context.

When there is no surface, `Rasterizer::GetAiksContext()` now tries to
get an AiksContext using the snapshot surface producer. This is similar
to flutter#194506's fix for `Picture.toImage()` when there is no surface
([here](https://github.com/flutter/flutter/pull/194506/changes#diff-5d98a88b599a9d73af3f6acda580a3f03bce1eab9c7abd1f29f425f7279768c0R99-R110)).

Adds tests similar to flutter#194506, but for this issue's `Image.toByteData()`
case.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

If this change needs to override an active code freeze, provide a
comment explaining why. The code freeze workflow can be overridden by
code reviewers. See pinned issues for any active code freezes with
guidance.

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
@keren-cw

keren-cw commented Nov 6, 2026

Copy link
Copy Markdown

Hi! Adding production impact data to support a cherry-pick of this fix (and #195629 if it's needed for the toByteData path) into the 3.47 stable branch (flutter-3.47-candidate.0), ideally as a 3.47.10 hotfix.

Impact: We upgraded our app from Flutter 3.41.9 to 3.47.8 and started seeing this exact crash (#192365) right away. Within a few hours of the staged rollout we had to halt it, and the affected cohort has since reached 129 events from 95 users. It hits 11+ device models (Xiaomi, Samsung, Realme, Motorola) on Android 11–16, all with Impeller on OpenGL ES. In most events the app had been backgrounded for 1–4 minutes before the crash.

Stack (thread 1.raster, SIGSEGV / SEGV_MAPERR):

impeller::BlitCopyBufferToTextureCommandGLES::Encode
impeller::EncodeCommandsInReactor (blit_pass_gles.cc:67)
impeller::ReactorGLES::React / FlushOps (reactor_gles.cc)
impeller::ContentContext::ContentContext (content_context.cc:600)
flutter::AndroidSurfaceGLImpeller::CreateSnapshotSurface (android_surface_gl_impeller.cc:163)
flutter::SnapshotControllerImpeller::MakeImpellerSnapshot
flutter::Picture::DoRasterizeToImage

Why it matters for 3.47: The fix isn't in 3.47.9 (or in the 3.50 beta), so apps on 3.47 stable have no engine-level fix available. Moving to 3.50 isn't a quick option for us mid-release.

Happy to provide more data or test a candidate build. Thanks!

@mhamri

mhamri commented Nov 8, 2026

Copy link
Copy Markdown

Another production app hit by this, on Flutter 3.44.8. A second FlutterEngine with no FlutterView (started by flutter_foreground_task for background printing) calls Picture.toImage. On OpenGL ES devices (OPPO CPH1907 with Adreno, Infinix X6852), the process dies with libEGL: call to OpenGL ES API with no current context, then SIGSEGV on 2.raster. The Android emulator does not crash.

A workaround for anyone who cannot upgrade yet: in onEngineCreate, give the engine a 1x1 ImageReader surface with FlutterRenderer.startRenderingToSurface, and call stopRenderingToSurface in onEngineWillDestroy. The engine then takes the normal onscreen path, and the crash stops.

+1 for a cherry-pick of this fix to stable.

eseidel added a commit to shorebirdtech/flutter that referenced this pull request Nov 8, 2026
Test-only support for backporting flutter#194506 and flutter#195629 onto
3.47.9: their lib/ui tests depend on the //flutter/impeller/renderer/
backend/gles:mock_gles target (split out upstream in flutter#193306) and on
MockGLESImpl's CreateShader, CreateProgram, GetShaderiv and GetProgramiv,
added upstream after 3.47.9. Copies upstream's definitions; no shipped code
changes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
eseidel pushed a commit to shorebirdtech/flutter that referenced this pull request Nov 8, 2026
…xt without a surface (flutter#195629)

Fixes flutter#195627. See the issue
for context.

When there is no surface, `Rasterizer::GetAiksContext()` now tries to
get an AiksContext using the snapshot surface producer. This is similar
to flutter#194506's fix for `Picture.toImage()` when there is no surface
([here](https://github.com/flutter/flutter/pull/194506/changes#diff-5d98a88b599a9d73af3f6acda580a3f03bce1eab9c7abd1f29f425f7279768c0R99-R110)).

Adds tests similar to flutter#194506, but for this issue's `Image.toByteData()`
case.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant in-code documentation (doc comments with
`///`).
- [x] If this PR introduces a new feature or capability, I created and
linked a website documentation issue or PR in [flutter/website] (or
verified none is needed).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

If this change needs to override an active code freeze, provide a
comment explaining why. The code freeze workflow can be overridden by
code reviewers. See pinned issues for any active code freezes with
guidance.

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/CONTRIBUTING.ai.md
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[flutter/website]: https://github.com/flutter/website
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

(cherry picked from commit 1ea585c)

Backport note: ImageToByteDataWithoutSurfaceOnImpellerGLES is dropped on
3.47.9. It guards flutter#195627, a regression from flutter#191747, which
3.47.9 doesn't have: here Image.toByteData doesn't go through
Rasterizer::GetAiksContext, so the test reaches the older encode path and
waits forever for a current context.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
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.

Projects

None yet

7 participants