[macOS] Refactor rendering infrastructure (flutter/engine#37789)

* [macos] Refactor rendering process

* Put includes and imports together

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

Co-authored-by: Chris Bracken <chris@bracken.jp>

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

Co-authored-by: Chris Bracken <chris@bracken.jp>

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

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.mm

Co-authored-by: Chris Bracken <chris@bracken.jp>

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

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Remove cast

* Do not manually add platform view layer to FlutterView

* Remove unnecesary _Internal header files

* Make surfaceManager a readonly property

* Add comment

* Style nit: Rewrite as a noun phrase.

* Naming nit for consistency with removeSurfaceForSize:

* Write as ternary conditional.

* Fix plural in comment.

* Offset and index are already set to 0.

* Remove FlutterSurfaceManager lookupSurface

And the associate bookkeeping of borrowed (lent) surfaces.

* Update shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.mm

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/darwin/macos/framework/Source/FlutterSurfaceManagerTest.mm

Co-authored-by: Chris Bracken <chris@bracken.jp>

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

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/darwin/macos/framework/Source/FlutterSurfaceManagerTest.mm

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/darwin/macos/framework/Source/FlutterSurfaceManagerTest.mm

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Update shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h

Co-authored-by: Chris Bracken <chris@bracken.jp>

* Fix build error

* Address nits

* Replace EVent with fml::AutoResetWaitableEvent

* Remove unecessary CATransaction

* Add GetRequiredFrameSize

Co-authored-by: Chris Bracken <chris@bracken.jp>
This commit is contained in:
Matej Knopp
2022-12-14 20:40:56 +01:00
committed by GitHub
parent 61f2e6b757
commit ed9d9bf372
26 changed files with 872 additions and 887 deletions

View File

@@ -2717,8 +2717,6 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngin
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTexture.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTexture.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm
@@ -2739,10 +2737,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatf
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterRendererTest.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizableBackingStoreProvider.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizableBackingStoreProvider.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/FlutterSurface.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterSurface.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/FlutterSurfaceManagerTest.mm
@@ -2754,6 +2750,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextI
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputSemanticsObjectTest.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextureRegistrar.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextureRegistrar.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterView.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm

View File

@@ -71,8 +71,6 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterEngine_Internal.h",
"framework/Source/FlutterExternalTexture.h",
"framework/Source/FlutterExternalTexture.mm",
"framework/Source/FlutterIOSurfaceHolder.h",
"framework/Source/FlutterIOSurfaceHolder.mm",
"framework/Source/FlutterKeyPrimaryResponder.h",
"framework/Source/FlutterKeyboardManager.h",
"framework/Source/FlutterKeyboardManager.mm",
@@ -88,10 +86,8 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterPlatformViewController.mm",
"framework/Source/FlutterRenderer.h",
"framework/Source/FlutterRenderer.mm",
"framework/Source/FlutterResizableBackingStoreProvider.h",
"framework/Source/FlutterResizableBackingStoreProvider.mm",
"framework/Source/FlutterResizeSynchronizer.h",
"framework/Source/FlutterResizeSynchronizer.mm",
"framework/Source/FlutterSurface.h",
"framework/Source/FlutterSurface.mm",
"framework/Source/FlutterSurfaceManager.h",
"framework/Source/FlutterSurfaceManager.mm",
"framework/Source/FlutterTextInputPlugin.h",
@@ -100,6 +96,8 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterTextInputSemanticsObject.mm",
"framework/Source/FlutterTextureRegistrar.h",
"framework/Source/FlutterTextureRegistrar.mm",
"framework/Source/FlutterThreadSynchronizer.h",
"framework/Source/FlutterThreadSynchronizer.mm",
"framework/Source/FlutterView.h",
"framework/Source/FlutterView.mm",
"framework/Source/FlutterViewController.mm",

View File

@@ -10,8 +10,8 @@
#include "flutter/fml/macros.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h"
#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewProvider.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewProvider.h"
#include "flutter/shell/platform/embedder/embedder.h"
namespace flutter {
@@ -28,8 +28,7 @@ class FlutterCompositor {
// It must not be null, and is typically FlutterViewEngineProvider.
explicit FlutterCompositor(
id<FlutterViewProvider> view_provider,
FlutterPlatformViewController* platform_views_controller,
id<MTLDevice> mtl_device);
FlutterPlatformViewController* platform_views_controller);
~FlutterCompositor() = default;
@@ -48,57 +47,12 @@ class FlutterCompositor {
bool CreateBackingStore(const FlutterBackingStoreConfig* config,
FlutterBackingStore* backing_store_out);
// Releases the memory for any resources that were allocated for the
// specified backing store.
bool CollectBackingStore(const FlutterBackingStore* backing_store);
// Presents the FlutterLayers by updating the FlutterView specified by
// `view_id` using the layer content. Sets frame_started_ to false.
bool Present(uint64_t view_id,
const FlutterLayer** layers,
size_t layers_count);
// Callback triggered at the end of the Present function. has_flutter_content
// is true when Flutter content was rendered, otherwise false.
using PresentCallback = std::function<bool(bool has_flutter_content)>;
// Registers a callback to be triggered at the end of the Present function.
// If a callback was previously registered, it will be replaced.
void SetPresentCallback(const PresentCallback& present_callback);
// The status of the frame being composited.
// Started: A new frame has begun and we have cleared the old layer tree and
// are now creating backingstore(s) for the embedder to use.
// Presenting: the embedder has finished rendering into the provided
// backingstore(s) and we are creating the layer tree for the system
// compositor to present with.
// Ended: The frame has been presented and we are no longer processing it.
typedef enum { kStarted, kPresenting, kEnded } FrameStatus;
protected:
// Returns the view associated with the view ID.
//
// Returns nil if the ID is invalid.
FlutterView* GetView(uint64_t view_id);
// Gets and sets the FrameStatus for the current frame.
void SetFrameStatus(FrameStatus frame_status);
FrameStatus GetFrameStatus();
// Clears the previous CALayers and updates the frame status to frame started.
void StartFrame();
// Calls the present callback and ensures the frame status is updated
// to frame ended, returning whether the present was successful or not.
bool EndFrame(bool has_flutter_content);
// Creates a CALayer object which is backed by the supplied IOSurface, and
// adds it to the root CALayer for the given view.
void InsertCALayerForIOSurface(
FlutterView* view,
const IOSurfaceRef& io_surface,
CATransform3D transform = CATransform3DIdentity);
private:
// Presents the platform view layer represented by `layer`. `layer_index` is
// used to position the layer in the z-axis. If the layer does not have a
@@ -107,25 +61,12 @@ class FlutterCompositor {
const FlutterLayer* layer,
size_t layer_position);
// A list of the active CALayer objects for the frame that need to be removed.
std::list<CALayer*> active_ca_layers_;
// Where the compositor can query FlutterViews. Must not be null.
id<FlutterViewProvider> const view_provider_;
// The controller used to manage creation and deletion of platform views.
const FlutterPlatformViewController* platform_view_controller_;
// The Metal device used to draw graphics.
const id<MTLDevice> mtl_device_;
// Callback set by the embedder to be called when the layer tree has been
// correctly set up for this frame.
PresentCallback present_callback_;
// Current frame status.
FrameStatus frame_status_ = kEnded;
FML_DISALLOW_COPY_AND_ASSIGN(FlutterCompositor);
};

View File

@@ -5,16 +5,12 @@
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
#include "flutter/fml/logging.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h"
namespace flutter {
FlutterCompositor::FlutterCompositor(id<FlutterViewProvider> view_provider,
FlutterPlatformViewController* platform_view_controller,
id<MTLDevice> mtl_device)
: view_provider_(view_provider),
platform_view_controller_(platform_view_controller),
mtl_device_(mtl_device) {
FlutterPlatformViewController* platform_view_controller)
: view_provider_(view_provider), platform_view_controller_(platform_view_controller) {
FML_CHECK(view_provider != nullptr) << "view_provider cannot be nullptr";
}
@@ -23,103 +19,67 @@ bool FlutterCompositor::CreateBackingStore(const FlutterBackingStoreConfig* conf
// TODO(dkwingsmt): This class only supports single-view for now. As more
// classes are gradually converted to multi-view, it should get the view ID
// from somewhere.
FlutterView* view = GetView(kFlutterDefaultViewId);
FlutterView* view = [view_provider_ getView:kFlutterDefaultViewId];
if (!view) {
return false;
}
CGSize size = CGSizeMake(config->size.width, config->size.height);
backing_store_out->metal.struct_size = sizeof(FlutterMetalBackingStore);
backing_store_out->metal.texture.struct_size = sizeof(FlutterMetalTexture);
if (GetFrameStatus() != FrameStatus::kStarted) {
StartFrame();
// If the backing store is for the first layer, return the MTLTexture for the
// FlutterView.
FlutterRenderBackingStore* backingStore = [view backingStoreForSize:size];
backing_store_out->metal.texture.texture =
(__bridge FlutterMetalTextureHandle)backingStore.texture;
} else {
FlutterIOSurfaceHolder* io_surface_holder = [[FlutterIOSurfaceHolder alloc] init];
[io_surface_holder recreateIOSurfaceWithSize:size];
auto texture_descriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
width:size.width
height:size.height
mipmapped:NO];
texture_descriptor.usage =
MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget | MTLTextureUsageShaderWrite;
backing_store_out->metal.texture.texture = (__bridge_retained FlutterMetalTextureHandle)
[mtl_device_ newTextureWithDescriptor:texture_descriptor
iosurface:[io_surface_holder ioSurface]
plane:0];
backing_store_out->metal.texture.user_data = (__bridge_retained void*)io_surface_holder;
}
FlutterSurface* surface = [view.surfaceManager surfaceForSize:size];
memset(backing_store_out, 0, sizeof(FlutterBackingStore));
backing_store_out->struct_size = sizeof(FlutterBackingStore);
backing_store_out->type = kFlutterBackingStoreTypeMetal;
backing_store_out->metal.texture.destruction_callback = [](void* user_data) {
if (user_data != nullptr) {
CFRelease(user_data);
}
};
return true;
}
bool FlutterCompositor::CollectBackingStore(const FlutterBackingStore* backing_store) {
// If we allocated this MTLTexture ourselves, user_data is not null, and we will need
// to release it manually.
if (backing_store->metal.texture.user_data != nullptr &&
backing_store->metal.texture.texture != nullptr) {
CFRelease(backing_store->metal.texture.texture);
}
backing_store_out->metal.struct_size = sizeof(FlutterMetalBackingStore);
backing_store_out->metal.texture = surface.asFlutterMetalTexture;
return true;
}
bool FlutterCompositor::Present(uint64_t view_id,
const FlutterLayer** layers,
size_t layers_count) {
FlutterView* view = GetView(view_id);
FlutterView* view = [view_provider_ getView:view_id];
if (!view) {
return false;
}
SetFrameStatus(FrameStatus::kPresenting);
NSMutableArray* surfaces = [NSMutableArray array];
for (size_t i = 0; i < layers_count; i++) {
const FlutterLayer* layer = layers[i];
if (layer->type == kFlutterLayerContentTypeBackingStore) {
FlutterSurface* surface =
[FlutterSurface fromFlutterMetalTexture:&layer->backing_store->metal.texture];
bool has_flutter_content = false;
for (size_t i = 0; i < layers_count; ++i) {
const auto* layer = layers[i];
FlutterBackingStore* backing_store = const_cast<FlutterBackingStore*>(layer->backing_store);
switch (layer->type) {
case kFlutterLayerContentTypeBackingStore: {
if (backing_store->metal.texture.user_data) {
FlutterIOSurfaceHolder* io_surface_holder =
(__bridge FlutterIOSurfaceHolder*)backing_store->metal.texture.user_data;
IOSurfaceRef io_surface = [io_surface_holder ioSurface];
InsertCALayerForIOSurface(view, io_surface);
}
has_flutter_content = true;
break;
if (surface) {
FlutterSurfacePresentInfo* info = [[FlutterSurfacePresentInfo alloc] init];
info.surface = surface;
info.offset = CGPointMake(layer->offset.x, layer->offset.y);
info.zIndex = i;
[surfaces addObject:info];
}
case kFlutterLayerContentTypePlatformView: {
PresentPlatformView(view, layer, i);
break;
}
};
}
}
return EndFrame(has_flutter_content);
[view.surfaceManager present:surfaces
notify:^{
for (size_t i = 0; i < layers_count; i++) {
const FlutterLayer* layer = layers[i];
switch (layer->type) {
case kFlutterLayerContentTypeBackingStore:
break;
case kFlutterLayerContentTypePlatformView:
PresentPlatformView(view, layer, i);
break;
}
}
[platform_view_controller_ disposePlatformViews];
}];
return true;
}
void FlutterCompositor::PresentPlatformView(FlutterView* default_base_view,
const FlutterLayer* layer,
size_t layer_position) {
// TODO (https://github.com/flutter/flutter/issues/96668)
// once the issue is fixed, this check will pass.
FML_DCHECK([[NSThread currentThread] isMainThread])
<< "Must be on the main thread to present platform views";
@@ -128,7 +88,7 @@ void FlutterCompositor::PresentPlatformView(FlutterView* default_base_view,
FML_DCHECK(platform_view) << "Platform view not found for id: " << platform_view_id;
CGFloat scale = [[NSScreen mainScreen] backingScaleFactor];
CGFloat scale = platform_view.layer.contentsScale;
platform_view.frame = CGRectMake(layer->offset.x / scale, layer->offset.y / scale,
layer->size.width / scale, layer->size.height / scale);
if (platform_view.superview == nil) {
@@ -137,51 +97,4 @@ void FlutterCompositor::PresentPlatformView(FlutterView* default_base_view,
platform_view.layer.zPosition = layer_position;
}
void FlutterCompositor::SetPresentCallback(
const FlutterCompositor::PresentCallback& present_callback) {
present_callback_ = present_callback;
}
void FlutterCompositor::StartFrame() {
// First remove all CALayers from the superlayer.
for (auto layer : active_ca_layers_) {
[layer removeFromSuperlayer];
}
// Reset active layers.
active_ca_layers_.clear();
SetFrameStatus(FrameStatus::kStarted);
}
bool FlutterCompositor::EndFrame(bool has_flutter_content) {
bool status = present_callback_(has_flutter_content);
SetFrameStatus(FrameStatus::kEnded);
return status;
}
FlutterView* FlutterCompositor::GetView(uint64_t view_id) {
return [view_provider_ getView:view_id];
}
void FlutterCompositor::SetFrameStatus(FlutterCompositor::FrameStatus frame_status) {
frame_status_ = frame_status;
}
FlutterCompositor::FrameStatus FlutterCompositor::GetFrameStatus() {
return frame_status_;
}
void FlutterCompositor::InsertCALayerForIOSurface(FlutterView* view,
const IOSurfaceRef& io_surface,
CATransform3D transform) {
// FlutterCompositor manages the lifecycle of CALayers.
CALayer* content_layer = [[CALayer alloc] init];
content_layer.transform = transform;
content_layer.frame = view.layer.bounds;
[content_layer setContents:(__bridge id)io_surface];
[view.layer addSublayer:content_layer];
active_ca_layers_.push_back(content_layer);
}
} // namespace flutter

View File

@@ -3,6 +3,7 @@
// found in the LICENSE file.
#import <Foundation/Foundation.h>
#import <Metal/Metal.h>
#import <OCMock/OCMock.h>
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h"
@@ -41,13 +42,17 @@
namespace flutter::testing {
namespace {
id<FlutterViewProvider> MockViewProvider() {
FlutterView* viewMock = OCMClassMock([FlutterView class]);
FlutterRenderBackingStore* backingStoreMock = OCMClassMock([FlutterRenderBackingStore class]);
__block id<MTLTexture> textureMock = OCMProtocolMock(@protocol(MTLTexture));
OCMStub([backingStoreMock texture]).andReturn(textureMock);
typedef void (^PresentBlock)(NSArray<FlutterSurfacePresentInfo*>*);
OCMStub([viewMock backingStoreForSize:CGSize{}])
id<FlutterViewProvider> MockViewProvider(PresentBlock onPresent = nil) {
FlutterView* viewMock = OCMClassMock([FlutterView class]);
FlutterSurfaceManager* surfaceManagerMock = OCMClassMock([FlutterSurfaceManager class]);
FlutterSurface* surfaceMock = OCMClassMock([FlutterSurface class]);
__block id<MTLTexture> textureMock = OCMProtocolMock(@protocol(MTLTexture));
OCMStub([viewMock surfaceManager]).andReturn(surfaceManagerMock);
OCMStub([surfaceManagerMock surfaceForSize:CGSize{}])
.ignoringNonObjectArgs()
.andDo(^(NSInvocation* invocation) {
CGSize size;
@@ -55,31 +60,35 @@ id<FlutterViewProvider> MockViewProvider() {
OCMStub([textureMock width]).andReturn(size.width);
OCMStub([textureMock height]).andReturn(size.height);
})
.andReturn(backingStoreMock);
.andReturn(surfaceMock);
FlutterMetalTexture texture = {
.struct_size = sizeof(FlutterMetalTexture),
.texture_id = 1,
.texture = (__bridge void*)textureMock,
.user_data = (__bridge void*)surfaceMock,
.destruction_callback = nullptr,
};
OCMStub([surfaceManagerMock present:[OCMArg any] notify:[OCMArg any]])
.andDo(^(NSInvocation* invocation) {
NSArray<FlutterSurfacePresentInfo*>* info;
[invocation getArgument:&info atIndex:2];
if (onPresent != nil) {
onPresent(info);
}
});
OCMStub([surfaceMock asFlutterMetalTexture]).andReturn(texture);
return [[FlutterViewMockProvider alloc] initWithDefaultView:viewMock];
}
} // namespace
TEST(FlutterCompositorTest, TestPresent) {
std::unique_ptr<flutter::FlutterCompositor> macos_compositor =
std::make_unique<FlutterCompositor>(MockViewProvider(), /*platform_view_controller*/ nullptr,
/*mtl_device*/ nullptr);
bool flag = false;
macos_compositor->SetPresentCallback([f = &flag](bool has_flutter_content) {
*f = true;
return true;
});
ASSERT_TRUE(macos_compositor->Present(0, nil, 0));
ASSERT_TRUE(flag);
}
TEST(FlutterCompositorTest, TestCreate) {
std::unique_ptr<flutter::FlutterCompositor> macos_compositor =
std::make_unique<FlutterCompositor>(MockViewProvider(), /*platform_view_controller*/ nullptr,
/*mtl_device*/ nullptr);
std::make_unique<FlutterCompositor>(MockViewProvider(),
/*platform_view_controller*/ nullptr);
FlutterBackingStore backing_store;
FlutterBackingStoreConfig config;
@@ -95,10 +104,16 @@ TEST(FlutterCompositorTest, TestCreate) {
ASSERT_EQ(texture.height, 600ul);
}
TEST(FlutterCompositorTest, TestCompositing) {
TEST(FlutterCompositorTest, TestPresent) {
__block NSArray<FlutterSurfacePresentInfo*>* presentedSurfaces = nil;
auto onPresent = ^(NSArray<FlutterSurfacePresentInfo*>* info) {
presentedSurfaces = info;
};
std::unique_ptr<flutter::FlutterCompositor> macos_compositor =
std::make_unique<FlutterCompositor>(MockViewProvider(), /*platform_view_controller*/ nullptr,
/*mtl_device*/ nullptr);
std::make_unique<FlutterCompositor>(MockViewProvider(onPresent),
/*platform_view_controller*/ nullptr);
FlutterBackingStore backing_store;
FlutterBackingStoreConfig config;
@@ -107,11 +122,18 @@ TEST(FlutterCompositorTest, TestCompositing) {
config.size.height = 600;
macos_compositor->CreateBackingStore(&config, &backing_store);
ASSERT_EQ(backing_store.type, kFlutterBackingStoreTypeMetal);
ASSERT_NE(backing_store.metal.texture.texture, nil);
id<MTLTexture> texture = (__bridge id<MTLTexture>)backing_store.metal.texture.texture;
ASSERT_EQ(texture.width, 800u);
ASSERT_EQ(texture.height, 600u);
FlutterLayer layers[] = {{
.struct_size = sizeof(FlutterLayer),
.type = kFlutterLayerContentTypeBackingStore,
.backing_store = &backing_store,
.offset = {0, 0},
.size = {800, 600},
}};
const FlutterLayer* layers_ptr = layers;
macos_compositor->Present(kFlutterDefaultViewId, &layers_ptr, 1);
ASSERT_EQ(presentedSurfaces.count, 1ul);
}
} // namespace flutter::testing

View File

@@ -432,22 +432,8 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
return nil;
}
__weak FlutterEngine* weakSelf = self;
_macOSCompositor = std::make_unique<flutter::FlutterCompositor>(
_viewProvider, _platformViewController, _renderer.device);
_macOSCompositor->SetPresentCallback([weakSelf](bool has_flutter_content) {
// TODO(dkwingsmt): The compositor only supports single-view for now. As
// more classes are gradually converted to multi-view, it should get the
// view ID from somewhere.
uint64_t viewId = kFlutterDefaultViewId;
if (has_flutter_content) {
return [weakSelf.renderer present:viewId] == YES;
} else {
[weakSelf.renderer presentWithoutContent:viewId];
return true;
}
});
_macOSCompositor =
std::make_unique<flutter::FlutterCompositor>(_viewProvider, _platformViewController);
_compositor = {};
_compositor.struct_size = sizeof(FlutterCompositor);
@@ -463,10 +449,7 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi
_compositor.collect_backing_store_callback = [](const FlutterBackingStore* backing_store, //
void* user_data //
) {
return reinterpret_cast<flutter::FlutterCompositor*>(user_data)->CollectBackingStore(
backing_store);
};
) { return true; };
_compositor.present_layers_callback = [](const FlutterLayer** layers, //
size_t layers_count, //

View File

@@ -30,6 +30,16 @@
@property(nonatomic, readonly, nullable) flutter::FlutterCompositor* macOSCompositor;
@end
@interface TestPlatformViewFactory : NSObject <FlutterPlatformViewFactory>
@end
@implementation TestPlatformViewFactory
- (nonnull NSView*)createWithViewIdentifier:(int64_t)viewId arguments:(nullable id)args {
return viewId == 42 ? [[NSView alloc] init] : nil;
}
@end
namespace flutter::testing {
TEST_F(FlutterEngineTest, CanLaunch) {
@@ -447,8 +457,7 @@ TEST_F(FlutterEngineTest, NativeCallbacks) {
ASSERT_TRUE(latch_called);
}
// TODO(iskakaushik): Enable after https://github.com/flutter/flutter/issues/96668 is fixed.
TEST(FlutterEngine, DISABLED_Compositor) {
TEST(FlutterEngine, Compositor) {
NSString* fixtures = @(flutter::testing::GetFixturesPath());
FlutterDartProject* project = [[FlutterDartProject alloc]
initWithAssetsPath:fixtures
@@ -462,26 +471,29 @@ TEST(FlutterEngine, DISABLED_Compositor) {
EXPECT_TRUE([engine runWithEntrypoint:@"canCompositePlatformViews"]);
// Latch to ensure the entire layer tree has been generated and presented.
fml::AutoResetWaitableEvent latch;
auto compositor = engine.macOSCompositor;
compositor->SetPresentCallback([&](bool has_flutter_content) {
latch.Signal();
return true;
});
latch.Wait();
[engine.platformViewController registerViewFactory:[[TestPlatformViewFactory alloc] init]
withId:@"factory_id"];
[engine.platformViewController
handleMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create"
arguments:@{
@"id" : @(42),
@"viewType" : @"factory_id",
}]
result:^(id result){
}];
[viewController.flutterView.threadSynchronizer blockUntilFrameAvailable];
CALayer* rootLayer = viewController.flutterView.layer;
// There are three layers total - the root layer and two sublayers.
// This test will need to be updated when PlatformViews are supported, as
// there are two PlatformView layers in this test.
// There are two layers with Flutter contents and one view
EXPECT_EQ(rootLayer.sublayers.count, 2u);
EXPECT_EQ(viewController.flutterView.subviews.count, 1u);
// TODO(gw280): add support for screenshot tests in this test harness
[engine shutDownEngine];
}
} // namespace flutter::testing
TEST(FlutterEngine, DartEntrypointArguments) {
NSString* fixtures = @(flutter::testing::GetFixturesPath());

View File

@@ -1,24 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
/**
* FlutterIOSurfaceHolder maintains an IOSurface
* and provides an interface to bind the IOSurface to a texture.
*/
@interface FlutterIOSurfaceHolder : NSObject
/**
* Releases the current IOSurface if one exists
* and creates a new IOSurface with the specified size.
*/
- (void)recreateIOSurfaceWithSize:(CGSize)size;
/**
* Returns a reference to the underlying IOSurface.
*/
- (const IOSurfaceRef&)ioSurface;
@end

View File

@@ -1,47 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h"
@interface FlutterIOSurfaceHolder () {
IOSurfaceRef _ioSurface;
}
@end
@implementation FlutterIOSurfaceHolder
- (void)recreateIOSurfaceWithSize:(CGSize)size {
if (_ioSurface) {
CFRelease(_ioSurface);
}
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 = IOSurfaceCreate((CFDictionaryRef)options);
IOSurfaceSetValue(_ioSurface, CFSTR("IOSurfaceColorSpace"), kCGColorSpaceSRGB);
}
- (const IOSurfaceRef&)ioSurface {
return _ioSurface;
}
- (void)dealloc {
if (_ioSurface) {
CFRelease(_ioSurface);
}
}
@end

View File

@@ -53,6 +53,9 @@
}
NSView* platform_view = [factory createWithViewIdentifier:viewId arguments:nil];
// Flutter compositing requires CALayer-backed platform views.
// Force the platform view to be backed by a CALayer.
[platform_view setWantsLayer:YES];
_platformViews[viewId] = platform_view;
result(nil);
}

View File

@@ -38,12 +38,7 @@
/**
* Called by the engine when the given view's buffers should be swapped.
*/
- (BOOL)present:(uint64_t)viewId;
/**
* Tells the renderer that there is no Flutter content available for this frame.
*/
- (void)presentWithoutContent:(uint64_t)viewId;
- (BOOL)present:(uint64_t)viewId texture:(nonnull const FlutterMetalTexture*)texture;
/**
* Creates a Metal texture for the given view with the given size.

View File

@@ -28,7 +28,7 @@ static bool OnPresentDrawableOfDefaultView(FlutterEngine* engine,
// operates on the default view. To support multi-view, we need a new callback
// that also receives a view ID.
uint64_t viewId = kFlutterDefaultViewId;
return [engine.renderer present:viewId];
return [engine.renderer present:viewId texture:texture];
}
static bool OnAcquireExternalTexture(FlutterEngine* engine,
@@ -95,30 +95,22 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine,
// FlutterMetalTexture has texture `null`, therefore is discarded.
return FlutterMetalTexture{};
}
FlutterRenderBackingStore* backingStore = [view backingStoreForSize:size];
id<MTLTexture> texture = backingStore.texture;
FlutterMetalTexture embedderTexture;
embedderTexture.struct_size = sizeof(FlutterMetalTexture);
embedderTexture.texture = (__bridge void*)texture;
embedderTexture.texture_id = reinterpret_cast<int64_t>(texture);
return embedderTexture;
return [view.surfaceManager surfaceForSize:size].asFlutterMetalTexture;
}
- (BOOL)present:(uint64_t)viewId {
- (BOOL)present:(uint64_t)viewId texture:(const FlutterMetalTexture*)texture {
FlutterView* view = [_viewProvider getView:viewId];
if (view == nil) {
return NO;
}
[view present];
return YES;
}
- (void)presentWithoutContent:(uint64_t)viewId {
FlutterView* view = [_viewProvider getView:viewId];
if (view == nil) {
return;
FlutterSurface* surface = [FlutterSurface fromFlutterMetalTexture:texture];
if (surface == nil) {
return NO;
}
[view presentWithoutContent];
FlutterSurfacePresentInfo* info = [[FlutterSurfacePresentInfo alloc] init];
info.surface = surface;
[view.surfaceManager present:@[ info ] notify:nil];
return YES;
}
#pragma mark - FlutterTextureRegistrar methods.

View File

@@ -37,17 +37,39 @@ void SetEngineDefaultView(FlutterEngine* engine, id flutterView) {
TEST(FlutterRenderer, PresentDelegatesToFlutterView) {
FlutterEngine* engine = CreateTestEngine();
FlutterRenderer* renderer = [[FlutterRenderer alloc] initWithFlutterEngine:engine];
id mockFlutterView = OCMClassMock([FlutterView class]);
SetEngineDefaultView(engine, mockFlutterView);
[(FlutterView*)[mockFlutterView expect] present];
[renderer present:kFlutterDefaultViewId];
id viewMock = OCMClassMock([FlutterView class]);
SetEngineDefaultView(engine, viewMock);
id surfaceManagerMock = OCMClassMock([FlutterSurfaceManager class]);
OCMStub([viewMock surfaceManager]).andReturn(surfaceManagerMock);
id surfaceMock = OCMClassMock([FlutterSurface class]);
FlutterMetalTexture texture = {
.user_data = (__bridge void*)surfaceMock,
};
[[surfaceManagerMock expect] present:[OCMArg checkWithBlock:^(id obj) {
NSArray* array = (NSArray*)obj;
return array.count == 1 ? YES : NO;
}]
notify:nil];
[renderer present:kFlutterDefaultViewId texture:&texture];
[surfaceManagerMock verify];
}
TEST(FlutterRenderer, TextureReturnedByFlutterView) {
FlutterEngine* engine = CreateTestEngine();
FlutterRenderer* renderer = [[FlutterRenderer alloc] initWithFlutterEngine:engine];
id mockFlutterView = OCMClassMock([FlutterView class]);
SetEngineDefaultView(engine, mockFlutterView);
id viewMock = OCMClassMock([FlutterView class]);
SetEngineDefaultView(engine, viewMock);
id surfaceManagerMock = OCMClassMock([FlutterSurfaceManager class]);
OCMStub([viewMock surfaceManager]).andReturn(surfaceManagerMock);
FlutterFrameInfo frameInfo;
frameInfo.struct_size = sizeof(FlutterFrameInfo);
FlutterUIntSize dimensions;
@@ -55,8 +77,10 @@ TEST(FlutterRenderer, TextureReturnedByFlutterView) {
dimensions.height = 200;
frameInfo.size = dimensions;
CGSize size = CGSizeMake(dimensions.width, dimensions.height);
[[mockFlutterView expect] backingStoreForSize:size];
[[surfaceManagerMock expect] surfaceForSize:size];
[renderer createTextureForView:kFlutterDefaultViewId size:size];
[surfaceManagerMock verify];
}
} // namespace flutter::testing

View File

@@ -1,33 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import <Metal/Metal.h>
#import <QuartzCore/CAMetalLayer.h>
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h"
/**
* Provides resizable buffers backed by a MTLTexture.
*/
@interface FlutterResizableBackingStoreProvider : NSObject <FlutterResizeSynchronizerDelegate>
/**
* Creates a resizable backing store provider for the given CAMetalLayer.
*/
- (nonnull instancetype)initWithDevice:(nonnull id<MTLDevice>)device
commandQueue:(nonnull id<MTLCommandQueue>)commandQueue
layer:(nonnull CALayer*)layer;
/**
* Notify of the required backing store size updates. Called during window resize.
*/
- (void)onBackingStoreResized:(CGSize)size;
/**
* Returns the FlutterBackingStore corresponding to the latest size.
*/
- (nonnull FlutterRenderBackingStore*)backingStore;
@end

View File

@@ -1,54 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterResizableBackingStoreProvider.h"
#import <QuartzCore/QuartzCore.h>
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"
@implementation FlutterResizableBackingStoreProvider {
id<MTLDevice> _device;
id<MTLCommandQueue> _commandQueue;
FlutterSurfaceManager* _surfaceManager;
}
- (instancetype)initWithDevice:(id<MTLDevice>)device
commandQueue:(id<MTLCommandQueue>)commandQueue
layer:(CALayer*)layer {
self = [super init];
if (self) {
_device = device;
_commandQueue = commandQueue;
_surfaceManager = [[FlutterSurfaceManager alloc] initWithDevice:device
commandQueue:commandQueue
layer:layer];
}
return self;
}
- (void)onBackingStoreResized:(CGSize)size {
[_surfaceManager ensureSurfaceSize:size];
}
- (FlutterRenderBackingStore*)backingStore {
return [_surfaceManager renderBuffer];
}
- (void)resizeSynchronizerFlush:(nonnull FlutterResizeSynchronizer*)synchronizer {
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
[commandBuffer commit];
[commandBuffer waitUntilScheduled];
}
- (void)resizeSynchronizerCommit:(nonnull FlutterResizeSynchronizer*)synchronizer {
[CATransaction begin];
[CATransaction setDisableActions:YES];
[_surfaceManager swapBuffers];
[CATransaction commit];
}
@end

View File

@@ -1,90 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
@class FlutterResizeSynchronizer;
/**
* Implemented by FlutterView.
*/
@protocol FlutterResizeSynchronizerDelegate
/**
* Invoked on raster thread; Delegate should flush the graphics context.
*/
- (void)resizeSynchronizerFlush:(nonnull FlutterResizeSynchronizer*)synchronizer;
/**
* Invoked on platform thread; Delegate should flip the surfaces.
*/
- (void)resizeSynchronizerCommit:(nonnull 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 the raster thread is 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 the FlutterSurfaceManager flipped the surface,
* which must be performed on platform thread.
*/
@interface FlutterResizeSynchronizer : NSObject
- (nullable instancetype)initWithDelegate:(nonnull 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:(nonnull 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;
/**
* Called from view to notify the synchronizer that there are no Flutter frames
* coming. Synchronizer must unblock main thread and not block until another
* frame is available.
*/
- (void)noFlutterContent;
/**
* Called when shutting down. Unblocks everything and prevents any further synchronization.
*/
- (void)shutdown;
@end

View File

@@ -1,153 +0,0 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h"
#include <mutex>
@interface FlutterResizeSynchronizer () {
// Counter to detect stale callbacks.
uint32_t _cookie;
std::mutex _mutex;
// Used to block [beginResize:].
std::condition_variable _condBlockBeginResize;
// Used to block [requestCommit].
std::condition_variable _condBlockRequestCommit;
// Whether a frame was received; the synchronizer doesn't block platform thread during resize
// until it knows that framework is running and producing frames
BOOL _hasFlutterContent;
// If NO, requestCommit calls are ignored until shouldEnsureSurfaceForSize is called with
// proper size.
BOOL _acceptingCommit;
// Waiting for resize to finish.
BOOL _waiting;
// RequestCommit was called and [delegate commit:] must be performed on platform thread.
BOOL _pendingCommit;
// Target size for resizing.
CGSize _newSize;
// if YES prevents all synchronization
BOOL _shuttingDown;
__weak id<FlutterResizeSynchronizerDelegate> _delegate;
}
@end
@implementation FlutterResizeSynchronizer
- (instancetype)initWithDelegate:(id<FlutterResizeSynchronizerDelegate>)delegate {
if (self = [super init]) {
_acceptingCommit = YES;
_delegate = delegate;
}
return self;
}
- (void)beginResize:(CGSize)size notify:(dispatch_block_t)notify {
std::unique_lock<std::mutex> lock(_mutex);
if (!_delegate) {
return;
}
if (!_hasFlutterContent || _shuttingDown) {
// No blocking until framework produces at least one frame
notify();
return;
}
++_cookie;
// from now on, ignore all incoming commits until the block below gets
// scheduled on raster thread
_acceptingCommit = NO;
// let pending commits finish to unblock the raster thread
_pendingCommit = NO;
_condBlockBeginResize.notify_all();
// let the engine send resize notification
notify();
_newSize = size;
_waiting = YES;
_condBlockRequestCommit.wait(lock, [&] { return _pendingCommit || _shuttingDown; });
[_delegate resizeSynchronizerFlush:self];
[_delegate resizeSynchronizerCommit:self];
_pendingCommit = NO;
_condBlockBeginResize.notify_all();
_waiting = NO;
}
- (BOOL)shouldEnsureSurfaceForSize:(CGSize)size {
std::unique_lock<std::mutex> lock(_mutex);
if (!_hasFlutterContent) {
return YES;
}
if (!_acceptingCommit) {
if (CGSizeEqualToSize(_newSize, size)) {
_acceptingCommit = YES;
}
}
return _acceptingCommit;
}
- (void)requestCommit {
std::unique_lock<std::mutex> lock(_mutex);
if (!_acceptingCommit || _shuttingDown) {
return;
}
_hasFlutterContent = YES;
_pendingCommit = YES;
if (_waiting) { // BeginResize is in progress, interrupt it and schedule commit call
_condBlockRequestCommit.notify_all();
_condBlockBeginResize.wait(lock, [&]() { return !_pendingCommit || _shuttingDown; });
} 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 = NO;
_condBlockBeginResize.notify_all();
}
});
_condBlockBeginResize.wait(lock, [&]() { return !_pendingCommit || _shuttingDown; });
}
}
- (void)noFlutterContent {
std::unique_lock<std::mutex> lock(_mutex);
_hasFlutterContent = NO;
_acceptingCommit = YES;
_condBlockBeginResize.notify_all();
}
- (void)shutdown {
std::unique_lock<std::mutex> lock(_mutex);
_shuttingDown = YES;
_condBlockBeginResize.notify_all();
_condBlockRequestCommit.notify_all();
}
@end

View File

@@ -0,0 +1,33 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import "flutter/shell/platform/embedder/embedder.h"
/**
* Opaque surface type.
* Can be represented as FlutterMetalTexture to cross the embedder API boundary.
*/
@interface FlutterSurface : NSObject
- (FlutterMetalTexture)asFlutterMetalTexture;
+ (nullable FlutterSurface*)fromFlutterMetalTexture:(nonnull const FlutterMetalTexture*)texture;
@end
/**
* Internal FlutterSurface interface used by FlutterSurfaceManager.
* Wraps an IOSurface framebuffer and metadata related to the surface.
*/
@interface FlutterSurface (Private)
- (nonnull instancetype)initWithSize:(CGSize)size device:(nonnull id<MTLDevice>)device;
@property(readonly, nonatomic, nonnull) IOSurfaceRef ioSurface;
@property(readonly, nonatomic) CGSize size;
@property(readonly, nonatomic) int64_t textureId;
@end

View File

@@ -0,0 +1,98 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurface.h"
#import <Metal/Metal.h>
@interface FlutterSurface () {
CGSize _size;
IOSurfaceRef _ioSurface;
id<MTLTexture> _texture;
}
@end
@implementation FlutterSurface
- (IOSurfaceRef)ioSurface {
return _ioSurface;
}
- (CGSize)size {
return _size;
}
- (int64_t)textureId {
return reinterpret_cast<int64_t>(_texture);
}
- (instancetype)initWithSize:(CGSize)size device:(id<MTLDevice>)device {
if (self = [super init]) {
self->_size = size;
self->_ioSurface = [FlutterSurface createIOSurfaceWithSize:size];
self->_texture = [FlutterSurface createTextureForIOSurface:_ioSurface size:size device:device];
}
return self;
}
static void ReleaseSurface(void* surface) {
if (surface != nullptr) {
CFBridgingRelease(surface);
}
}
- (FlutterMetalTexture)asFlutterMetalTexture {
FlutterMetalTexture res;
memset(&res, 0, sizeof(FlutterMetalTexture));
res.struct_size = sizeof(FlutterMetalTexture);
res.texture = (__bridge void*)_texture;
res.texture_id = self.textureId;
res.user_data = (void*)CFBridgingRetain(self);
res.destruction_callback = ReleaseSurface;
return res;
}
+ (FlutterSurface*)fromFlutterMetalTexture:(const FlutterMetalTexture*)texture {
return (__bridge FlutterSurface*)texture->user_data;
}
- (void)dealloc {
CFRelease(_ioSurface);
}
+ (IOSurfaceRef)createIOSurfaceWithSize:(CGSize)size {
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),
};
IOSurfaceRef res = IOSurfaceCreate((CFDictionaryRef)options);
IOSurfaceSetValue(res, CFSTR("IOSurfaceColorSpace"), kCGColorSpaceSRGB);
return res;
}
+ (id<MTLTexture>)createTextureForIOSurface:(IOSurfaceRef)surface
size:(CGSize)size
device:(id<MTLDevice>)device {
MTLTextureDescriptor* textureDescriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
width:size.width
height:size.height
mipmapped:NO];
textureDescriptor.usage =
MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget | MTLTextureUsageShaderWrite;
// plane = 0 for BGRA.
return [device newTextureWithDescriptor:textureDescriptor iosurface:surface plane:0];
}
@end

View File

@@ -3,42 +3,100 @@
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import <QuartzCore/CAMetalLayer.h>
#import <QuartzCore/QuartzCore.h>
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurface.h"
/**
* Manages render surfaces and corresponding backing stores used by the engine.
* Surface with additional properties needed for presenting.
*/
@interface FlutterSurfacePresentInfo : NSObject
@property(readwrite, strong, nonatomic, nonnull) FlutterSurface* surface;
@property(readwrite, nonatomic) CGPoint offset;
@property(readwrite, nonatomic) size_t zIndex;
@end
@protocol FlutterSurfaceManagerDelegate <NSObject>
/*
* Schedules the block on the platform thread and blocks until the block is executed.
* Provided `frameSize` is used to unblock the platform thread if it waits for
* a certain frame size during resizing.
*/
- (void)onPresent:(CGSize)frameSize withBlock:(nonnull dispatch_block_t)block;
@end
/**
* FlutterSurfaceManager is responsible for providing and presenting Core Animation render
* surfaces and managing sublayers.
*
* The backing store when rendering with on Metal is a Metal texture. There are two IOSurfaces
* created during initialization, FlutterSurfaceManager manages the lifecycle of these.
* Owned by `FlutterView`.
*/
@interface FlutterSurfaceManager : NSObject
/**
* Initializes and returns a surface manager that renders to a child layer (referred to as the
* content layer) of the containing layer and applies the transform to the contents of the content
* layer.
* content layer) of the containing layer.
*/
- (nullable instancetype)initWithDevice:(nonnull id<MTLDevice>)device
commandQueue:(nonnull id<MTLCommandQueue>)commandQueue
layer:(nonnull CALayer*)containingLayer;
layer:(nonnull CALayer*)containingLayer
delegate:(nonnull id<FlutterSurfaceManagerDelegate>)delegate;
/**
* Updates the backing store size of the managed IOSurfaces the specified size. If the surfaces are
* already of this size, this is a no-op.
* Returns a back buffer surface of the given size to which Flutter can render content.
* A cached surface will be returned if available; otherwise a new one will be created.
*
* Must be called on raster thread.
*/
- (void)ensureSurfaceSize:(CGSize)size;
- (nonnull FlutterSurface*)surfaceForSize:(CGSize)size;
/**
* Swaps the front and the back buffer.
* Sets the provided surfaces as contents of FlutterView. Will create, update and
* remove sublayers as needed.
*
* Must be called on raster thread. This will schedule a commit on the platform thread and block the
* raster thread until the commit is done. The `notify` block will be invoked on the platform thread
* and can be used to perform additional work, such as mutating platform views. It is guaranteed be
* called in the same CATransaction.
*/
- (void)swapBuffers;
/**
* Returns the backing store for the back buffer.
*/
- (nonnull FlutterRenderBackingStore*)renderBuffer;
- (void)present:(nonnull NSArray<FlutterSurfacePresentInfo*>*)surfaces
notify:(nullable dispatch_block_t)notify;
@end
/**
* Cache of back buffers to prevent unnecessary IOSurface allocations.
*/
@interface FlutterBackBufferCache : NSObject
/**
* Removes surface with given size from cache (if available) and returns it.
*/
- (nullable FlutterSurface*)removeSurfaceForSize:(CGSize)size;
/**
* Removes all cached surfaces replacing them with new ones.
*/
- (void)replaceSurfaces:(nonnull NSArray<FlutterSurface*>*)surfaces;
/**
* Returns number of surfaces currently in cache. Used for tests.
*/
- (NSUInteger)count;
@end
/**
* Interface to internal properties used for testing.
*/
@interface FlutterSurfaceManager (Private)
@property(readonly, nonatomic, nonnull) FlutterBackBufferCache* backBufferCache;
@property(readonly, nonatomic, nonnull) NSArray<FlutterSurface*>* frontSurfaces;
@property(readonly, nonatomic, nonnull) NSArray<CALayer*>* layers;
@end

View File

@@ -3,105 +3,191 @@
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurface.h"
#import <Metal/Metal.h>
#include <algorithm>
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h"
@implementation FlutterSurfacePresentInfo
@end
enum {
kFlutterSurfaceManagerFrontBuffer = 0,
kFlutterSurfaceManagerBackBuffer = 1,
kFlutterSurfaceManagerBufferCount,
};
@interface FlutterSurfaceManager () {
id<MTLDevice> _device;
id<MTLCommandQueue> _commandQueue;
CALayer* _containingLayer;
__weak id<FlutterSurfaceManagerDelegate> _delegate;
// BackBuffer will be released after kIdleDelay if there is no activity.
static const double kIdleDelay = 1.0;
// Available (cached) back buffer surfaces. These will be cleared during
// present and replaced by current frong surfaces.
FlutterBackBufferCache* _backBufferCache;
@interface FlutterSurfaceManager ()
// Surfaces currently used to back visible layers.
NSMutableArray<FlutterSurface*>* _frontSurfaces;
// Currently visible layers.
NSMutableArray<CALayer*>* _layers;
}
/**
* Cancels any previously-scheduled onIdle requests.
* Updates underlying CALayers with the contents of the surfaces to present.
*/
- (void)cancelIdle;
/**
* Creates a backing textures for the specified surface with the specified size.
*/
- (id<MTLTexture>)createTextureForSurface:(FlutterIOSurfaceHolder*)surface size:(CGSize)size;
- (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces;
@end
@implementation FlutterSurfaceManager {
CALayer* _containingLayer; // provided (parent layer)
CALayer* _contentLayer;
CATransform3D _contentTransform;
CGSize _surfaceSize;
FlutterIOSurfaceHolder* _ioSurfaces[kFlutterSurfaceManagerBufferCount];
BOOL _frameInProgress;
id<MTLDevice> _device;
id<MTLCommandQueue> _commandQueue;
id<MTLTexture> _textures[kFlutterSurfaceManagerBufferCount];
}
- (nullable instancetype)initWithDevice:(nonnull id<MTLDevice>)device
commandQueue:(nonnull id<MTLCommandQueue>)commandQueue
layer:(nonnull CALayer*)containingLayer {
self = [super init];
if (self) {
_containingLayer = containingLayer;
_contentTransform = CATransform3DIdentity;
_contentLayer = [[CALayer alloc] init];
[_containingLayer addSublayer:_contentLayer];
_ioSurfaces[0] = [[FlutterIOSurfaceHolder alloc] init];
_ioSurfaces[1] = [[FlutterIOSurfaceHolder alloc] init];
@implementation FlutterSurfaceManager
- (instancetype)initWithDevice:(id<MTLDevice>)device
commandQueue:(id<MTLCommandQueue>)commandQueue
layer:(CALayer*)containingLayer
delegate:(__weak id<FlutterSurfaceManagerDelegate>)delegate {
if (self = [super init]) {
_device = device;
_commandQueue = commandQueue;
_containingLayer = containingLayer;
_delegate = delegate;
_backBufferCache = [[FlutterBackBufferCache alloc] init];
_frontSurfaces = [NSMutableArray array];
_layers = [NSMutableArray array];
}
return self;
}
- (void)ensureSurfaceSize:(CGSize)size {
if (CGSizeEqualToSize(size, _surfaceSize)) {
return;
- (FlutterBackBufferCache*)backBufferCache {
return _backBufferCache;
}
- (NSArray*)frontSurfaces {
return _frontSurfaces;
}
- (NSArray*)layers {
return _layers;
}
- (FlutterSurface*)surfaceForSize:(CGSize)size {
FlutterSurface* surface = [_backBufferCache removeSurfaceForSize:size];
if (surface == nil) {
surface = [[FlutterSurface alloc] initWithSize:size device:_device];
}
_surfaceSize = size;
for (int i = 0; i < kFlutterSurfaceManagerBufferCount; ++i) {
if (_ioSurfaces[i] != nil) {
[_ioSurfaces[i] recreateIOSurfaceWithSize:size];
_textures[i] = [self createTextureForSurface:_ioSurfaces[i] size:size];
}
return surface;
}
- (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces {
assert([NSThread isMainThread]);
// Release all unused back buffer surfaces and replace them with front surfaces.
[_backBufferCache replaceSurfaces:_frontSurfaces];
// Front surfaces will be replaced by currently presented surfaces.
[_frontSurfaces removeAllObjects];
for (FlutterSurfacePresentInfo* info in surfaces) {
[_frontSurfaces addObject:info.surface];
}
// Add or remove layers to match the count of surfaces to present.
while (_layers.count > _frontSurfaces.count) {
[_layers.lastObject removeFromSuperlayer];
[_layers removeLastObject];
}
while (_layers.count < _frontSurfaces.count) {
CALayer* layer = [CALayer layer];
[_containingLayer addSublayer:layer];
[_layers addObject:layer];
}
// Update contents of surfaces.
for (size_t i = 0; i < surfaces.count; ++i) {
FlutterSurfacePresentInfo* info = surfaces[i];
CALayer* layer = _layers[i];
CGFloat scale = _containingLayer.contentsScale;
layer.frame = CGRectMake(info.offset.x / scale, info.offset.y / scale,
info.surface.size.width / scale, info.surface.size.height / scale);
layer.contents = (__bridge id)info.surface.ioSurface;
layer.zPosition = info.zIndex;
}
}
- (void)swapBuffers {
#ifndef NDEBUG
// swapBuffers should not be called unless a frame was drawn
@synchronized(self) {
assert(_frameInProgress);
static CGSize GetRequiredFrameSize(NSArray<FlutterSurfacePresentInfo*>* surfaces) {
CGSize size = CGSizeZero;
for (FlutterSurfacePresentInfo* info in surfaces) {
size = CGSizeMake(std::max(size.width, info.offset.x + info.surface.size.width),
std::max(size.height, info.offset.y + info.surface.size.height));
}
#endif
return size;
}
_contentLayer.frame = _containingLayer.bounds;
_contentLayer.transform = _contentTransform;
IOSurfaceRef contentIOSurface = [_ioSurfaces[kFlutterSurfaceManagerBackBuffer] ioSurface];
[_contentLayer setContents:(__bridge id)contentIOSurface];
- (void)present:(NSArray<FlutterSurfacePresentInfo*>*)surfaces notify:(dispatch_block_t)notify {
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
[commandBuffer commit];
[commandBuffer waitUntilScheduled];
std::swap(_ioSurfaces[kFlutterSurfaceManagerBackBuffer],
_ioSurfaces[kFlutterSurfaceManagerFrontBuffer]);
std::swap(_textures[kFlutterSurfaceManagerBackBuffer],
_textures[kFlutterSurfaceManagerFrontBuffer]);
// Get the actual dimensions of the frame (relevant for thread synchronizer).
CGSize size = GetRequiredFrameSize(surfaces);
[_delegate onPresent:size
withBlock:^{
[self commit:surfaces];
if (notify != nil) {
notify();
}
}];
}
@end
// Cached back buffers will be released after kIdleDelay if there is no activity.
static const double kIdleDelay = 1.0;
@interface FlutterBackBufferCache () {
NSMutableArray<FlutterSurface*>* _surfaces;
}
@end
@implementation FlutterBackBufferCache
- (instancetype)init {
if (self = [super init]) {
self->_surfaces = [[NSMutableArray alloc] init];
}
return self;
}
- (nullable FlutterSurface*)removeSurfaceForSize:(CGSize)size {
@synchronized(self) {
for (FlutterSurface* surface in _surfaces) {
if (CGSizeEqualToSize(surface.size, size)) {
// By default ARC doesn't retain enumeration iteration variables.
FlutterSurface* res = surface;
[_surfaces removeObject:surface];
return res;
}
}
return nil;
}
}
- (void)replaceSurfaces:(nonnull NSArray<FlutterSurface*>*)surfaces {
@synchronized(self) {
[_surfaces removeAllObjects];
[_surfaces addObjectsFromArray:surfaces];
}
// performSelector:withObject:afterDelay needs to be performed on RunLoop thread
[self performSelectorOnMainThread:@selector(reschedule) withObject:nil waitUntilDone:NO];
}
- (NSUInteger)count {
@synchronized(self) {
_frameInProgress = NO;
return _surfaces.count;
}
}
- (void)onIdle {
@synchronized(self) {
[_surfaces removeAllObjects];
}
}
@@ -110,52 +196,8 @@ static const double kIdleDelay = 1.0;
[self performSelector:@selector(onIdle) withObject:nil afterDelay:kIdleDelay];
}
- (void)onIdle {
@synchronized(self) {
if (!_frameInProgress) {
// Release the back buffer and notify delegate. The buffer will be restored
// on demand in ensureBackBuffer
_ioSurfaces[kFlutterSurfaceManagerBackBuffer] = nil;
_textures[kFlutterSurfaceManagerBackBuffer] = nil;
}
}
}
- (void)ensureBackBuffer {
@synchronized(self) {
_frameInProgress = YES;
if (_ioSurfaces[kFlutterSurfaceManagerBackBuffer] == nil) {
// Restore previously released backbuffer
_ioSurfaces[kFlutterSurfaceManagerBackBuffer] = [[FlutterIOSurfaceHolder alloc] init];
[_ioSurfaces[kFlutterSurfaceManagerBackBuffer] recreateIOSurfaceWithSize:_surfaceSize];
_textures[kFlutterSurfaceManagerBackBuffer] =
[self createTextureForSurface:_ioSurfaces[kFlutterSurfaceManagerBackBuffer]
size:_surfaceSize];
}
};
[self performSelectorOnMainThread:@selector(cancelIdle) withObject:nil waitUntilDone:NO];
}
- (void)cancelIdle {
- (void)dealloc {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(onIdle) object:nil];
}
- (nonnull FlutterRenderBackingStore*)renderBuffer {
[self ensureBackBuffer];
id<MTLTexture> texture = _textures[kFlutterSurfaceManagerBackBuffer];
return [[FlutterRenderBackingStore alloc] initWithTexture:texture];
}
- (id<MTLTexture>)createTextureForSurface:(FlutterIOSurfaceHolder*)surface size:(CGSize)size {
MTLTextureDescriptor* textureDescriptor =
[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
width:size.width
height:size.height
mipmapped:NO];
textureDescriptor.usage =
MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget | MTLTextureUsageShaderWrite;
// plane = 0 for BGRA.
return [_device newTextureWithDescriptor:textureDescriptor iosurface:[surface ioSurface] plane:0];
}
@end

View File

@@ -3,14 +3,18 @@
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import <Metal/Metal.h>
#import <OCMock/OCMock.h>
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurface.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"
#include "flutter/testing/testing.h"
#include "gtest/gtest.h"
@interface TestView : NSView
@interface TestView : NSView <FlutterSurfaceManagerDelegate>
@property(readwrite, nonatomic) CGSize presentedFrameSize;
- (nonnull instancetype)init;
@end
@@ -25,38 +29,172 @@
return self;
}
- (void)onPresent:(CGSize)frameSize withBlock:(nonnull dispatch_block_t)block {
self.presentedFrameSize = frameSize;
block();
}
@end
namespace flutter::testing {
static FlutterSurfaceManager* CreateSurfaceManager() {
static FlutterSurfaceManager* CreateSurfaceManager(TestView* testView) {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
id<MTLCommandQueue> commandQueue = [device newCommandQueue];
TestView* metalView = [[TestView alloc] init];
CALayer* layer = reinterpret_cast<CALayer*>(metalView.layer);
CALayer* layer = reinterpret_cast<CALayer*>(testView.layer);
return [[FlutterSurfaceManager alloc] initWithDevice:device
commandQueue:commandQueue
layer:layer];
layer:layer
delegate:testView];
}
TEST(FlutterSurfaceManager, EnsureSizeUpdatesSize) {
FlutterSurfaceManager* surfaceManager = CreateSurfaceManager();
CGSize size = CGSizeMake(100, 50);
[surfaceManager ensureSurfaceSize:size];
id<MTLTexture> texture = [surfaceManager renderBuffer].texture;
CGSize textureSize = CGSizeMake(texture.width, texture.height);
ASSERT_TRUE(CGSizeEqualToSize(size, textureSize));
static FlutterSurfacePresentInfo* CreatePresentInfo(FlutterSurface* surface,
CGPoint offset = CGPointZero,
size_t index = 0) {
FlutterSurfacePresentInfo* res = [[FlutterSurfacePresentInfo alloc] init];
res.surface = surface;
res.offset = offset;
res.zIndex = index;
return res;
}
TEST(FlutterSurfaceManager, EnsureSizeUpdatesSizeForBackBuffer) {
FlutterSurfaceManager* surfaceManager = CreateSurfaceManager();
CGSize size = CGSizeMake(100, 50);
[surfaceManager ensureSurfaceSize:size];
[surfaceManager renderBuffer]; // make sure we have back buffer
[surfaceManager swapBuffers];
id<MTLTexture> texture = [surfaceManager renderBuffer].texture;
CGSize textureSize = CGSizeMake(texture.width, texture.height);
ASSERT_TRUE(CGSizeEqualToSize(size, textureSize));
TEST(FlutterSurfaceManager, MetalTextureSizeMatchesSurfaceSize) {
TestView* testView = [[TestView alloc] init];
FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
// Get back buffer, lookup should work for borrowed surfaces util present.
auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
auto texture = surface.asFlutterMetalTexture;
id<MTLTexture> metalTexture = (__bridge id)texture.texture;
EXPECT_EQ(metalTexture.width, 100ul);
EXPECT_EQ(metalTexture.height, 50ul);
texture.destruction_callback(texture.user_data);
}
TEST(FlutterSurfaceManager, TestSurfaceLookupFromTexture) {
TestView* testView = [[TestView alloc] init];
FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
// Get back buffer, lookup should work for borrowed surfaces util present.
auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
// SurfaceManager should keep texture alive while borrowed.
auto texture = surface.asFlutterMetalTexture;
texture.destruction_callback(texture.user_data);
FlutterMetalTexture dummyTexture{.texture_id = 1, .texture = nullptr, .user_data = nullptr};
auto surface1 = [FlutterSurface fromFlutterMetalTexture:&dummyTexture];
EXPECT_EQ(surface1, nil);
auto surface2 = [FlutterSurface fromFlutterMetalTexture:&texture];
EXPECT_EQ(surface2, surface);
}
TEST(FlutterSurfaceManager, BackBufferCacheDoesNotLeak) {
TestView* testView = [[TestView alloc] init];
FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
[surfaceManager present:@[ CreatePresentInfo(surface1) ] notify:nil];
EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(110, 110)];
[surfaceManager present:@[ CreatePresentInfo(surface2) ] notify:nil];
EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
auto surface3 = [surfaceManager surfaceForSize:CGSizeMake(120, 120)];
[surfaceManager present:@[ CreatePresentInfo(surface3) ] notify:nil];
// Cache should be cleaned during present and only contain the last visible
// surface(s).
EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
auto surfaceFromCache = [surfaceManager surfaceForSize:CGSizeMake(110, 110)];
EXPECT_EQ(surfaceFromCache, surface2);
[surfaceManager present:@[] notify:nil];
EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
[surfaceManager present:@[] notify:nil];
EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
}
TEST(FlutterSurfaceManager, SurfacesAreRecycled) {
TestView* testView = [[TestView alloc] init];
FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
EXPECT_EQ(surfaceManager.frontSurfaces.count, 0ul);
// Get first surface and present it.
auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
EXPECT_TRUE(CGSizeEqualToSize(surface1.size, CGSizeMake(100, 100)));
EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
EXPECT_EQ(surfaceManager.frontSurfaces.count, 0ul);
[surfaceManager present:@[ CreatePresentInfo(surface1) ] notify:nil];
EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
EXPECT_EQ(surfaceManager.frontSurfaces.count, 1ul);
EXPECT_EQ(testView.layer.sublayers.count, 1ul);
// Get second surface and present it.
auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
EXPECT_TRUE(CGSizeEqualToSize(surface2.size, CGSizeMake(100, 100)));
EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
[surfaceManager present:@[ CreatePresentInfo(surface2) ] notify:nil];
// Check that current front surface returns to cache.
EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
EXPECT_EQ(surfaceManager.frontSurfaces.count, 1ul);
EXPECT_EQ(testView.layer.sublayers.count, 1ull);
// Check that surface is properly reused.
auto surface3 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
EXPECT_EQ(surface3, surface1);
}
TEST(FlutterSurfaceManager, LayerManagement) {
TestView* testView = [[TestView alloc] init];
FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
EXPECT_EQ(testView.layer.sublayers.count, 0ul);
auto surface1_1 = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
[surfaceManager present:@[ CreatePresentInfo(surface1_1, CGPointMake(20, 10)) ] notify:nil];
EXPECT_EQ(testView.layer.sublayers.count, 1ul);
EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(70, 40)));
auto surface2_1 = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
auto surface2_2 = [surfaceManager surfaceForSize:CGSizeMake(20, 20)];
[surfaceManager present:@[
CreatePresentInfo(surface2_1, CGPointMake(20, 10), 1),
CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2)
]
notify:nil];
EXPECT_EQ(testView.layer.sublayers.count, 2ul);
EXPECT_EQ([testView.layer.sublayers objectAtIndex:0].zPosition, 1.0);
EXPECT_EQ([testView.layer.sublayers objectAtIndex:1].zPosition, 2.0);
EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(70, 70)));
auto surface3_1 = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
[surfaceManager present:@[ CreatePresentInfo(surface3_1, CGPointMake(20, 10)) ] notify:nil];
EXPECT_EQ(testView.layer.sublayers.count, 1ul);
EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(70, 40)));
// Check removal of all surfaces.
[surfaceManager present:@[] notify:nil];
EXPECT_EQ(testView.layer.sublayers.count, 0ul);
EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(0, 0)));
}
} // namespace flutter::testing

View File

@@ -0,0 +1,36 @@
#import <Cocoa/Cocoa.h>
/**
* Takes care of synchronization between raster and platform thread.
*/
@interface FlutterThreadSynchronizer : NSObject
/**
* Blocks current thread until there is frame available.
* Used in FlutterEngineTest.
*/
- (void)blockUntilFrameAvailable;
/**
* Called from platform thread. Blocks until commit with given size (or empty)
* is requested.
*/
- (void)beginResize:(CGSize)size notify:(nonnull dispatch_block_t)notify;
/**
* Called from raster thread. Schedules the given block on platform thread
* and blocks until it is performed.
*
* If platform thread is blocked in `beginResize:` for given size (or size is empty),
* unblocks platform thread.
*
* The notify block is guaranteed to be called within a core animation transaction.
*/
- (void)performCommit:(CGSize)size notify:(nonnull dispatch_block_t)notify;
/**
* Called when shutting down. Unblocks everything and prevents any further synchronization.
*/
- (void)shutdown;
@end

View File

@@ -0,0 +1,103 @@
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.h"
#import "fml/synchronization/waitable_event.h"
#import <QuartzCore/QuartzCore.h>
#include <mutex>
@interface FlutterThreadSynchronizer () {
std::mutex _mutex;
BOOL _shuttingDown;
CGSize _contentSize;
std::vector<dispatch_block_t> _scheduledBlocks;
BOOL _beginResizeWaiting;
// Used to block [beginResize:].
std::condition_variable _condBlockBeginResize;
}
@end
@implementation FlutterThreadSynchronizer
- (void)drain {
assert([NSThread isMainThread]);
[CATransaction begin];
[CATransaction setDisableActions:YES];
for (dispatch_block_t block : _scheduledBlocks) {
block();
}
[CATransaction commit];
_scheduledBlocks.clear();
}
- (void)blockUntilFrameAvailable {
std::unique_lock<std::mutex> lock(_mutex);
_beginResizeWaiting = YES;
while (CGSizeEqualToSize(_contentSize, CGSizeZero) && !_shuttingDown) {
_condBlockBeginResize.wait(lock);
[self drain];
}
_beginResizeWaiting = NO;
}
- (void)beginResize:(CGSize)size notify:(nonnull dispatch_block_t)notify {
std::unique_lock<std::mutex> lock(_mutex);
if (CGSizeEqualToSize(_contentSize, CGSizeZero) || _shuttingDown) {
// No blocking until framework produces at least one frame
notify();
return;
}
[self drain];
notify();
_contentSize = CGSizeMake(-1, -1);
_beginResizeWaiting = YES;
while (!CGSizeEqualToSize(_contentSize, size) && //
!CGSizeEqualToSize(_contentSize, CGSizeZero) && !_shuttingDown) {
_condBlockBeginResize.wait(lock);
[self drain];
}
_beginResizeWaiting = NO;
}
- (void)performCommit:(CGSize)size notify:(nonnull dispatch_block_t)notify {
fml::AutoResetWaitableEvent event;
{
std::unique_lock<std::mutex> lock(_mutex);
fml::AutoResetWaitableEvent& e = event;
_scheduledBlocks.push_back(^{
notify();
_contentSize = size;
e.Signal();
});
if (_beginResizeWaiting) {
_condBlockBeginResize.notify_all();
} else {
dispatch_async(dispatch_get_main_queue(), ^{
std::unique_lock<std::mutex> lock(_mutex);
[self drain];
});
}
}
event.Wait();
}
- (void)shutdown {
std::unique_lock<std::mutex> lock(_mutex);
_shuttingDown = YES;
_condBlockBeginResize.notify_all();
[self drain];
}
@end

View File

@@ -3,9 +3,11 @@
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#include <stdint.h>
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterResizableBackingStoreProvider.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.h"
#include <stdint.h>
/**
* The view ID for APIs that don't support multi-view.
@@ -49,21 +51,10 @@ constexpr uint64_t kFlutterDefaultViewId = 0;
- (nonnull instancetype)init NS_UNAVAILABLE;
/**
* Flushes the graphics context and flips the surfaces. Expected to be called on raster thread.
* Returns SurfaceManager for this view. SurfaceManager is responsible for
* providing and presenting render surfaces.
*/
- (void)present;
/**
* Called when there is no Flutter content available to render. This must be passed to resize
* synchronizer.
*/
- (void)presentWithoutContent;
/**
* Ensures that a backing store with requested size exists and returns the descriptor. Expected to
* be called on raster thread.
*/
- (nonnull FlutterRenderBackingStore*)backingStoreForSize:(CGSize)size;
@property(readonly, nonatomic, nonnull) FlutterSurfaceManager* surfaceManager;
/**
* Must be called when shutting down. Unblocks raster thread and prevents any further
@@ -81,3 +72,13 @@ constexpr uint64_t kFlutterDefaultViewId = 0;
- (void)setBackgroundColor:(nonnull NSColor*)color;
@end
@interface FlutterView (FlutterViewPrivate)
/**
* Returns FlutterThreadSynchronizer for this view.
* Used for FlutterEngineTest.
*/
- (nonnull FlutterThreadSynchronizer*)threadSynchronizer;
@end

View File

@@ -4,15 +4,15 @@
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterThreadSynchronizer.h"
#import <QuartzCore/QuartzCore.h>
@interface FlutterView () {
@interface FlutterView () <FlutterSurfaceManagerDelegate> {
__weak id<FlutterViewReshapeListener> _reshapeListener;
FlutterResizeSynchronizer* _resizeSynchronizer;
FlutterResizableBackingStoreProvider* _resizableBackingStoreProvider;
FlutterThreadSynchronizer* _threadSynchronizer;
FlutterSurfaceManager* _surfaceManager;
}
@end
@@ -28,34 +28,30 @@
[self setBackgroundColor:[NSColor blackColor]];
[self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawDuringViewResize];
_reshapeListener = reshapeListener;
_resizableBackingStoreProvider =
[[FlutterResizableBackingStoreProvider alloc] initWithDevice:device
commandQueue:commandQueue
layer:self.layer];
_resizeSynchronizer =
[[FlutterResizeSynchronizer alloc] initWithDelegate:_resizableBackingStoreProvider];
_threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
_surfaceManager = [[FlutterSurfaceManager alloc] initWithDevice:device
commandQueue:commandQueue
layer:self.layer
delegate:self];
}
return self;
}
- (FlutterRenderBackingStore*)backingStoreForSize:(CGSize)size {
if ([_resizeSynchronizer shouldEnsureSurfaceForSize:size]) {
[_resizableBackingStoreProvider onBackingStoreResized:size];
}
return [_resizableBackingStoreProvider backingStore];
- (void)onPresent:(CGSize)frameSize withBlock:(dispatch_block_t)block {
[_threadSynchronizer performCommit:frameSize notify:block];
}
- (void)present {
[_resizeSynchronizer requestCommit];
- (FlutterSurfaceManager*)surfaceManager {
return _surfaceManager;
}
- (void)presentWithoutContent {
[_resizeSynchronizer noFlutterContent];
- (FlutterThreadSynchronizer*)threadSynchronizer {
return _threadSynchronizer;
}
- (void)reshaped {
CGSize scaledSize = [self convertSizeToBacking:self.bounds.size];
[_resizeSynchronizer beginResize:scaledSize
[_threadSynchronizer beginResize:scaledSize
notify:^{
[_reshapeListener viewDidReshape:self];
}];
@@ -111,7 +107,7 @@
}
- (void)shutdown {
[_resizeSynchronizer shutdown];
[_threadSynchronizer shutdown];
}
#pragma mark - NSAccessibility overrides