diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index b436d7548da..53dca4f67c3 100755 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -1174,6 +1174,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeybo FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMacOSExternalTexture.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositorUnittests.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRendererTest.mm diff --git a/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn b/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn index 97a89b5fa59..26b14025fe7 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn +++ b/engine/src/flutter/shell/platform/darwin/macos/BUILD.gn @@ -178,6 +178,7 @@ executable("flutter_desktop_darwin_unittests") { "framework/Source/FlutterEngineTest.mm", "framework/Source/FlutterGLCompositorUnittests.mm", "framework/Source/FlutterKeyboardManagerUnittests.mm", + "framework/Source/FlutterMetalCompositorUnittests.mm", "framework/Source/FlutterMetalRendererTest.mm", "framework/Source/FlutterMetalSurfaceManagerTest.mm", "framework/Source/FlutterOpenGLRendererTest.mm", diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h index f1dc4f961da..43e1ea20e21 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h @@ -14,14 +14,8 @@ */ @interface FlutterBackingStoreData : NSObject -- (nullable instancetype)initWithLayerId:(size_t)layerId - fbProvider:(nonnull FlutterFrameBufferProvider*)fbProvider - ioSurfaceHolder:(nonnull FlutterIOSurfaceHolder*)ioSurfaceHolder; - -/** - * The layer's key value in FlutterGLCompositor's ca_layer_map_. - */ -@property(nonatomic, readonly) size_t layerId; +- (nullable instancetype)initWithFbProvider:(nonnull FlutterFrameBufferProvider*)fbProvider + ioSurfaceHolder:(nonnull FlutterIOSurfaceHolder*)ioSurfaceHolder; /** * Provides the fbo for rendering the layer. diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.mm index 12524913d2b..7150ba30baa 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.mm @@ -11,11 +11,9 @@ @implementation FlutterBackingStoreData -- (nullable instancetype)initWithLayerId:(size_t)layerId - fbProvider:(nonnull FlutterFrameBufferProvider*)fbProvider - ioSurfaceHolder:(nonnull FlutterIOSurfaceHolder*)ioSurfaceHolder { +- (nullable instancetype)initWithFbProvider:(nonnull FlutterFrameBufferProvider*)fbProvider + ioSurfaceHolder:(nonnull FlutterIOSurfaceHolder*)ioSurfaceHolder { if (self = [super init]) { - _layerId = layerId; _frameBufferProvider = fbProvider; _ioSurfaceHolder = ioSurfaceHolder; } diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h index 42997ea464f..8e3bcff066a 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h @@ -6,6 +6,7 @@ #define FLUTTER_COMPOSITOR_H_ #include +#include #include "flutter/fml/macros.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" @@ -50,11 +51,47 @@ class FlutterCompositor { // PresentCallback is called at the end of the Present function. void SetPresentCallback(const PresentCallback& present_callback); + // Denotes the current 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: __weak const FlutterViewController* view_controller_; + // 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(); + + // Creates a CALayer object which is backed by the supplied IOSurface, and + // adds it to the root CALayer for this FlutterViewController's view. + void InsertCALayerForIOSurface( + const IOSurfaceRef& io_surface, + CATransform3D transform = CATransform3DIdentity); + + private: + // A list of the active CALayer objects for the frame that need to be removed. + std::list active_ca_layers_; + + // 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); }; diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.mm index 3f5057ab0ad..487cc41b762 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.mm @@ -18,4 +18,41 @@ void FlutterCompositor::SetPresentCallback( 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 status = present_callback_(); + SetFrameStatus(FrameStatus::kEnded); + return status; +} + +void FlutterCompositor::SetFrameStatus(FlutterCompositor::FrameStatus frame_status) { + frame_status_ = frame_status; +} + +FlutterCompositor::FrameStatus FlutterCompositor::GetFrameStatus() { + return frame_status_; +} + +void FlutterCompositor::InsertCALayerForIOSurface(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_controller_.flutterView.layer.bounds; + [content_layer setContents:(__bridge id)io_surface]; + [view_controller_.flutterView.layer addSublayer:content_layer]; + + active_ca_layers_.push_back(content_layer); +} + } // namespace flutter diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index ca10495f48b..de75c40f7bc 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -12,6 +12,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTextureGL.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderingBackend.h" @@ -139,9 +140,10 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi // Pointer to the Dart AOT snapshot and instruction data. _FlutterEngineAOTData* _aotData; - // _macOSGLCompositor is created when the engine is created and - // it's destruction is handled by ARC when the engine is destroyed. - std::unique_ptr _macOSGLCompositor; + // _macOSCompositor is created when the engine is created and + // its destruction is handled by ARC when the engine is destroyed. + // This is either a FlutterGLCompositor or a FlutterMetalCompositor instance. + std::unique_ptr _macOSCompositor; // FlutterCompositor is copied and used in embedder.cc. FlutterCompositor _compositor; @@ -324,40 +326,53 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi } - (FlutterCompositor*)createFlutterCompositor { - // When rendering with metal do not support platform views. - if ([FlutterRenderingBackend renderUsingMetal]) { - return nil; - } - - // TODO(richardjcai): Add support for creating a FlutterGLCompositor + // TODO(richardjcai): Add support for creating a FlutterCompositor // with a nil _viewController for headless engines. // https://github.com/flutter/flutter/issues/71606 if (!_viewController) { return nil; } - FlutterOpenGLRenderer* openGLRenderer = reinterpret_cast(_renderer); - [openGLRenderer.openGLContext makeCurrentContext]; + __weak FlutterEngine* weakSelf = self; - _macOSGLCompositor = - std::make_unique(_viewController, openGLRenderer.openGLContext); + if ([FlutterRenderingBackend renderUsingMetal]) { + FlutterMetalRenderer* metalRenderer = reinterpret_cast(_renderer); + _macOSCompositor = + std::make_unique(_viewController, metalRenderer.device); + _macOSCompositor->SetPresentCallback([weakSelf]() { + FlutterMetalRenderer* metalRenderer = + reinterpret_cast(weakSelf.renderer); + return [metalRenderer present:0 /*=textureID*/]; + }); + } else { + FlutterOpenGLRenderer* openGLRenderer = reinterpret_cast(_renderer); + [openGLRenderer.openGLContext makeCurrentContext]; + _macOSCompositor = std::make_unique(_viewController, + openGLRenderer.openGLContext); + + _macOSCompositor->SetPresentCallback([weakSelf]() { + FlutterOpenGLRenderer* openGLRenderer = + reinterpret_cast(weakSelf.renderer); + return [openGLRenderer glPresent]; + }); + } _compositor = {}; _compositor.struct_size = sizeof(FlutterCompositor); - _compositor.user_data = _macOSGLCompositor.get(); + _compositor.user_data = _macOSCompositor.get(); _compositor.create_backing_store_callback = [](const FlutterBackingStoreConfig* config, // FlutterBackingStore* backing_store_out, // void* user_data // ) { - return reinterpret_cast(user_data)->CreateBackingStore( + return reinterpret_cast(user_data)->CreateBackingStore( config, backing_store_out); }; _compositor.collect_backing_store_callback = [](const FlutterBackingStore* backing_store, // void* user_data // ) { - return reinterpret_cast(user_data)->CollectBackingStore( + return reinterpret_cast(user_data)->CollectBackingStore( backing_store); }; @@ -365,17 +380,9 @@ static void OnPlatformMessage(const FlutterPlatformMessage* message, FlutterEngi size_t layers_count, // void* user_data // ) { - return reinterpret_cast(user_data)->Present(layers, - layers_count); + return reinterpret_cast(user_data)->Present(layers, layers_count); }; - __weak FlutterEngine* weakSelf = self; - _macOSGLCompositor->SetPresentCallback([weakSelf]() { - FlutterOpenGLRenderer* openGLRenderer = - reinterpret_cast(weakSelf.renderer); - return [openGLRenderer glPresent]; - }); - _compositor.avoid_backing_store_cache = true; return &_compositor; diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index 99c8c6e4ce4..0b7ce35a31b 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -14,6 +14,10 @@ #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/testing/testing.h" +@interface FlutterEngineTestObjC : NSObject +- (bool)testCompositor; +@end + namespace flutter::testing { namespace { @@ -330,4 +334,49 @@ TEST(FlutterEngine, ResetsAccessibilityBridgeWhenSetsNewViewController) { [engine shutDownEngine]; } +TEST(FlutterEngine, Compositor) { + ASSERT_TRUE([[FlutterEngineTestObjC alloc] testCompositor]); +} + } // namespace flutter::testing + +@implementation FlutterEngineTestObjC + +- (bool)testCompositor { + NSString* fixtures = @(flutter::testing::GetFixturesPath()); + FlutterDartProject* project = [[FlutterDartProject alloc] + initWithAssetsPath:fixtures + ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; + id engine = [[FlutterEngine alloc] initWithName:@"test" project:project]; + + FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project]; + [viewController loadView]; + viewController.flutterView.frame = CGRectMake(0, 0, 800, 600); + [engine setViewController:viewController]; + + EXPECT_TRUE([engine runWithEntrypoint:@"can_composite_platform_views"]); + + id mockFlutterView = OCMPartialMock(viewController.flutterView); + OCMExpect([mockFlutterView present]); + + @try { + OCMVerifyAllWithDelay( // NOLINT(google-objc-avoid-throwing-exception) + mockFlutterView, 5); + } @catch (...) { + return false; + } + + 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. + EXPECT_EQ(rootLayer.sublayers.count, 2u); + + // TODO(gw280): add support for screenshot tests in this test harness + + [engine shutDownEngine]; + return true; +} + +@end diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index a379e93d867..8066bd579ea 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -9,6 +9,7 @@ #include #include "flutter/shell/platform/common/accessibility_bridge.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h" @interface FlutterEngine () diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h index ad7ee589dff..385099a66d1 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.h @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include +#ifndef FLUTTER_GL_COMPOSITOR_H_ +#define FLUTTER_GL_COMPOSITOR_H_ #include "flutter/fml/macros.h" -#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterCompositor.h" #include "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h" #include "flutter/shell/platform/embedder/embedder.h" @@ -49,29 +49,9 @@ class FlutterGLCompositor : public FlutterCompositor { private: const NSOpenGLContext* open_gl_context_; - // Count for how many CALayers have been created for a frame. - // Resets when a frame is finished. - // ca_layer_count_ is also used as a layerId. - size_t ca_layer_count_ = 0; - - // Maps a layer_id (size_t) to a CALayer. - // The layer_id starts at 0 for a given frame - // and increments by 1 for each new CALayer. - std::map ca_layer_map_; - - // frame_started_ keeps track of if a layer has been - // created for the frame. - bool frame_started_ = false; - - // Set frame_started_ to true and reset all layer state. - void StartFrame(); - - // Creates a CALayer and adds it to ca_layer_map_ and increments - // ca_layer_count_; Returns the key value (size_t) for the layer in - // ca_layer_map_. - size_t CreateCALayer(); - FML_DISALLOW_COPY_AND_ASSIGN(FlutterGLCompositor); }; } // namespace flutter + +#endif // FLUTTER_GL_COMPOSITOR_H_ diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm index 97e1c9d7f29..89f9b55b321 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositor.mm @@ -31,7 +31,7 @@ bool FlutterGLCompositor::CreateBackingStore(const FlutterBackingStoreConfig* co CGSize size = CGSizeMake(config->size.width, config->size.height); - if (!frame_started_) { + if (GetFrameStatus() != FrameStatus::kStarted) { StartFrame(); // If the backing store is for the first layer, return the fbo for the // FlutterView. @@ -47,13 +47,11 @@ bool FlutterGLCompositor::CreateBackingStore(const FlutterBackingStoreConfig* co GLuint fbo = [fb_provider glFrameBufferId]; GLuint texture = [fb_provider glTextureId]; - size_t layer_id = CreateCALayer(); - [io_surface_holder bindSurfaceToTexture:texture fbo:fbo size:size]; + FlutterBackingStoreData* data = - [[FlutterBackingStoreData alloc] initWithLayerId:layer_id - fbProvider:fb_provider - ioSurfaceHolder:io_surface_holder]; + [[FlutterBackingStoreData alloc] initWithFbProvider:fb_provider + ioSurfaceHolder:io_surface_holder]; backing_store_out->open_gl.framebuffer.name = fbo; backing_store_out->open_gl.framebuffer.user_data = (__bridge_retained void*)data; @@ -64,6 +62,7 @@ bool FlutterGLCompositor::CreateBackingStore(const FlutterBackingStoreConfig* co backing_store_out->open_gl.framebuffer.target = GL_RGBA8; backing_store_out->open_gl.framebuffer.destruction_callback = [](void* user_data) { if (user_data != nullptr) { + // This deletes the OpenGL framebuffer object and texture backing it. CFRelease(user_data); } }; @@ -76,6 +75,8 @@ bool FlutterGLCompositor::CollectBackingStore(const FlutterBackingStore* backing } bool FlutterGLCompositor::Present(const FlutterLayer** layers, size_t layers_count) { + SetFrameStatus(FrameStatus::kPresenting); + for (size_t i = 0; i < layers_count; ++i) { const auto* layer = layers[i]; FlutterBackingStore* backing_store = const_cast(layer->backing_store); @@ -86,19 +87,11 @@ bool FlutterGLCompositor::Present(const FlutterLayer** layers, size_t layers_cou (__bridge FlutterBackingStoreData*)backing_store->open_gl.framebuffer.user_data; FlutterIOSurfaceHolder* io_surface_holder = [backing_store_data ioSurfaceHolder]; - size_t layer_id = [backing_store_data layerId]; - - CALayer* content_layer = ca_layer_map_[layer_id]; - - FML_CHECK(content_layer) << "Unable to find a content layer with layer id " << layer_id; - - content_layer.frame = content_layer.superlayer.bounds; + IOSurfaceRef io_surface = [io_surface_holder ioSurface]; // The surface is an OpenGL texture, which means it has origin in bottom left corner // and needs to be flipped vertically - content_layer.transform = CATransform3DMakeScale(1, -1, 1); - IOSurfaceRef io_surface_contents = [io_surface_holder ioSurface]; - [content_layer setContents:(__bridge id)io_surface_contents]; + InsertCALayerForIOSurface(io_surface, CATransform3DMakeScale(1, -1, 1)); } break; } @@ -108,39 +101,8 @@ bool FlutterGLCompositor::Present(const FlutterLayer** layers, size_t layers_cou break; }; } - // The frame has been presented, prepare FlutterGLCompositor to - // render a new frame. - frame_started_ = false; - return present_callback_(); -} -void FlutterGLCompositor::StartFrame() { - // First reset all the state. - ca_layer_count_ = 0; - - // First remove all CALayers from the superlayer. - for (auto const& ca_layer_kvp : ca_layer_map_) { - [ca_layer_kvp.second removeFromSuperlayer]; - } - - // Reset layer map. - ca_layer_map_.clear(); - - frame_started_ = true; -} - -size_t FlutterGLCompositor::CreateCALayer() { - if (!view_controller_) { - return 0; - } - - // FlutterGLCompositor manages the lifecycle of content layers. - // The id for a CALayer starts at 0 and increments by 1 for - // any given frame. - CALayer* content_layer = [[CALayer alloc] init]; - [view_controller_.flutterView.layer addSublayer:content_layer]; - ca_layer_map_[ca_layer_count_] = content_layer; - return ca_layer_count_++; + return EndFrame(); } } // namespace flutter diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h index 0eeb56f5c0f..2c089e2f593 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h @@ -12,7 +12,8 @@ namespace flutter { class FlutterMetalCompositor : public FlutterCompositor { public: - explicit FlutterMetalCompositor(FlutterViewController* view_controller); + explicit FlutterMetalCompositor(FlutterViewController* view_controller, + id mtl_device); virtual ~FlutterMetalCompositor() = default; @@ -40,6 +41,9 @@ class FlutterMetalCompositor : public FlutterCompositor { // frame to the FlutterView(s). bool Present(const FlutterLayer** layers, size_t layers_count) override; + private: + const id mtl_device_; + FML_DISALLOW_COPY_AND_ASSIGN(FlutterMetalCompositor); }; diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.mm index 56de5280eab..aa36b4ae292 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.mm @@ -4,26 +4,100 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h" + #include "flutter/fml/logging.h" namespace flutter { -FlutterMetalCompositor::FlutterMetalCompositor(FlutterViewController* view_controller) - : FlutterCompositor(view_controller) {} +FlutterMetalCompositor::FlutterMetalCompositor(FlutterViewController* view_controller, + id mtl_device) + : FlutterCompositor(view_controller), mtl_device_(mtl_device) {} bool FlutterMetalCompositor::CreateBackingStore(const FlutterBackingStoreConfig* config, FlutterBackingStore* backing_store_out) { - FML_CHECK(false) << "Not implemented, see issue: https://github.com/flutter/flutter/issues/81320"; - return false; + if (!view_controller_) { + 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. + FlutterMetalRenderBackingStore* backingStore = + reinterpret_cast( + [view_controller_.flutterView 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; + } + + 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 FlutterMetalCompositor::CollectBackingStore(const FlutterBackingStore* backing_store) { - FML_CHECK(false) << "Not implemented, see issue: https://github.com/flutter/flutter/issues/81320"; - return false; + // 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); + } + return true; } bool FlutterMetalCompositor::Present(const FlutterLayer** layers, size_t layers_count) { - FML_CHECK(false) << "Not implemented, see issue: https://github.com/flutter/flutter/issues/81320"; - return false; + SetFrameStatus(FrameStatus::kPresenting); + + for (size_t i = 0; i < layers_count; ++i) { + const auto* layer = layers[i]; + FlutterBackingStore* backing_store = const_cast(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(io_surface); + } + break; + } + case kFlutterLayerContentTypePlatformView: + // Add functionality in follow up PR. + FML_LOG(WARNING) << "Presenting PlatformViews not yet supported"; + break; + }; + } + + return EndFrame(); } } // namespace flutter diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositorUnittests.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositorUnittests.mm new file mode 100644 index 00000000000..0f4dc409fbd --- /dev/null +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositorUnittests.mm @@ -0,0 +1,71 @@ +// 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 + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h" +#import "flutter/testing/testing.h" + +namespace flutter::testing { + +TEST(FlutterMetalCompositorTest, TestPresent) { + id mockViewController = CreateMockViewController(nil); + + std::unique_ptr macos_compositor = + std::make_unique(mockViewController, nullptr); + + bool flag = false; + macos_compositor->SetPresentCallback([f = &flag]() { + *f = true; + return true; + }); + + ASSERT_TRUE(macos_compositor->Present(nil, 0)); + ASSERT_TRUE(flag); +} + +TEST(FlutterMetalCompositorTest, TestCreate) { + id mockViewController = CreateMockViewController(nil); + [mockViewController loadView]; + + std::unique_ptr macos_compositor = + std::make_unique(mockViewController, nullptr); + + FlutterBackingStore backing_store; + FlutterBackingStoreConfig config; + config.struct_size = sizeof(FlutterBackingStoreConfig); + config.size.width = 800; + 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 texture = (__bridge id)backing_store.metal.texture.texture; + ASSERT_EQ(texture.width, 800ul); + ASSERT_EQ(texture.height, 600ul); +} + +TEST(FlutterMetalCompositorTest, TestCompositing) { + id mockViewController = CreateMockViewController(nil); + [mockViewController loadView]; + + std::unique_ptr macos_compositor = + std::make_unique(mockViewController, nullptr); + + FlutterBackingStore backing_store; + FlutterBackingStoreConfig config; + config.struct_size = sizeof(FlutterBackingStoreConfig); + config.size.width = 800; + 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 texture = (__bridge id)backing_store.metal.texture.texture; + ASSERT_EQ(texture.width, 800u); + ASSERT_EQ(texture.height, 600u); +} + +} // flutter::testing diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart index f858e3d0e2d..eed6a499007 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart @@ -2,6 +2,29 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:ui'; void main() { } + +Picture CreateSimplePicture() { + Paint blackPaint = Paint(); + PictureRecorder baseRecorder = PictureRecorder(); + Canvas canvas = Canvas(baseRecorder); + canvas.drawRect(Rect.fromLTRB(0.0, 0.0, 1000.0, 1000.0), blackPaint); + return baseRecorder.endRecording(); +} + +@pragma('vm:entry-point') +void can_composite_platform_views() { + PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + SceneBuilder builder = SceneBuilder(); + builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); + builder.pushOffset(1.0, 2.0); + builder.addPlatformView(42, width: 123.0, height: 456.0); + builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); + builder.pop(); // offset + PlatformDispatcher.instance.views.first.render(builder.build()); + }; + PlatformDispatcher.instance.scheduleFrame(); +}