Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit f8e24c0

Browse files
authored
[Impeller] Do not reference this in the submit callback for Metal GPU Surfaces (#50361)
Fixes flutter/flutter#141351 (speculatively - I have not directly reproduced this in an application, but without this change the added test crashes with a segfault in the submit callback). If the rasterizer gets torn down, the surface gets released and the submit callback may fire on a collected object. Capturing `this` isn't safe. I'm not quite sure how that could happen from the linked stack trace though, since the draw call and the teardown call should be happening on the raster thread, and if the surface was reset then the draw call should've failed earlier... The added test causes a segfault without the change.
1 parent 9bd98bc commit f8e24c0

File tree

3 files changed

+42
-19
lines changed

3 files changed

+42
-19
lines changed

shell/gpu/gpu_surface_metal_impeller.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ class IMPELLER_CA_METAL_LAYER_AVAILABLE GPUSurfaceMetalImpeller
4747
bool disable_partial_repaint_ = false;
4848
// Accumulated damage for each framebuffer; Key is address of underlying
4949
// MTLTexture for each drawable
50-
std::map<uintptr_t, SkIRect> damage_;
50+
std::shared_ptr<std::map<uintptr_t, SkIRect>> damage_ =
51+
std::make_shared<std::map<uintptr_t, SkIRect>>();
5152

5253
// |Surface|
5354
std::unique_ptr<SurfaceFrame> AcquireFrame(

shell/gpu/gpu_surface_metal_impeller.mm

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,12 @@
108108

109109
id<MTLTexture> last_texture = static_cast<id<MTLTexture>>(last_texture_);
110110
SurfaceFrame::SubmitCallback submit_callback =
111-
fml::MakeCopyable([this, //
112-
renderer = impeller_renderer_, //
113-
aiks_context = aiks_context_, //
114-
drawable, //
115-
last_texture //
111+
fml::MakeCopyable([damage = damage_,
112+
disable_partial_repaint = disable_partial_repaint_, //
113+
renderer = impeller_renderer_, //
114+
aiks_context = aiks_context_, //
115+
drawable, //
116+
last_texture //
116117
](SurfaceFrame& surface_frame, DlCanvas* canvas) mutable -> bool {
117118
if (!aiks_context) {
118119
return false;
@@ -124,10 +125,10 @@
124125
return false;
125126
}
126127

127-
if (!disable_partial_repaint_) {
128+
if (!disable_partial_repaint && damage) {
128129
uintptr_t texture = reinterpret_cast<uintptr_t>(last_texture);
129130

130-
for (auto& entry : damage_) {
131+
for (auto& entry : *damage) {
131132
if (entry.first != texture) {
132133
// Accumulate damage for other framebuffers
133134
if (surface_frame.submit_info().frame_damage) {
@@ -136,7 +137,7 @@
136137
}
137138
}
138139
// Reset accumulated damage for current framebuffer
139-
damage_[texture] = SkIRect::MakeEmpty();
140+
(*damage)[texture] = SkIRect::MakeEmpty();
140141
}
141142

142143
std::optional<impeller::IRect> clip_rect;
@@ -146,8 +147,8 @@
146147
buffer_damage->width(), buffer_damage->height());
147148
}
148149

149-
auto surface = impeller::SurfaceMTL::MakeFromMetalLayerDrawable(
150-
impeller_renderer_->GetContext(), drawable, clip_rect);
150+
auto surface = impeller::SurfaceMTL::MakeFromMetalLayerDrawable(renderer->GetContext(),
151+
drawable, clip_rect);
151152

152153
if (clip_rect && clip_rect->IsEmpty()) {
153154
return surface->Present();
@@ -174,8 +175,8 @@
174175
// Provide accumulated damage to rasterizer (area in current framebuffer that lags behind
175176
// front buffer)
176177
uintptr_t texture = reinterpret_cast<uintptr_t>(drawable.texture);
177-
auto i = damage_.find(texture);
178-
if (i != damage_.end()) {
178+
auto i = damage_->find(texture);
179+
if (i != damage_->end()) {
179180
framebuffer_info.existing_damage = i->second;
180181
}
181182
framebuffer_info.supports_partial_repaint = true;
@@ -205,7 +206,8 @@
205206
}
206207

207208
SurfaceFrame::SubmitCallback submit_callback =
208-
fml::MakeCopyable([this, //
209+
fml::MakeCopyable([disable_partial_repaint = disable_partial_repaint_, //
210+
damage = damage_,
209211
renderer = impeller_renderer_, //
210212
aiks_context = aiks_context_, //
211213
texture_info, //
@@ -222,10 +224,10 @@
222224
return false;
223225
}
224226

225-
if (!disable_partial_repaint_) {
227+
if (!disable_partial_repaint && damage) {
226228
uintptr_t texture_ptr = reinterpret_cast<uintptr_t>(mtl_texture);
227229

228-
for (auto& entry : damage_) {
230+
for (auto& entry : *damage) {
229231
if (entry.first != texture_ptr) {
230232
// Accumulate damage for other framebuffers
231233
if (surface_frame.submit_info().frame_damage) {
@@ -234,7 +236,7 @@
234236
}
235237
}
236238
// Reset accumulated damage for current framebuffer
237-
damage_[texture_ptr] = SkIRect::MakeEmpty();
239+
(*damage)[texture_ptr] = SkIRect::MakeEmpty();
238240
}
239241

240242
std::optional<impeller::IRect> clip_rect;
@@ -278,8 +280,8 @@
278280
// Provide accumulated damage to rasterizer (area in current framebuffer that lags behind
279281
// front buffer)
280282
uintptr_t texture = reinterpret_cast<uintptr_t>(mtl_texture);
281-
auto i = damage_.find(texture);
282-
if (i != damage_.end()) {
283+
auto i = damage_->find(texture);
284+
if (i != damage_->end()) {
283285
framebuffer_info.existing_damage = i->second;
284286
}
285287
framebuffer_info.supports_partial_repaint = true;

shell/gpu/gpu_surface_metal_impeller_unittests.mm

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
~TestGPUSurfaceMetalDelegate() = default;
2525

2626
GPUCAMetalLayerHandle GetCAMetalLayer(const SkISize& frame_info) const override {
27+
layer_.drawableSize = CGSizeMake(frame_info.width(), frame_info.height());
2728
return (__bridge GPUCAMetalLayerHandle)(layer_);
2829
}
2930

@@ -35,6 +36,8 @@ GPUCAMetalLayerHandle GetCAMetalLayer(const SkISize& frame_info) const override
3536

3637
bool AllowsDrawingWhenGpuDisabled() const override { return true; }
3738

39+
void SetDevice() { layer_.device = ::MTLCreateSystemDefaultDevice(); }
40+
3841
private:
3942
CAMetalLayer* layer_ = nil;
4043
};
@@ -77,5 +80,22 @@ GPUCAMetalLayerHandle GetCAMetalLayer(const SkISize& frame_info) const override
7780
ASSERT_EQ(frame, nullptr);
7881
}
7982

83+
TEST(GPUSurfaceMetalImpeller, AcquireFrameFromCAMetalLayerDoesNotRetainThis) {
84+
auto delegate = std::make_shared<TestGPUSurfaceMetalDelegate>();
85+
delegate->SetDevice();
86+
std::unique_ptr<Surface> surface =
87+
std::make_unique<GPUSurfaceMetalImpeller>(delegate.get(), CreateImpellerContext());
88+
89+
ASSERT_TRUE(surface->IsValid());
90+
91+
auto frame = surface->AcquireFrame(SkISize::Make(100, 100));
92+
ASSERT_TRUE(frame);
93+
94+
// Simulate a rasterizer teardown, e.g. due to going to the background.
95+
surface.reset();
96+
97+
ASSERT_TRUE(frame->Submit());
98+
}
99+
80100
} // namespace testing
81101
} // namespace flutter

0 commit comments

Comments
 (0)