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

Commit 0727db3

Browse files
committed
macOS: Fix platform view threading issues
1 parent 83c4c95 commit 0727db3

12 files changed

+93
-32
lines changed

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ class FlutterCompositor {
5555
// Present sets frame_started_ to false.
5656
bool Present(const FlutterLayer** layers, size_t layers_count);
5757

58-
using PresentCallback = std::function<bool(bool has_flutter_content)>;
58+
using PresentCallback =
59+
std::function<bool(bool has_flutter_content,
60+
dispatch_block_t platfrom_thread_notify)>;
5961

6062
// PresentCallback is called at the end of the Present function.
6163
void SetPresentCallback(const PresentCallback& present_callback);
@@ -80,18 +82,22 @@ class FlutterCompositor {
8082
void SetFrameStatus(FrameStatus frame_status);
8183
FrameStatus GetFrameStatus();
8284

83-
// Clears the previous CALayers and updates the frame status to frame started.
85+
// Sets frame status to frame started.
8486
void StartFrame();
8587

88+
// Clears the previous CALayers.
89+
void RemoveOldLayers();
90+
8691
// Calls the present callback and ensures the frame status is updated
8792
// to frame ended, returning whether the present was successful or not.
88-
bool EndFrame(bool has_flutter_content);
93+
bool EndFrame(bool has_flutter_content, dispatch_block_t on_notify);
8994

9095
// Creates a CALayer object which is backed by the supplied IOSurface, and
9196
// adds it to the root CALayer for the given view.
9297
void InsertCALayerForIOSurface(
9398
FlutterView* view,
9499
const IOSurfaceRef& io_surface,
100+
size_t layer_position,
95101
CATransform3D transform = CATransform3DIdentity);
96102

97103
private:

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

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

8383
bool has_flutter_content = false;
84+
85+
std::vector<dispatch_block_t> actions;
86+
actions.push_back(^{
87+
RemoveOldLayers();
88+
});
89+
8490
for (size_t i = 0; i < layers_count; ++i) {
8591
const auto* layer = layers[i];
8692
FlutterBackingStore* backing_store = const_cast<FlutterBackingStore*>(layer->backing_store);
@@ -91,19 +97,27 @@
9197
FlutterIOSurfaceHolder* io_surface_holder =
9298
(__bridge FlutterIOSurfaceHolder*)backing_store->metal.texture.user_data;
9399
IOSurfaceRef io_surface = [io_surface_holder ioSurface];
94-
InsertCALayerForIOSurface(view, io_surface);
100+
actions.push_back(^{
101+
InsertCALayerForIOSurface(view, io_surface, i);
102+
});
95103
}
96104
has_flutter_content = true;
97105
break;
98106
}
99107
case kFlutterLayerContentTypePlatformView: {
100-
PresentPlatformView(view, layer, i);
108+
actions.push_back(^{
109+
PresentPlatformView(view, layer, i);
110+
});
101111
break;
102112
}
103113
};
104114
}
105115

106-
return EndFrame(has_flutter_content);
116+
return EndFrame(has_flutter_content, ^{
117+
for (auto& action : actions) {
118+
action();
119+
}
120+
});
107121
}
108122

109123
void FlutterCompositor::PresentPlatformView(FlutterView* default_base_view,
@@ -144,18 +158,21 @@
144158
}
145159

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

152170
// Reset active layers.
153171
active_ca_layers_.clear();
154-
SetFrameStatus(FrameStatus::kStarted);
155172
}
156173

157-
bool FlutterCompositor::EndFrame(bool has_flutter_content) {
158-
bool status = present_callback_(has_flutter_content);
174+
bool FlutterCompositor::EndFrame(bool has_flutter_content, dispatch_block_t on_notify) {
175+
bool status = present_callback_(has_flutter_content, on_notify);
159176
SetFrameStatus(FrameStatus::kEnded);
160177
return status;
161178
}
@@ -174,14 +191,15 @@
174191

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

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

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

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

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
#include <iostream>
1010
#include <vector>
1111

12+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
1213
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
1314
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.h"
14-
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
1515
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h"
1616
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h"
1717
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h"
@@ -430,14 +430,15 @@ - (FlutterCompositor*)createFlutterCompositor {
430430
FlutterMetalRenderer* metalRenderer = reinterpret_cast<FlutterMetalRenderer*>(_renderer);
431431
_macOSCompositor = std::make_unique<flutter::FlutterCompositor>(
432432
_viewProvider, _platformViewController, metalRenderer.device);
433-
_macOSCompositor->SetPresentCallback([weakSelf](bool has_flutter_content) {
434-
if (has_flutter_content) {
435-
return [weakSelf.renderer present] == YES;
436-
} else {
437-
[weakSelf.renderer presentWithoutContent];
438-
return true;
439-
}
440-
});
433+
_macOSCompositor->SetPresentCallback(
434+
[weakSelf](bool has_flutter_content, dispatch_block_t on_notify) {
435+
if (has_flutter_content) {
436+
return [weakSelf.renderer presentWithBlock:on_notify] == YES;
437+
} else {
438+
[weakSelf.renderer presentWithoutContent];
439+
return true;
440+
}
441+
});
441442

442443
_compositor = {};
443444
_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
@@ -473,7 +473,7 @@ @interface FlutterEngine (Test)
473473
// Latch to ensure the entire layer tree has been generated and presented.
474474
fml::AutoResetWaitableEvent latch;
475475
auto compositor = engine.macOSCompositor;
476-
compositor->SetPresentCallback([&](bool has_flutter_content) {
476+
compositor->SetPresentCallback([&](bool has_flutter_content, dispatch_block_t completion) {
477477
latch.Signal();
478478
return true;
479479
});

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ static FlutterMetalTexture OnGetNextDrawable(FlutterEngine* engine,
2020
}
2121

2222
static bool OnPresentDrawable(FlutterEngine* engine, const FlutterMetalTexture* texture) {
23-
return [engine.renderer present];
23+
return [engine.renderer presentWithBlock:nil];
2424
}
2525

2626
static bool OnAcquireExternalTexture(FlutterEngine* engine,
@@ -94,11 +94,11 @@ - (FlutterMetalTexture)createTextureForSize:(CGSize)size {
9494
return embedderTexture;
9595
}
9696

97-
- (BOOL)present {
97+
- (BOOL)presentWithBlock:(dispatch_block_t)block {
9898
if (!_flutterView) {
9999
return NO;
100100
}
101-
[_flutterView present];
101+
[_flutterView presentWithBlock:block];
102102
return YES;
103103
}
104104

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
FlutterEngine* engine = CreateTestEngine();
3131
FlutterMetalRenderer* renderer = [[FlutterMetalRenderer alloc] initWithFlutterEngine:engine];
3232
id mockFlutterView = OCMClassMock([FlutterView class]);
33-
[(FlutterView*)[mockFlutterView expect] present];
33+
[(FlutterView*)[mockFlutterView expect] presentWithBlock:nil];
3434
[renderer setFlutterView:mockFlutterView];
35-
[renderer present];
35+
[renderer presentWithBlock:nil];
3636
}
3737

3838
TEST(FlutterMetalRenderer, TextureReturnedByFlutterView) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
/**
3333
* Called by the engine when the context's buffers should be swapped.
3434
*/
35-
- (BOOL)present;
35+
- (BOOL)presentWithBlock:(nullable dispatch_block_t)block;
3636

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

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)