Repository navigation
[impeller:gles] fixes early calls to flatten scenes - #194506
Conversation
offscreen surface if one isn't present
There was a problem hiding this comment.
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.
| /*gr_context=*/nullptr, | ||
| aiks_context); | ||
| }); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
It's kind of up in the air wether that helps for the layer tree case =T
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I just went back to the closures.
| if (pbuffer_surface) { | ||
| context_result = pbuffer_surface->MakeRenderContextCurrent(); | ||
| } | ||
| ScopedSnapshotContext snapshot_context(delegate); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Transitioned it to a function that returns a struct. I think that's more amenable.
|
@b-luk I can't request a review again, the last one is outstanding but made the edits we discussed in the chat |
| /*gr_context=*/nullptr, | ||
| aiks_context); | ||
| }); | ||
| } |
There was a problem hiding this comment.
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 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 Impact Description Workaround Risk Test Coverage Validation Steps |
|
@bjrochem72 I believe Sentry did a a workaround on their side though? Can't you workaround it by upgrading Sentry? |
|
@gaaclarke I'm already on the upgraded Sentry ( #3935 is a different crash: a Vulkan #192365 also reproduces with no Sentry involved at all. The repro in the issue (and the independent one from @vib3App) is a plain |
|
Supporting the cherry-pick request above with numbers from another production app, no Sentry involved. Our app sampled the status bar area with 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 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 (The |
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
|
@gaaclarke can this be cherry-picked for the current stable? |
|
Supporting the cherry-pick-to-stable request as the app that filed We hit this through the Sentry SDK rather than a direct To stop it on stable 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 |
…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
|
Hi! Adding production impact data to support a cherry-pick of this fix (and #195629 if it's needed for the 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 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! |
|
Another production app hit by this, on Flutter 3.44.8. A second A workaround for anyone who cannot upgrade yet: in +1 for a cherry-pick of this fix to stable. |
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]>
…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]>
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-assistbot 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.