Skip to content

Commit

Permalink
Smooth resizing for macOS embedder
Browse files Browse the repository at this point in the history
  • Loading branch information
knopp committed Sep 30, 2020
1 parent 15fe4b9 commit d97ebaa
Show file tree
Hide file tree
Showing 8 changed files with 363 additions and 16 deletions.
6 changes: 6 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 Down Expand Up @@ -81,6 +85,8 @@ source_set("flutter_framework_source") {
libs = [
"Cocoa.framework",
"CoreVideo.framework",
"IOSurface.framework",
"QuartzCore.framework",
]
}

Expand Down
20 changes: 12 additions & 8 deletions shell/platform/darwin/macos/framework/Source/FlutterEngine.mm
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,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 @@ -230,7 +230,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 @@ -287,8 +288,8 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
}

[self sendUserLocales];
[self updateWindowMetrics];
[self updateDisplayConfig];
[self.viewController.flutterView start];
return YES;
}

Expand All @@ -299,7 +300,9 @@ - (void)setViewController:(FlutterViewController*)controller {
[self shutDownEngine];
_resourceContext = nil;
}
[self updateWindowMetrics];
if (_engine) {
[self.viewController.flutterView start];
}
}

- (id<FlutterBinaryMessenger>)binaryMessenger {
Expand All @@ -317,7 +320,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 @@ -350,6 +353,7 @@ - (void)updateDisplayConfig {
CVDisplayLinkRelease(displayLinkRef);
}

// Must be driven by FlutterView (i.e. [FlutterView start])
- (void)updateWindowMetrics {
if (!_engine) {
return;
Expand Down Expand Up @@ -413,7 +417,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,34 @@
#import <Cocoa/Cocoa.h>

@class FlutterResizeSynchronizer;

@protocol FlutterResizeSynchronizerDelegate

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

@end

// Encapsulates the logic for blocking platform thread during window resize
@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;
- (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,97 @@
#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 condRun;
std::condition_variable condBlock;
bool accepting;
bool waiting;
bool pendingCommit;
CGSize newSize;
__weak id<FlutterResizeSynchronizerDelegate> delegate;
}
@end

@implementation FlutterResizeSynchronizer

- (instancetype)initWithDelegate:(id<FlutterResizeSynchronizerDelegate>)delegate_ {
if (self = [super init]) {
accepting = 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
accepting = false;

// let pending commits finish to unblock the raster thread
condRun.notify_all();

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

newSize = size;

waiting = true;

condBlock.wait(lock);

if (pendingCommit) {
[delegate resizeSynchronizerCommit:self];
pendingCommit = false;
condRun.notify_all();
}

waiting = false;
}

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

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

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

@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
119 changes: 119 additions & 0 deletions shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"

#include <OpenGL/gl.h>

enum {
kFront = 0,
kBack = 1,
kBufferCount,
};

@interface FlutterSurfaceManager () {
CGSize surfaceSize;
CALayer* layer;
NSOpenGLContext* openGLContext;
uint32_t _frameBufferId[kBufferCount];
uint32_t _backingTexture[kBufferCount];
IOSurfaceRef _ioSurface[kBufferCount];
}
@end

@implementation FlutterSurfaceManager

- (instancetype)initWithLayer:(CALayer*)layer_ openGLContext:(NSOpenGLContext*)opengLContext_ {
if (self = [super init]) {
layer = layer_;
openGLContext = opengLContext_;

NSOpenGLContext* prev = [NSOpenGLContext currentContext];
[openGLContext makeCurrentContext];
glGenFramebuffers(2, _frameBufferId);
glGenTextures(2, _backingTexture);

glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferId[0]);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, _backingTexture[0]);
glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);

glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferId[1]);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, _backingTexture[1]);
glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);

if (prev) {
[prev makeCurrentContext];
} else {
[NSOpenGLContext clearCurrentContext];
}
}
return self;
}

- (void)ensureSurfaceSize:(CGSize)size {
if (CGSizeEqualToSize(size, surfaceSize)) {
return;
}
surfaceSize = size;
NSOpenGLContext* prev = [NSOpenGLContext currentContext];
[openGLContext makeCurrentContext];

for (int i = 0; i < 2; ++i) {
if (_ioSurface[i]) {
CFRelease(_ioSurface[i]);
}
unsigned pixelFormat = 'BGRA';
unsigned bytesPerElement = 4;

size_t bytesPerRow =
IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width * bytesPerElement);
size_t totalBytes = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height * bytesPerRow);
NSDictionary* options = @{
(id)kIOSurfaceWidth : @(size.width),
(id)kIOSurfaceHeight : @(size.height),
(id)kIOSurfacePixelFormat : @(pixelFormat),
(id)kIOSurfaceBytesPerElement : @(bytesPerElement),
(id)kIOSurfaceBytesPerRow : @(bytesPerRow),
(id)kIOSurfaceAllocSize : @(totalBytes),
};
_ioSurface[i] = IOSurfaceCreate((CFDictionaryRef)options);

glBindTexture(GL_TEXTURE_RECTANGLE_ARB, _backingTexture[i]);

CGLTexImageIOSurface2D(CGLGetCurrentContext(), GL_TEXTURE_RECTANGLE_ARB, GL_RGBA,
int(size.width), int(size.height), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
_ioSurface[i], 0 /* plane */);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0);

glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferId[i]);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB,
_backingTexture[i], 0);
}
if (prev) {
[prev makeCurrentContext];
} else {
[NSOpenGLContext clearCurrentContext];
}
}

- (void)swapBuffers {
[layer setContents:(__bridge id)_ioSurface[kBack]];
std::swap(_ioSurface[kBack], _ioSurface[kFront]);
std::swap(_frameBufferId[kBack], _frameBufferId[kFront]);
std::swap(_backingTexture[kBack], _backingTexture[kFront]);
}

- (uint32_t)glFrameBufferId {
return _frameBufferId[kBack];
}

- (void)dealloc {
for (int i = 0; i < kBufferCount; ++i) {
if (_ioSurface[i]) {
CFRelease(_ioSurface[i]);
}
}
}

@end
8 changes: 7 additions & 1 deletion shell/platform/darwin/macos/framework/Source/FlutterView.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
* View capable of acting as a rendering target and input source for the Flutter
* engine.
*/
@interface FlutterView : NSOpenGLView
@interface FlutterView : NSView

@property(readwrite, nonatomic, nonnull) NSOpenGLContext* openGLContext;

- (nullable instancetype)initWithFrame:(NSRect)frame
shareContext:(nonnull NSOpenGLContext*)shareContext
Expand All @@ -35,4 +37,8 @@
- (nullable instancetype)initWithCoder:(nonnull NSCoder*)coder NS_UNAVAILABLE;
- (nonnull instancetype)init NS_UNAVAILABLE;

- (void)start;
- (void)present;
- (int)getFrameBufferIdForSize:(CGSize)size;

@end
Loading

0 comments on commit d97ebaa

Please sign in to comment.