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

Commit 78e3020

Browse files
committed
macOS: Fix platform view threading issues
1 parent 774bce8 commit 78e3020

12 files changed

+96
-35
lines changed

shell/platform/darwin/macos/framework/Source/FlutterCompositor.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ class FlutterCompositor {
6060

6161
// Callback triggered at the end of the Present function. has_flutter_content
6262
// is true when Flutter content was rendered, otherwise false.
63-
using PresentCallback = std::function<bool(bool has_flutter_content)>;
63+
using PresentCallback =
64+
std::function<bool(bool has_flutter_content,
65+
dispatch_block_t platfrom_thread_notify)>;
6466

6567
// Registers a callback to be triggered at the end of the Present function.
6668
// If a callback was previously registered, it will be replaced.
@@ -85,18 +87,22 @@ class FlutterCompositor {
8587
void SetFrameStatus(FrameStatus frame_status);
8688
FrameStatus GetFrameStatus();
8789

88-
// Clears the previous CALayers and updates the frame status to frame started.
90+
// Sets frame status to frame started.
8991
void StartFrame();
9092

93+
// Clears the previous CALayers.
94+
void RemoveOldLayers();
95+
9196
// Calls the present callback and ensures the frame status is updated
9297
// to frame ended, returning whether the present was successful or not.
93-
bool EndFrame(bool has_flutter_content);
98+
bool EndFrame(bool has_flutter_content, dispatch_block_t on_notify);
9499

95100
// Creates a CALayer object which is backed by the supplied IOSurface, and
96101
// adds it to the root CALayer for the given view.
97102
void InsertCALayerForIOSurface(
98103
FlutterView* view,
99104
const IOSurfaceRef& io_surface,
105+
size_t layer_position,
100106
CATransform3D transform = CATransform3DIdentity);
101107

102108
private:

shell/platform/darwin/macos/framework/Source/FlutterCompositor.mm

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@
9090
SetFrameStatus(FrameStatus::kPresenting);
9191

9292
bool has_flutter_content = false;
93+
94+
std::vector<dispatch_block_t> actions;
95+
actions.push_back(^{
96+
RemoveOldLayers();
97+
});
98+
9399
for (size_t i = 0; i < layers_count; ++i) {
94100
const auto* layer = layers[i];
95101
FlutterBackingStore* backing_store = const_cast<FlutterBackingStore*>(layer->backing_store);
@@ -100,19 +106,27 @@
100106
FlutterIOSurfaceHolder* io_surface_holder =
101107
(__bridge FlutterIOSurfaceHolder*)backing_store->metal.texture.user_data;
102108
IOSurfaceRef io_surface = [io_surface_holder ioSurface];
103-
InsertCALayerForIOSurface(view, io_surface);
109+
actions.push_back(^{
110+
InsertCALayerForIOSurface(view, io_surface, i);
111+
});
104112
}
105113
has_flutter_content = true;
106114
break;
107115
}
108116
case kFlutterLayerContentTypePlatformView: {
109-
PresentPlatformView(view, layer, i);
117+
actions.push_back(^{
118+
PresentPlatformView(view, layer, i);
119+
});
110120
break;
111121
}
112122
};
113123
}
114124

115-
return EndFrame(has_flutter_content);
125+
return EndFrame(has_flutter_content, ^{
126+
for (auto& action : actions) {
127+
action();
128+
}
129+
});
116130
}
117131

118132
void FlutterCompositor::PresentPlatformView(FlutterView* default_base_view,
@@ -143,18 +157,21 @@
143157
}
144158

145159
void FlutterCompositor::StartFrame() {
160+
SetFrameStatus(FrameStatus::kStarted);
161+
}
162+
163+
void FlutterCompositor::RemoveOldLayers() {
146164
// First remove all CALayers from the superlayer.
147165
for (auto layer : active_ca_layers_) {
148166
[layer removeFromSuperlayer];
149167
}
150168

151169
// Reset active layers.
152170
active_ca_layers_.clear();
153-
SetFrameStatus(FrameStatus::kStarted);
154171
}
155172

156-
bool FlutterCompositor::EndFrame(bool has_flutter_content) {
157-
bool status = present_callback_(has_flutter_content);
173+
bool FlutterCompositor::EndFrame(bool has_flutter_content, dispatch_block_t on_notify) {
174+
bool status = present_callback_(has_flutter_content, on_notify);
158175
SetFrameStatus(FrameStatus::kEnded);
159176
return status;
160177
}
@@ -173,14 +190,15 @@
173190

174191
void FlutterCompositor::InsertCALayerForIOSurface(FlutterView* view,
175192
const IOSurfaceRef& io_surface,
193+
size_t layer_position,
176194
CATransform3D transform) {
177195
// FlutterCompositor manages the lifecycle of CALayers.
178196
CALayer* content_layer = [[CALayer alloc] init];
179197
content_layer.transform = transform;
180198
content_layer.frame = view.layer.bounds;
181199
[content_layer setContents:(__bridge id)io_surface];
182200
[view.layer addSublayer:content_layer];
183-
201+
content_layer.zPosition = layer_position;
184202
active_ca_layers_.push_back(content_layer);
185203
}
186204

shell/platform/darwin/macos/framework/Source/FlutterCompositorTest.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ - (nullable FlutterView*)getView:(uint64_t)viewId {
6767
/*mtl_device*/ nullptr);
6868

6969
bool flag = false;
70-
macos_compositor->SetPresentCallback([f = &flag](bool has_flutter_content) {
70+
macos_compositor->SetPresentCallback([f = &flag](bool has_flutter_content, dispatch_block_t) {
7171
*f = true;
7272
return true;
7373
});

shell/platform/darwin/macos/framework/Source/FlutterEngine.mm

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -423,18 +423,19 @@ - (FlutterCompositor*)createFlutterCompositor {
423423

424424
_macOSCompositor = std::make_unique<flutter::FlutterCompositor>(
425425
_viewProvider, _platformViewController, _renderer.device);
426-
_macOSCompositor->SetPresentCallback([weakSelf](bool has_flutter_content) {
427-
// TODO(dkwingsmt): The compositor only supports single-view for now. As
428-
// more classes are gradually converted to multi-view, it should get the
429-
// view ID from somewhere.
430-
uint64_t viewId = kFlutterDefaultViewId;
431-
if (has_flutter_content) {
432-
return [weakSelf.renderer present:viewId] == YES;
433-
} else {
434-
[weakSelf.renderer presentWithoutContent:viewId];
435-
return true;
436-
}
437-
});
426+
_macOSCompositor->SetPresentCallback(
427+
[weakSelf](bool has_flutter_content, dispatch_block_t on_notify) {
428+
// TODO(dkwingsmt): The compositor only supports single-view for now. As
429+
// more classes are gradually converted to multi-view, it should get the
430+
// view ID from somewhere.
431+
uint64_t viewId = kFlutterDefaultViewId;
432+
if (has_flutter_content) {
433+
return [weakSelf.renderer present:viewId withBlock:on_notify] == YES;
434+
} else {
435+
[weakSelf.renderer presentWithoutContent:viewId];
436+
return true;
437+
}
438+
});
438439

439440
_compositor = {};
440441
_compositor.struct_size = sizeof(FlutterCompositor);

shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ @interface FlutterEngine (Test)
465465
// Latch to ensure the entire layer tree has been generated and presented.
466466
fml::AutoResetWaitableEvent latch;
467467
auto compositor = engine.macOSCompositor;
468-
compositor->SetPresentCallback([&](bool has_flutter_content) {
468+
compositor->SetPresentCallback([&](bool has_flutter_content, dispatch_block_t completion) {
469469
latch.Signal();
470470
return true;
471471
});

shell/platform/darwin/macos/framework/Source/FlutterRenderer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
/**
3939
* Called by the engine when the given view's buffers should be swapped.
4040
*/
41-
- (BOOL)present:(uint64_t)viewId;
41+
- (BOOL)present:(uint64_t)viewId withBlock:(nullable dispatch_block_t)block;
4242

4343
/**
4444
* Tells the renderer that there is no Flutter content available for this frame.

shell/platform/darwin/macos/framework/Source/FlutterRenderer.mm

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ static bool OnPresentDrawableOfDefaultView(FlutterEngine* engine,
2828
// operates on the default view. To support multi-view, we need a new callback
2929
// that also receives a view ID.
3030
uint64_t viewId = kFlutterDefaultViewId;
31-
return [engine.renderer present:viewId];
31+
return [engine.renderer present:viewId withBlock:nil];
3232
}
3333

3434
static bool OnAcquireExternalTexture(FlutterEngine* engine,
@@ -104,12 +104,12 @@ - (FlutterMetalTexture)createTextureForView:(uint64_t)viewId size:(CGSize)size {
104104
return embedderTexture;
105105
}
106106

107-
- (BOOL)present:(uint64_t)viewId {
107+
- (BOOL)present:(uint64_t)viewId withBlock:(dispatch_block_t)block {
108108
FlutterView* view = [_viewProvider getView:viewId];
109109
if (view == nil) {
110110
return NO;
111111
}
112-
[view present];
112+
[view presentWithBlock:block];
113113
return YES;
114114
}
115115

shell/platform/darwin/macos/framework/Source/FlutterRendererTest.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ void SetEngineDefaultView(FlutterEngine* engine, id flutterView) {
3939
FlutterRenderer* renderer = [[FlutterRenderer alloc] initWithFlutterEngine:engine];
4040
id mockFlutterView = OCMClassMock([FlutterView class]);
4141
SetEngineDefaultView(engine, mockFlutterView);
42-
[(FlutterView*)[mockFlutterView expect] present];
43-
[renderer present:kFlutterDefaultViewId];
42+
[(FlutterView*)[mockFlutterView expect] presentWithBlock:nil];
43+
[renderer present:kFlutterDefaultViewId withBlock:nil];
4444
}
4545

4646
TEST(FlutterRenderer, TextureReturnedByFlutterView) {

shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@
7575
*/
7676
- (void)requestCommit;
7777

78+
/**
79+
* Called from rasterizer thread, will block until delegate resizeSynchronizerCommit:
80+
* method is called (on platform thread).
81+
*
82+
* The onCommit block will be invoked on platform thread during the core
83+
* animation transaction.
84+
*/
85+
- (void)requestCommitWithBlock:(nullable dispatch_block_t)onCommit;
86+
7887
/**
7988
* Called from view to notify the synchronizer that there are no Flutter frames
8089
* coming. Synchronizer must unblock main thread and not block until another

shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h"
66

7+
#import <QuartzCore/QuartzCore.h>
78
#include <mutex>
89

910
@interface FlutterResizeSynchronizer () {
@@ -38,6 +39,8 @@ @interface FlutterResizeSynchronizer () {
3839
BOOL _shuttingDown;
3940

4041
__weak id<FlutterResizeSynchronizerDelegate> _delegate;
42+
43+
dispatch_block_t _onCommit;
4144
}
4245
@end
4346

@@ -83,7 +86,16 @@ - (void)beginResize:(CGSize)size notify:(dispatch_block_t)notify {
8386
_condBlockRequestCommit.wait(lock, [&] { return _pendingCommit || _shuttingDown; });
8487

8588
[_delegate resizeSynchronizerFlush:self];
89+
90+
[CATransaction begin];
91+
[CATransaction setDisableActions:YES];
8692
[_delegate resizeSynchronizerCommit:self];
93+
if (_onCommit) {
94+
_onCommit();
95+
}
96+
_onCommit = nil;
97+
[CATransaction commit];
98+
8799
_pendingCommit = NO;
88100
_condBlockBeginResize.notify_all();
89101

@@ -106,6 +118,10 @@ - (BOOL)shouldEnsureSurfaceForSize:(CGSize)size {
106118
}
107119

108120
- (void)requestCommit {
121+
[self requestCommitWithBlock:nil];
122+
}
123+
124+
- (void)requestCommitWithBlock:(dispatch_block_t)onCommit {
109125
std::unique_lock<std::mutex> lock(_mutex);
110126

111127
if (!_acceptingCommit || _shuttingDown) {
@@ -116,18 +132,25 @@ - (void)requestCommit {
116132

117133
_pendingCommit = YES;
118134
if (_waiting) { // BeginResize is in progress, interrupt it and schedule commit call
135+
_onCommit = onCommit;
119136
_condBlockRequestCommit.notify_all();
120137
_condBlockBeginResize.wait(lock, [&]() { return !_pendingCommit || _shuttingDown; });
121138
} else {
122139
// No resize, schedule commit on platform thread and wait until either done
123140
// or interrupted by incoming BeginResize
124141
[_delegate resizeSynchronizerFlush:self];
125-
dispatch_async(dispatch_get_main_queue(), [self, cookie = _cookie] {
142+
dispatch_async(dispatch_get_main_queue(), [self, cookie = _cookie, onCommit] {
126143
std::unique_lock<std::mutex> lock(_mutex);
127144
if (cookie == _cookie) {
145+
[CATransaction begin];
146+
[CATransaction setDisableActions:YES];
128147
if (_delegate) {
129148
[_delegate resizeSynchronizerCommit:self];
130149
}
150+
if (onCommit) {
151+
onCommit();
152+
}
153+
[CATransaction commit];
131154
_pendingCommit = NO;
132155
_condBlockBeginResize.notify_all();
133156
}

shell/platform/darwin/macos/framework/Source/FlutterView.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,13 @@ constexpr uint64_t kFlutterDefaultViewId = 0;
4949
- (nonnull instancetype)init NS_UNAVAILABLE;
5050

5151
/**
52-
* Flushes the graphics context and flips the surfaces. Expected to be called on raster thread.
52+
* Flushes the OpenGL context and flips the surfaces. Expected to be called
53+
* on raster thread. Blocks until the operation is complete.
54+
*
55+
* The onCommit block will be invoked on platform thread during the core
56+
* animation transaction;
5357
*/
54-
- (void)present;
58+
- (void)presentWithBlock:(nullable dispatch_block_t)onCommit;
5559

5660
/**
5761
* Called when there is no Flutter content available to render. This must be passed to resize

shell/platform/darwin/macos/framework/Source/FlutterView.mm

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ - (FlutterRenderBackingStore*)backingStoreForSize:(CGSize)size {
4545
return [_resizableBackingStoreProvider backingStore];
4646
}
4747

48-
- (void)present {
49-
[_resizeSynchronizer requestCommit];
48+
- (void)presentWithBlock:(dispatch_block_t)onCommit {
49+
[_resizeSynchronizer requestCommitWithBlock:onCommit];
5050
}
5151

5252
- (void)presentWithoutContent {

0 commit comments

Comments
 (0)