[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:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, //
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user