Skip to content

Commit

Permalink
Smooth window resizing on macOS (flutter#21525)
Browse files Browse the repository at this point in the history
  • Loading branch information
knopp authored and chaselatta committed Nov 30, 2020
1 parent 818007c commit 3c883a8
Show file tree
Hide file tree
Showing 11 changed files with 442 additions and 16 deletions.
6 changes: 6 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,10 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExter
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputModel.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h
Expand All @@ -1056,6 +1060,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterView.
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/MacOSSwitchableGLContext.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/MacOSSwitchableGLContext.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart
FILE: ../../../flutter/shell/platform/darwin/macos/framework/module.modulemap
FILE: ../../../flutter/shell/platform/embedder/assets/EmbedderInfo.plist
Expand Down
10 changes: 10 additions & 0 deletions shell/platform/darwin/macos/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterExternalTextureGL.mm",
"framework/Source/FlutterMouseCursorPlugin.h",
"framework/Source/FlutterMouseCursorPlugin.mm",
"framework/Source/FlutterResizeSynchronizer.h",
"framework/Source/FlutterResizeSynchronizer.mm",
"framework/Source/FlutterSurfaceManager.h",
"framework/Source/FlutterSurfaceManager.mm",
"framework/Source/FlutterTextInputModel.h",
"framework/Source/FlutterTextInputModel.mm",
"framework/Source/FlutterTextInputPlugin.h",
Expand All @@ -62,11 +66,15 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterView.mm",
"framework/Source/FlutterViewController.mm",
"framework/Source/FlutterViewController_Internal.h",
"framework/Source/MacOSSwitchableGLContext.h",
"framework/Source/MacOSSwitchableGLContext.mm",
]

sources += _flutter_framework_headers

deps = [
"//flutter/flow:flow",
"//flutter/fml:fml",
"//flutter/shell/platform/common/cpp:common_cpp_switches",
"//flutter/shell/platform/darwin/common:framework_shared",
"//flutter/shell/platform/embedder:embedder_as_internal_library",
Expand All @@ -81,6 +89,8 @@ source_set("flutter_framework_source") {
libs = [
"Cocoa.framework",
"CoreVideo.framework",
"IOSurface.framework",
"QuartzCore.framework",
]
}

Expand Down
19 changes: 11 additions & 8 deletions shell/platform/darwin/macos/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,9 @@ static bool OnPresent(FlutterEngine* engine) {
return [engine engineCallbackOnPresent];
}

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

static bool OnMakeResourceCurrent(FlutterEngine* engine) {
Expand Down Expand Up @@ -248,7 +248,8 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
.open_gl.make_current = (BoolCallback)OnMakeCurrent,
.open_gl.clear_current = (BoolCallback)OnClearCurrent,
.open_gl.present = (BoolCallback)OnPresent,
.open_gl.fbo_callback = (UIntCallback)OnFBO,
.open_gl.fbo_with_frame_info_callback = (UIntFrameInfoCallback)OnFBO,
.open_gl.fbo_reset_after_present = true,
.open_gl.make_resource_current = (BoolCallback)OnMakeResourceCurrent,
.open_gl.gl_external_texture_frame_callback = (TextureFrameCallback)OnAcquireExternalTexture,
};
Expand Down Expand Up @@ -297,7 +298,6 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
const FlutterCustomTaskRunners custom_task_runners = {
.struct_size = sizeof(FlutterCustomTaskRunners),
.platform_task_runner = &cocoa_task_runner_description,
.render_task_runner = &cocoa_task_runner_description,
};
flutterArguments.custom_task_runners = &custom_task_runners;

Expand All @@ -322,6 +322,7 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
[self sendUserLocales];
[self updateWindowMetrics];
[self updateDisplayConfig];
self.viewController.flutterView.synchronousResizing = YES;
return YES;
}

Expand Down Expand Up @@ -362,7 +363,9 @@ - (void)setViewController:(FlutterViewController*)controller {
[self shutDownEngine];
_resourceContext = nil;
}
[self updateWindowMetrics];
if (_engine) {
self.viewController.flutterView.synchronousResizing = YES;
}
}

- (id<FlutterBinaryMessenger>)binaryMessenger {
Expand All @@ -380,7 +383,7 @@ - (BOOL)running {
- (NSOpenGLContext*)resourceContext {
if (!_resourceContext) {
NSOpenGLPixelFormatAttribute attributes[] = {
NSOpenGLPFAColorSize, 24, NSOpenGLPFAAlphaSize, 8, NSOpenGLPFADoubleBuffer, 0,
NSOpenGLPFAColorSize, 24, NSOpenGLPFAAlphaSize, 8, 0,
};
NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
_resourceContext = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
Expand Down Expand Up @@ -479,7 +482,7 @@ - (bool)engineCallbackOnPresent {
if (!_mainOpenGLContext) {
return false;
}
[_mainOpenGLContext flushBuffer];
[self.viewController.flutterView present];
return true;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#import <Cocoa/Cocoa.h>

@class FlutterResizeSynchronizer;

@protocol FlutterResizeSynchronizerDelegate

// Invoked on raster thread; Delegate should flush the OpenGL context
- (void)resizeSynchronizerFlush:(FlutterResizeSynchronizer*)synchronizer;

// Invoked on platform thread; Delegate should flip the surfaces
- (void)resizeSynchronizerCommit:(FlutterResizeSynchronizer*)synchronizer;

@end

// Encapsulates the logic for blocking platform thread during window resize as
// well as synchronizing the raster and platform thread during commit (presenting frame)
//
// Flow during window resize
//
// 1. Platform thread calls [synchronizer beginResize:notify:]
// This will hold the platform thread until we're ready to display contents.
// 2. Raster thread calls [synchronizer shouldEnsureSurfaceForSize:] with target size
// This will return false for any size other than target size
// 3. Raster thread calls [synchronizer requestCommit]
// Any commit calls before shouldEnsureSurfaceForSize: is called with the right
// size are simply ignored; There's no point rasterizing and displaying frames
// with wrong size.
// Both delegate methods (flush/commit) will be invoked before beginResize returns
//
// Flow during regular operation (no resizing)
//
// 1. Raster thread calls [synchronizer requestCommit]
// This will invoke [delegate flush:] on raster thread and
// [delegate commit:] on platform thread. The requestCommit call will be blocked
// until this is done. This is necessary to ensure that rasterizer won't start
// rasterizing next frame before we flipped the surface, which must be performed
// on platform thread
@interface FlutterResizeSynchronizer : NSObject

- (instancetype)initWithDelegate:(id<FlutterResizeSynchronizerDelegate>)delegate;

// Blocks the platform thread until
// - shouldEnsureSurfaceForSize is called with proper size and
// - requestCommit is called
// All requestCommit calls before `shouldEnsureSurfaceForSize` is called with
// expected size are ignored;
// The notify block is invoked immediately after synchronizer mutex is acquired
- (void)beginResize:(CGSize)size notify:(dispatch_block_t)notify;

// Returns whether the view should ensure surfaces with given size;
// This will be false during resizing for any size other than size specified
// during beginResize
- (bool)shouldEnsureSurfaceForSize:(CGSize)size;

// Called from rasterizer thread, will block until delegate resizeSynchronizerCommit:
// method is called (on platform thread)
- (void)requestCommit;

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h"

#import <mutex>

@interface FlutterResizeSynchronizer () {
uint32_t cookie; // counter to detect stale callbacks

std::mutex mutex;
std::condition_variable condBlockBeginResize; // used to block [beginResize:]
std::condition_variable condBlockRequestCommit; // used to block [requestCommit]

bool acceptingCommit; // if false, requestCommit calls are ignored until
// shouldEnsureSurfaceForSize is called with proper size
bool waiting; // waiting for resize to finish
bool pendingCommit; // requestCommit was called and [delegate commit:] must be performed on
// platform thread
CGSize newSize; // target size for resizing

__weak id<FlutterResizeSynchronizerDelegate> delegate;
}
@end

@implementation FlutterResizeSynchronizer

- (instancetype)initWithDelegate:(id<FlutterResizeSynchronizerDelegate>)delegate_ {
if (self = [super init]) {
acceptingCommit = true;
delegate = delegate_;
}
return self;
}

- (void)beginResize:(CGSize)size notify:(dispatch_block_t)notify {
std::unique_lock<std::mutex> lock(mutex);
if (!delegate) {
return;
}

++cookie;

// from now on, ignore all incoming commits until the block below gets
// scheduled on raster thread
acceptingCommit = false;

// let pending commits finish to unblock the raster thread
pendingCommit = false;
condBlockBeginResize.notify_all();

// let the engine send resize notification
notify();

newSize = size;

waiting = true;

condBlockRequestCommit.wait(lock, [&] { return pendingCommit; });

[delegate resizeSynchronizerFlush:self];
[delegate resizeSynchronizerCommit:self];
pendingCommit = false;
condBlockBeginResize.notify_all();

waiting = false;
}

- (bool)shouldEnsureSurfaceForSize:(CGSize)size {
std::unique_lock<std::mutex> lock(mutex);
if (!acceptingCommit) {
if (CGSizeEqualToSize(newSize, size)) {
acceptingCommit = true;
}
}
return acceptingCommit;
}

- (void)requestCommit {
std::unique_lock<std::mutex> lock(mutex);
if (!acceptingCommit) {
return;
}

pendingCommit = true;
if (waiting) { // BeginResize is in progress, interrupt it and schedule commit call
condBlockRequestCommit.notify_all();
condBlockBeginResize.wait(lock, [&]() { return !pendingCommit; });
} else {
// No resize, schedule commit on platform thread and wait until either done
// or interrupted by incoming BeginResize
[delegate resizeSynchronizerFlush:self];
dispatch_async(dispatch_get_main_queue(), [self, cookie_ = cookie] {
std::unique_lock<std::mutex> lock(mutex);
if (cookie_ == cookie) {
if (delegate) {
[delegate resizeSynchronizerCommit:self];
}
pendingCommit = false;
condBlockBeginResize.notify_all();
}
});
condBlockBeginResize.wait(lock, [&]() { return !pendingCommit; });
}
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#import <Cocoa/Cocoa.h>

// Manages the IOSurfaces for FlutterView
@interface FlutterSurfaceManager : NSObject

- (instancetype)initWithLayer:(CALayer*)layer openGLContext:(NSOpenGLContext*)opengLContext;

- (void)ensureSurfaceSize:(CGSize)size;
- (void)swapBuffers;

- (uint32_t)glFrameBufferId;

@end
Loading

0 comments on commit 3c883a8

Please sign in to comment.