Skip to content

Commit e61e8c2

Browse files
authored
Smooth window resizing on macOS (flutter#21525)
1 parent e5f168a commit e61e8c2

File tree

11 files changed

+442
-16
lines changed

11 files changed

+442
-16
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,10 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExter
10471047
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm
10481048
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
10491049
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
1050+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h
1051+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm
1052+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h
1053+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm
10501054
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.h
10511055
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.mm
10521056
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h
@@ -1056,6 +1060,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterView.
10561060
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm
10571061
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm
10581062
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h
1063+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/MacOSSwitchableGLContext.h
1064+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/MacOSSwitchableGLContext.mm
10591065
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart
10601066
FILE: ../../../flutter/shell/platform/darwin/macos/framework/module.modulemap
10611067
FILE: ../../../flutter/shell/platform/embedder/assets/EmbedderInfo.plist

shell/platform/darwin/macos/BUILD.gn

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ source_set("flutter_framework_source") {
5454
"framework/Source/FlutterExternalTextureGL.mm",
5555
"framework/Source/FlutterMouseCursorPlugin.h",
5656
"framework/Source/FlutterMouseCursorPlugin.mm",
57+
"framework/Source/FlutterResizeSynchronizer.h",
58+
"framework/Source/FlutterResizeSynchronizer.mm",
59+
"framework/Source/FlutterSurfaceManager.h",
60+
"framework/Source/FlutterSurfaceManager.mm",
5761
"framework/Source/FlutterTextInputModel.h",
5862
"framework/Source/FlutterTextInputModel.mm",
5963
"framework/Source/FlutterTextInputPlugin.h",
@@ -62,11 +66,15 @@ source_set("flutter_framework_source") {
6266
"framework/Source/FlutterView.mm",
6367
"framework/Source/FlutterViewController.mm",
6468
"framework/Source/FlutterViewController_Internal.h",
69+
"framework/Source/MacOSSwitchableGLContext.h",
70+
"framework/Source/MacOSSwitchableGLContext.mm",
6571
]
6672

6773
sources += _flutter_framework_headers
6874

6975
deps = [
76+
"//flutter/flow:flow",
77+
"//flutter/fml:fml",
7078
"//flutter/shell/platform/common/cpp:common_cpp_switches",
7179
"//flutter/shell/platform/darwin/common:framework_shared",
7280
"//flutter/shell/platform/embedder:embedder_as_internal_library",
@@ -81,6 +89,8 @@ source_set("flutter_framework_source") {
8189
libs = [
8290
"Cocoa.framework",
8391
"CoreVideo.framework",
92+
"IOSurface.framework",
93+
"QuartzCore.framework",
8494
]
8595
}
8696

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,9 @@ static bool OnPresent(FlutterEngine* engine) {
154154
return [engine engineCallbackOnPresent];
155155
}
156156

157-
static uint32_t OnFBO(FlutterEngine* engine) {
158-
// There is currently no case where a different FBO is used, so no need to forward.
159-
return 0;
157+
static uint32_t OnFBO(FlutterEngine* engine, const FlutterFrameInfo* info) {
158+
CGSize size = CGSizeMake(info->size.width, info->size.height);
159+
return [engine.viewController.flutterView getFrameBufferIdForSize:size];
160160
}
161161

162162
static bool OnMakeResourceCurrent(FlutterEngine* engine) {
@@ -248,7 +248,8 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
248248
.open_gl.make_current = (BoolCallback)OnMakeCurrent,
249249
.open_gl.clear_current = (BoolCallback)OnClearCurrent,
250250
.open_gl.present = (BoolCallback)OnPresent,
251-
.open_gl.fbo_callback = (UIntCallback)OnFBO,
251+
.open_gl.fbo_with_frame_info_callback = (UIntFrameInfoCallback)OnFBO,
252+
.open_gl.fbo_reset_after_present = true,
252253
.open_gl.make_resource_current = (BoolCallback)OnMakeResourceCurrent,
253254
.open_gl.gl_external_texture_frame_callback = (TextureFrameCallback)OnAcquireExternalTexture,
254255
};
@@ -297,7 +298,6 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
297298
const FlutterCustomTaskRunners custom_task_runners = {
298299
.struct_size = sizeof(FlutterCustomTaskRunners),
299300
.platform_task_runner = &cocoa_task_runner_description,
300-
.render_task_runner = &cocoa_task_runner_description,
301301
};
302302
flutterArguments.custom_task_runners = &custom_task_runners;
303303

@@ -322,6 +322,7 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
322322
[self sendUserLocales];
323323
[self updateWindowMetrics];
324324
[self updateDisplayConfig];
325+
self.viewController.flutterView.synchronousResizing = YES;
325326
return YES;
326327
}
327328

@@ -362,7 +363,9 @@ - (void)setViewController:(FlutterViewController*)controller {
362363
[self shutDownEngine];
363364
_resourceContext = nil;
364365
}
365-
[self updateWindowMetrics];
366+
if (_engine) {
367+
self.viewController.flutterView.synchronousResizing = YES;
368+
}
366369
}
367370

368371
- (id<FlutterBinaryMessenger>)binaryMessenger {
@@ -380,7 +383,7 @@ - (BOOL)running {
380383
- (NSOpenGLContext*)resourceContext {
381384
if (!_resourceContext) {
382385
NSOpenGLPixelFormatAttribute attributes[] = {
383-
NSOpenGLPFAColorSize, 24, NSOpenGLPFAAlphaSize, 8, NSOpenGLPFADoubleBuffer, 0,
386+
NSOpenGLPFAColorSize, 24, NSOpenGLPFAAlphaSize, 8, 0,
384387
};
385388
NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
386389
_resourceContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
@@ -479,7 +482,7 @@ - (bool)engineCallbackOnPresent {
479482
if (!_mainOpenGLContext) {
480483
return false;
481484
}
482-
[_mainOpenGLContext flushBuffer];
485+
[self.viewController.flutterView present];
483486
return true;
484487
}
485488

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#import <Cocoa/Cocoa.h>
2+
3+
@class FlutterResizeSynchronizer;
4+
5+
@protocol FlutterResizeSynchronizerDelegate
6+
7+
// Invoked on raster thread; Delegate should flush the OpenGL context
8+
- (void)resizeSynchronizerFlush:(FlutterResizeSynchronizer*)synchronizer;
9+
10+
// Invoked on platform thread; Delegate should flip the surfaces
11+
- (void)resizeSynchronizerCommit:(FlutterResizeSynchronizer*)synchronizer;
12+
13+
@end
14+
15+
// Encapsulates the logic for blocking platform thread during window resize as
16+
// well as synchronizing the raster and platform thread during commit (presenting frame)
17+
//
18+
// Flow during window resize
19+
//
20+
// 1. Platform thread calls [synchronizer beginResize:notify:]
21+
// This will hold the platform thread until we're ready to display contents.
22+
// 2. Raster thread calls [synchronizer shouldEnsureSurfaceForSize:] with target size
23+
// This will return false for any size other than target size
24+
// 3. Raster thread calls [synchronizer requestCommit]
25+
// Any commit calls before shouldEnsureSurfaceForSize: is called with the right
26+
// size are simply ignored; There's no point rasterizing and displaying frames
27+
// with wrong size.
28+
// Both delegate methods (flush/commit) will be invoked before beginResize returns
29+
//
30+
// Flow during regular operation (no resizing)
31+
//
32+
// 1. Raster thread calls [synchronizer requestCommit]
33+
// This will invoke [delegate flush:] on raster thread and
34+
// [delegate commit:] on platform thread. The requestCommit call will be blocked
35+
// until this is done. This is necessary to ensure that rasterizer won't start
36+
// rasterizing next frame before we flipped the surface, which must be performed
37+
// on platform thread
38+
@interface FlutterResizeSynchronizer : NSObject
39+
40+
- (instancetype)initWithDelegate:(id<FlutterResizeSynchronizerDelegate>)delegate;
41+
42+
// Blocks the platform thread until
43+
// - shouldEnsureSurfaceForSize is called with proper size and
44+
// - requestCommit is called
45+
// All requestCommit calls before `shouldEnsureSurfaceForSize` is called with
46+
// expected size are ignored;
47+
// The notify block is invoked immediately after synchronizer mutex is acquired
48+
- (void)beginResize:(CGSize)size notify:(dispatch_block_t)notify;
49+
50+
// Returns whether the view should ensure surfaces with given size;
51+
// This will be false during resizing for any size other than size specified
52+
// during beginResize
53+
- (bool)shouldEnsureSurfaceForSize:(CGSize)size;
54+
55+
// Called from rasterizer thread, will block until delegate resizeSynchronizerCommit:
56+
// method is called (on platform thread)
57+
- (void)requestCommit;
58+
59+
@end
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h"
2+
3+
#import <mutex>
4+
5+
@interface FlutterResizeSynchronizer () {
6+
uint32_t cookie; // counter to detect stale callbacks
7+
8+
std::mutex mutex;
9+
std::condition_variable condBlockBeginResize; // used to block [beginResize:]
10+
std::condition_variable condBlockRequestCommit; // used to block [requestCommit]
11+
12+
bool acceptingCommit; // if false, requestCommit calls are ignored until
13+
// shouldEnsureSurfaceForSize is called with proper size
14+
bool waiting; // waiting for resize to finish
15+
bool pendingCommit; // requestCommit was called and [delegate commit:] must be performed on
16+
// platform thread
17+
CGSize newSize; // target size for resizing
18+
19+
__weak id<FlutterResizeSynchronizerDelegate> delegate;
20+
}
21+
@end
22+
23+
@implementation FlutterResizeSynchronizer
24+
25+
- (instancetype)initWithDelegate:(id<FlutterResizeSynchronizerDelegate>)delegate_ {
26+
if (self = [super init]) {
27+
acceptingCommit = true;
28+
delegate = delegate_;
29+
}
30+
return self;
31+
}
32+
33+
- (void)beginResize:(CGSize)size notify:(dispatch_block_t)notify {
34+
std::unique_lock<std::mutex> lock(mutex);
35+
if (!delegate) {
36+
return;
37+
}
38+
39+
++cookie;
40+
41+
// from now on, ignore all incoming commits until the block below gets
42+
// scheduled on raster thread
43+
acceptingCommit = false;
44+
45+
// let pending commits finish to unblock the raster thread
46+
pendingCommit = false;
47+
condBlockBeginResize.notify_all();
48+
49+
// let the engine send resize notification
50+
notify();
51+
52+
newSize = size;
53+
54+
waiting = true;
55+
56+
condBlockRequestCommit.wait(lock, [&] { return pendingCommit; });
57+
58+
[delegate resizeSynchronizerFlush:self];
59+
[delegate resizeSynchronizerCommit:self];
60+
pendingCommit = false;
61+
condBlockBeginResize.notify_all();
62+
63+
waiting = false;
64+
}
65+
66+
- (bool)shouldEnsureSurfaceForSize:(CGSize)size {
67+
std::unique_lock<std::mutex> lock(mutex);
68+
if (!acceptingCommit) {
69+
if (CGSizeEqualToSize(newSize, size)) {
70+
acceptingCommit = true;
71+
}
72+
}
73+
return acceptingCommit;
74+
}
75+
76+
- (void)requestCommit {
77+
std::unique_lock<std::mutex> lock(mutex);
78+
if (!acceptingCommit) {
79+
return;
80+
}
81+
82+
pendingCommit = true;
83+
if (waiting) { // BeginResize is in progress, interrupt it and schedule commit call
84+
condBlockRequestCommit.notify_all();
85+
condBlockBeginResize.wait(lock, [&]() { return !pendingCommit; });
86+
} else {
87+
// No resize, schedule commit on platform thread and wait until either done
88+
// or interrupted by incoming BeginResize
89+
[delegate resizeSynchronizerFlush:self];
90+
dispatch_async(dispatch_get_main_queue(), [self, cookie_ = cookie] {
91+
std::unique_lock<std::mutex> lock(mutex);
92+
if (cookie_ == cookie) {
93+
if (delegate) {
94+
[delegate resizeSynchronizerCommit:self];
95+
}
96+
pendingCommit = false;
97+
condBlockBeginResize.notify_all();
98+
}
99+
});
100+
condBlockBeginResize.wait(lock, [&]() { return !pendingCommit; });
101+
}
102+
}
103+
104+
@end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#import <Cocoa/Cocoa.h>
2+
3+
// Manages the IOSurfaces for FlutterView
4+
@interface FlutterSurfaceManager : NSObject
5+
6+
- (instancetype)initWithLayer:(CALayer*)layer openGLContext:(NSOpenGLContext*)opengLContext;
7+
8+
- (void)ensureSurfaceSize:(CGSize)size;
9+
- (void)swapBuffers;
10+
11+
- (uint32_t)glFrameBufferId;
12+
13+
@end

0 commit comments

Comments
 (0)