diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc index 5bbbb438c9d79..1189b90d88f13 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -96,6 +96,19 @@ void ContainerLayer::TryToPrepareRasterCache(PrerollContext* context, } } +#if defined(OS_MACOSX) +bool ContainerLayer::HasPlatformView() { + bool hasPlatformView = false; + for (auto& layer : layers_) { + if (layer->HasPlatformView()) { + hasPlatformView = true; + } + } + + return hasPlatformView; +} +#endif + #if defined(LEGACY_FUCHSIA_EMBEDDER) void ContainerLayer::CheckForChildLayerBelow(PrerollContext* context) { diff --git a/flow/layers/container_layer.h b/flow/layers/container_layer.h index 11eae5f3fe123..ed86200aedba3 100644 --- a/flow/layers/container_layer.h +++ b/flow/layers/container_layer.h @@ -19,6 +19,9 @@ class ContainerLayer : public Layer { void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; +#if defined(OS_MACOSX) + bool HasPlatformView() override; +#endif #if defined(LEGACY_FUCHSIA_EMBEDDER) void CheckForChildLayerBelow(PrerollContext* context) override; void UpdateScene(std::shared_ptr context) override; diff --git a/flow/layers/layer.cc b/flow/layers/layer.cc index 006f5cb84e916..1fa10c5931142 100644 --- a/flow/layers/layer.cc +++ b/flow/layers/layer.cc @@ -27,6 +27,12 @@ uint64_t Layer::NextUniqueID() { void Layer::Preroll(PrerollContext* context, const SkMatrix& matrix) {} +#if defined(OS_MACOSX) +bool Layer::HasPlatformView() { + return false; +} +#endif + Layer::AutoPrerollSaveLayerState::AutoPrerollSaveLayerState( PrerollContext* preroll_context, bool save_layer_is_active, diff --git a/flow/layers/layer.h b/flow/layers/layer.h index 4ab93ce86903e..bea69375fe52b 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -75,6 +75,10 @@ class Layer { virtual void Preroll(PrerollContext* context, const SkMatrix& matrix); +#if defined(OS_MACOSX) + virtual bool HasPlatformView(); +#endif + // Used during Preroll by layers that employ a saveLayer to manage the // PrerollContext settings with values affected by the saveLayer mechanism. // This object must be created before calling Preroll on the children to diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index 8169e8bf6ba2e..15f9bdca0bcb4 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -60,6 +60,11 @@ bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame, root_layer_->Preroll(&context, frame.root_surface_transformation()); return context.surface_needs_readback; } +#if defined(OS_MACOSX) +bool LayerTree::HasPlatformView() { + return root_layer_->HasPlatformView(); +} +#endif #if defined(LEGACY_FUCHSIA_EMBEDDER) void LayerTree::UpdateScene(std::shared_ptr context) { diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h index b59f278296381..5f90343dc7edc 100644 --- a/flow/layers/layer_tree.h +++ b/flow/layers/layer_tree.h @@ -31,6 +31,13 @@ class LayerTree { bool Preroll(CompositorContext::ScopedFrame& frame, bool ignore_raster_cache = false); + // Used to check before Prerolling to see if the layer tree + // contains a platform view. + // Specifically used for MacOS to determine if the compositor + // should be used. +#if defined(OS_MACOSX) + bool HasPlatformView(); +#endif #if defined(LEGACY_FUCHSIA_EMBEDDER) void UpdateScene(std::shared_ptr context); #endif diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 0971f93db777e..fcb1b1cc5243b 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -34,6 +34,12 @@ void PlatformViewLayer::Preroll(PrerollContext* context, std::move(params)); } +#if defined(OS_MACOSX) +bool PlatformViewLayer::HasPlatformView() { + return true; +} +#endif + void PlatformViewLayer::Paint(PaintContext& context) const { if (context.view_embedder == nullptr) { #if !defined(LEGACY_FUCHSIA_EMBEDDER) diff --git a/flow/layers/platform_view_layer.h b/flow/layers/platform_view_layer.h index e9d6fac42eb23..0d807a19d838c 100644 --- a/flow/layers/platform_view_layer.h +++ b/flow/layers/platform_view_layer.h @@ -17,6 +17,9 @@ class PlatformViewLayer : public Layer { void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; +#if defined(OS_MACOSX) + bool HasPlatformView() override; +#endif #if defined(LEGACY_FUCHSIA_EMBEDDER) // Updates the system composited scene. void UpdateScene(std::shared_ptr context) override; diff --git a/flow/surface.cc b/flow/surface.cc index 7dbe56c1e8e78..aa81de16c9319 100644 --- a/flow/surface.cc +++ b/flow/surface.cc @@ -18,4 +18,6 @@ bool Surface::ClearRenderContext() { return false; } +void Surface::SetRenderToSurface(bool render_to_surface) {} + } // namespace flutter diff --git a/flow/surface.h b/flow/surface.h index 21b248c074d4c..f407e4a98f22f 100644 --- a/flow/surface.h +++ b/flow/surface.h @@ -33,6 +33,11 @@ class Surface { virtual bool ClearRenderContext(); + // SetRenderToSurface sets whether or not the surface should be rendered to. + // This is needed for MacOS as we want to render scenes without platform views + // to the surface even in the existence of a external_view_embedder. + virtual void SetRenderToSurface(bool render_to_surface); + private: FML_DISALLOW_COPY_AND_ASSIGN(Surface); }; diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 1d326a7727166..b8fcefa7cc0a9 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -424,13 +424,30 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { compositor_context_->ui_time().SetLapTime(layer_tree.build_time()); SkCanvas* embedder_root_canvas = nullptr; +#if defined(OS_MACOSX) + // For MacOS, we only want to use the external_view_embedder / compositor + // if there is a platform view in the layer tree. + // TODO(richardjcai): Remove branching logic for using external_view_embedder + // once the compositor supports smooth resizing. + // https://github.com/flutter/flutter/issues/70848. + bool hasPlatformView = layer_tree.HasPlatformView(); + if (external_view_embedder_ && hasPlatformView) { +#else if (external_view_embedder_) { +#endif external_view_embedder_->BeginFrame( layer_tree.frame_size(), surface_->GetContext(), layer_tree.device_pixel_ratio(), raster_thread_merger_); embedder_root_canvas = external_view_embedder_->GetRootCanvas(); } +#if defined(OS_MACOSX) + if (external_view_embedder_ && hasPlatformView) { + surface_->SetRenderToSurface(false); + } else { + surface_->SetRenderToSurface(true); + } +#endif // On Android, the external view embedder deletes surfaces in `BeginFrame`. // // Deleting a surface also clears the GL context. Therefore, acquire the @@ -450,14 +467,23 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { auto root_surface_canvas = embedder_root_canvas ? embedder_root_canvas : frame->SkiaCanvas(); +#if defined(OS_MACOSX) + auto external_view_embedder = + hasPlatformView ? external_view_embedder_.get() : nullptr; + auto raster_thread_merger = hasPlatformView ? raster_thread_merger_ : nullptr; +#else + auto external_view_embedder = external_view_embedder_.get(); + auto raster_thread_merger = raster_thread_merger_; +#endif + auto compositor_frame = compositor_context_->AcquireFrame( - surface_->GetContext(), // skia GrContext - root_surface_canvas, // root surface canvas - external_view_embedder_.get(), // external view embedder - root_surface_transformation, // root surface transformation - true, // instrumentation enabled - frame->supports_readback(), // surface supports pixel reads - raster_thread_merger_ // thread merger + surface_->GetContext(), // skia GrContext + root_surface_canvas, // root surface canvas + external_view_embedder, // external view embedder + root_surface_transformation, // root surface transformation + true, // instrumentation enabled + frame->supports_readback(), // surface supports pixel reads + raster_thread_merger // thread merger ); if (compositor_frame) { @@ -466,8 +492,14 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { raster_status == RasterStatus::kSkipAndRetry) { return raster_status; } +#if defined(OS_MACOSX) + if (external_view_embedder_ && + (!raster_thread_merger_ || raster_thread_merger_->IsMerged()) && + hasPlatformView) { +#else if (external_view_embedder_ && (!raster_thread_merger_ || raster_thread_merger_->IsMerged())) { +#endif FML_DCHECK(!frame->IsSubmitted()); external_view_embedder_->SubmitFrame(surface_->GetContext(), std::move(frame)); diff --git a/shell/gpu/gpu_surface_gl.cc b/shell/gpu/gpu_surface_gl.cc index 388a22e352dd5..9edd1b991c699 100644 --- a/shell/gpu/gpu_surface_gl.cc +++ b/shell/gpu/gpu_surface_gl.cc @@ -343,4 +343,9 @@ bool GPUSurfaceGL::ClearRenderContext() { return delegate_->GLContextClearCurrent(); } +// |Surface| +void GPUSurfaceGL::SetRenderToSurface(bool render_to_surface) { + render_to_surface_ = render_to_surface; +} + } // namespace flutter diff --git a/shell/gpu/gpu_surface_gl.h b/shell/gpu/gpu_surface_gl.h index e7fa001123e52..6ec7848c3efd5 100644 --- a/shell/gpu/gpu_surface_gl.h +++ b/shell/gpu/gpu_surface_gl.h @@ -48,6 +48,9 @@ class GPUSurfaceGL : public Surface { // |Surface| bool ClearRenderContext() override; + // |Surface| + void SetRenderToSurface(bool render_to_surface) override; + private: GPUSurfaceGLDelegate* delegate_; sk_sp context_; @@ -59,7 +62,7 @@ class GPUSurfaceGL : public Surface { // external view embedder may want to render to the root surface. This is a // hack to make avoid allocating resources for the root surface when an // external view embedder is present. - const bool render_to_surface_; + bool render_to_surface_; bool valid_ = false; fml::TaskRunnerAffineWeakPtrFactory weak_factory_; diff --git a/shell/gpu/gpu_surface_software.h b/shell/gpu/gpu_surface_software.h index 23d5cdedf8fed..1f51e8f6e2dc4 100644 --- a/shell/gpu/gpu_surface_software.h +++ b/shell/gpu/gpu_surface_software.h @@ -37,7 +37,7 @@ class GPUSurfaceSoftware : public Surface { // external view embedder may want to render to the root surface. This is a // hack to make avoid allocating resources for the root surface when an // external view embedder is present. - const bool render_to_surface_; + bool render_to_surface_; fml::TaskRunnerAffineWeakPtrFactory weak_factory_; FML_DISALLOW_COPY_AND_ASSIGN(GPUSurfaceSoftware); diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 4c345a008c19c..3ad09aa02bd78 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -52,6 +52,8 @@ source_set("flutter_framework_source") { "framework/Source/FlutterEngine_Internal.h", "framework/Source/FlutterExternalTextureGL.h", "framework/Source/FlutterExternalTextureGL.mm", + "framework/Source/FlutterMacOSGLCompositor.h", + "framework/Source/FlutterMacOSGLCompositor.mm", "framework/Source/FlutterMouseCursorPlugin.h", "framework/Source/FlutterMouseCursorPlugin.mm", "framework/Source/FlutterResizeSynchronizer.h", @@ -73,9 +75,12 @@ source_set("flutter_framework_source") { sources += _flutter_framework_headers deps = [ + "//flutter/flow:flow", + "//flutter/fml", "//flutter/shell/platform/common/cpp:common_cpp_switches", "//flutter/shell/platform/darwin/common:framework_shared", "//flutter/shell/platform/embedder:embedder_as_internal_library", + "//third_party/skia", ] public_configs = [ "//flutter:config" ] @@ -115,7 +120,10 @@ executable("flutter_desktop_darwin_unittests") { sources = [ "framework/Source/FlutterEngineTest.mm", + "framework/Source/FlutterMacOSGLCompositorUnittests.mm", "framework/Source/FlutterViewControllerTest.mm", + "framework/Source/FlutterViewControllerTestUtils.h", + "framework/Source/FlutterViewControllerTestUtils.mm", ] cflags_objcc = [ "-fobjc-arc" ] diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index d9ebd55f0d7ec..4c05d80ab49f6 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -10,7 +10,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/FlutterViewController_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMacOSGLCompositor.h" #import "flutter/shell/platform/embedder/embedder.h" /** @@ -197,6 +197,15 @@ @implementation FlutterEngine { // Pointer to the Dart AOT snapshot and instruction data. _FlutterEngineAOTData* _aotData; + + // FlutterMacOSGLCompositor is created by the engine. + // This is only created when the engine has a FlutterViewController + // and used to support platform views. + // Creation / Destruction. + std::unique_ptr _macOSCompositor; + + // FlutterCompositor is copied and used in embedder.cc. + FlutterCompositor _compositor; } - (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project { @@ -306,6 +315,9 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint { flutterArguments.aot_data = _aotData; } + [self setupCompositor]; + flutterArguments.compositor = &_compositor; + FlutterEngineResult result = _embedderAPI.Initialize( FLUTTER_ENGINE_VERSION, &rendererConfig, &flutterArguments, (__bridge void*)(self), &_engine); if (result != kSuccess) { @@ -360,6 +372,43 @@ - (void)setViewController:(FlutterViewController*)controller { } } +- (void)setupCompositor { + [_mainOpenGLContext makeCurrentContext]; + + _macOSCompositor = std::make_unique(_viewController); + + _compositor = {}; + _compositor.struct_size = sizeof(FlutterCompositor); + _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( + config, backing_store_out); + }; + + _compositor.collect_backing_store_callback = [](const FlutterBackingStore* backing_store, // + void* user_data // + ) { + return reinterpret_cast(user_data)->CollectBackingStore( + backing_store); + }; + + _compositor.present_layers_callback = [](const FlutterLayer** layers, // + size_t layers_count, // + void* user_data // + ) { + return reinterpret_cast(user_data)->Present(layers, + layers_count); + }; + + __weak FlutterEngine* weak_self = self; + _macOSCompositor->SetPresentCallback( + [weak_self]() { return [weak_self engineCallbackOnPresent]; }); +} + - (id)binaryMessenger { // TODO(stuartmorgan): Switch to FlutterBinaryMessengerRelay to avoid plugins // keeping the engine alive. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMacOSGLCompositor.h b/shell/platform/darwin/macos/framework/Source/FlutterMacOSGLCompositor.h new file mode 100644 index 0000000000000..e840ba1ceab97 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterMacOSGLCompositor.h @@ -0,0 +1,52 @@ +// 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. + +#include + +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h" +#include "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "third_party/skia/include/gpu/GrDirectContext.h" + +namespace flutter { + +// FlutterMacOSGLCompositor creates and manages the backing stores used for +// rendering Flutter content and presents Flutter content and Platform views. +// Platform views are not yet supported. +// FlutterMacOSGLCompositor is created and destroyed by FlutterEngine. +class FlutterMacOSGLCompositor { + public: + FlutterMacOSGLCompositor(FlutterViewController* view_controller); + + // Creates a FlutterSurfaceManager and uses the FlutterSurfaceManager's + // underlying FBO and texture in the backing store. + // Any additional state allocated for the backing store and + // saved as user_data in the backing store must be collected + // in the backing_store's desctruction_callback field which will + // be called when the embedder collects the backing store. + bool CreateBackingStore(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out); + + // Releases the memory for any state used by the backing store. + bool CollectBackingStore(const FlutterBackingStore* backing_store); + + // Presents the FlutterLayers by updating FlutterView(s) using the + // layer content. + bool Present(const FlutterLayer** layers, size_t layers_count); + + using PresentCallback = std::function; + + // PresentCallback is called at the end of the Present function. + void SetPresentCallback(const PresentCallback& present_callback); + + private: + FlutterViewController* view_controller_; + PresentCallback present_callback_; + NSOpenGLContext* open_gl_context_; + + FML_DISALLOW_COPY_AND_ASSIGN(FlutterMacOSGLCompositor); +}; + +} // namespace flutter diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMacOSGLCompositor.mm b/shell/platform/darwin/macos/framework/Source/FlutterMacOSGLCompositor.mm new file mode 100644 index 0000000000000..58d7224e35862 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterMacOSGLCompositor.mm @@ -0,0 +1,84 @@ +// 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/FlutterMacOSGLCompositor.h" + +#import +#import "flutter/fml/logging.h" +#import "flutter/fml/platform/darwin/cf_utils.h" +#import "third_party/skia/include/core/SkCanvas.h" +#import "third_party/skia/include/core/SkSurface.h" +#import "third_party/skia/include/gpu/gl/GrGLAssembleInterface.h" +#import "third_party/skia/include/utils/mac/SkCGUtils.h" + +#include + +namespace flutter { + +FlutterMacOSGLCompositor::FlutterMacOSGLCompositor(FlutterViewController* view_controller) + : view_controller_(view_controller), + open_gl_context_(view_controller.flutterView.openGLContext) {} + +bool FlutterMacOSGLCompositor::CreateBackingStore(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + FlutterSurfaceManager* surfaceManager = + [[FlutterSurfaceManager alloc] initWithLayer:view_controller_.flutterView.layer + openGLContext:open_gl_context_ + numFramebuffers:1]; + + CGSize size = CGSizeMake(config->size.width, config->size.height); + int kFlutterSurfaceManagerFrontBuffer = 0; + [surfaceManager recreateIOSurface:kFlutterSurfaceManagerFrontBuffer size:size]; + [surfaceManager backTextureWithIOSurface:kFlutterSurfaceManagerFrontBuffer size:size]; + + backing_store_out->type = kFlutterBackingStoreTypeOpenGL; + backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + backing_store_out->open_gl.framebuffer.target = GL_RGBA8; + backing_store_out->open_gl.framebuffer.name = [surfaceManager glFrameBufferFrontId]; + backing_store_out->open_gl.framebuffer.user_data = (__bridge_retained void*)surfaceManager; + backing_store_out->open_gl.framebuffer.destruction_callback = [](void* user_data) { + if (user_data != nullptr) { + CFRelease(user_data); + } + }; + + return true; +} + +bool FlutterMacOSGLCompositor::CollectBackingStore(const FlutterBackingStore* backing_store) { + // The memory for FlutterSurfaceManager is handled in the destruction callback. + // No other memory has to be collected. + return true; +} + +bool FlutterMacOSGLCompositor::Present(const FlutterLayer** layers, size_t layers_count) { + 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: { + FlutterSurfaceManager* surfaceManager = + (__bridge FlutterSurfaceManager*)backing_store->open_gl.framebuffer.user_data; + + CGSize size = CGSizeMake(layer->size.width, layer->size.height); + [view_controller_.flutterView frameBufferIDForSize:size]; + int kFlutterSurfaceManagerFrontBuffer = 0; + [surfaceManager setLayerContentWithIOSurface:kFlutterSurfaceManagerFrontBuffer]; + break; + } + case kFlutterLayerContentTypePlatformView: + // Add functionality in follow up PR. + FML_CHECK(false) << "Presenting PlatformViews not yet supported"; + break; + }; + } + return present_callback_(); +} + +void FlutterMacOSGLCompositor::SetPresentCallback( + const FlutterMacOSGLCompositor::PresentCallback& present_callback) { + present_callback_ = present_callback; +} + +} // namespace flutter diff --git a/shell/platform/darwin/macos/framework/Source/FlutterMacOSGLCompositorUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterMacOSGLCompositorUnittests.mm new file mode 100644 index 0000000000000..6383a1cd2a6b5 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterMacOSGLCompositorUnittests.mm @@ -0,0 +1,29 @@ +// 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/FlutterMacOSGLCompositor.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h" +#import "flutter/testing/testing.h" + +namespace flutter::testing { + +TEST(FlutterMacOSGLCompositorTest, TestPresent) { + id mockViewController = CreateMockViewController(nil); + + std::unique_ptr macos_compositor = + std::make_unique(mockViewController); + + bool flag = false; + macos_compositor->SetPresentCallback([f = &flag]() { + *f = true; + return true; + }); + + ASSERT_TRUE(macos_compositor->Present(nil, 0)); + ASSERT_TRUE(flag); +} + +} // flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h b/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h index afaeebb3ba8a3..fd13089bd3de1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h @@ -1,18 +1,47 @@ -// 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 // Manages the IOSurfaces for FlutterView @interface FlutterSurfaceManager : NSObject -- (nullable instancetype)initWithLayer:(nonnull CALayer*)containingLayer - openGLContext:(nonnull NSOpenGLContext*)opengLContext; +- (instancetype)initWithLayer:(CALayer*)containingLayer + openGLContext:(NSOpenGLContext*)openGLContext + numFramebuffers:(int)numFramebuffers; - (void)ensureSurfaceSize:(CGSize)size; - (void)swapBuffers; -- (uint32_t)glFrameBufferId; +/** + * Sets the CALayer content to the content of _ioSurface[kBack]. + */ +- (void)setLayerContent; + +/** + * Sets the CALayer content to the content of _ioSurface[index]. + */ +- (void)setLayerContentWithIOSurface:(int)index; + +/** + * Recreates the IOSurface _ioSurface[index] with specified size. + */ +- (void)recreateIOSurface:(int)index size:(CGSize)size; + +/** + * Binds the IOSurface at _ioSurface[index] to the texture id at + * _backingTexture[index] and fbo at _frameBufferId[index]. + */ +- (void)backTextureWithIOSurface:(int)index size:(CGSize)size; + +/** + * Returns the kBack framebuffer. + */ +- (uint32_t)glFrameBufferBackId; + +/** + * Returns the kFront framebuffer. + * The framebuffer is used by FlutterMacOSCompositor to create a backing store. + * The framebuffer is collected when the backing store that uses the + * framebuffer is collected. + */ +- (uint32_t)glFrameBufferFrontId; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm index c316d7359245f..dfcdf0fd68e18 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm @@ -29,7 +29,8 @@ @interface FlutterSurfaceManager () { @implementation FlutterSurfaceManager - (instancetype)initWithLayer:(CALayer*)containingLayer - openGLContext:(NSOpenGLContext*)openGLContext { + openGLContext:(NSOpenGLContext*)openGLContext + numFramebuffers:(int)numFramebuffers { if (self = [super init]) { _containingLayer = containingLayer; _openGLContext = openGLContext; @@ -41,11 +42,12 @@ - (instancetype)initWithLayer:(CALayer*)containingLayer MacOSGLContextSwitch context_switch(openGLContext); - glGenFramebuffers(2, _frameBufferId); - glGenTextures(2, _backingTexture); + glGenFramebuffers(numFramebuffers, _frameBufferId); + glGenTextures(numFramebuffers, _backingTexture); - [self createFramebuffer:_frameBufferId[0] withBackingTexture:_backingTexture[0]]; - [self createFramebuffer:_frameBufferId[1] withBackingTexture:_backingTexture[1]]; + for (int i = 0; i < numFramebuffers; ++i) { + [self createFramebuffer:_frameBufferId[i] withBackingTexture:_backingTexture[1]]; + } } return self; } @@ -69,49 +71,62 @@ - (void)ensureSurfaceSize:(CGSize)size { MacOSGLContextSwitch context_switch(_openGLContext); for (int i = 0; i < kFlutterSurfaceManagerBufferCount; ++i) { - if (_ioSurface[i]) { - CFRelease(_ioSurface[i]); - } - unsigned pixelFormat = 'BGRA'; - unsigned bytesPerElement = 4; - - size_t bytesPerRow = - IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width * bytesPerElement); - size_t totalBytes = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height * bytesPerRow); - NSDictionary* options = @{ - (id)kIOSurfaceWidth : @(size.width), - (id)kIOSurfaceHeight : @(size.height), - (id)kIOSurfacePixelFormat : @(pixelFormat), - (id)kIOSurfaceBytesPerElement : @(bytesPerElement), - (id)kIOSurfaceBytesPerRow : @(bytesPerRow), - (id)kIOSurfaceAllocSize : @(totalBytes), - }; - _ioSurface[i] = IOSurfaceCreate((CFDictionaryRef)options); - - glBindTexture(GL_TEXTURE_RECTANGLE_ARB, _backingTexture[i]); - - CGLTexImageIOSurface2D(CGLGetCurrentContext(), GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, - int(size.width), int(size.height), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, - _ioSurface[i], 0 /* plane */); - glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); - - glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferId[i]); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, - _backingTexture[i], 0); - - NSAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, - @"Framebuffer status check failed"); + [self recreateIOSurface:i size:size]; + [self backTextureWithIOSurface:i size:size]; } } -- (void)swapBuffers { +- (void)recreateIOSurface:(int)index size:(CGSize)size { + if (_ioSurface[index]) { + CFRelease(_ioSurface[index]); + } + + 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[index] = IOSurfaceCreate((CFDictionaryRef)options); +} + +- (void)backTextureWithIOSurface:(int)index size:(CGSize)size { + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, _backingTexture[index]); + + CGLTexImageIOSurface2D(CGLGetCurrentContext(), GL_TEXTURE_RECTANGLE_ARB, GL_RGBA, int(size.width), + int(size.height), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, _ioSurface[index], + 0 /* plane */); + glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, _frameBufferId[index]); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, + _backingTexture[index], 0); + + NSAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, + @"Framebuffer status check failed"); +} + +- (void)setLayerContent { + [self setLayerContentWithIOSurface:kFlutterSurfaceManagerBackBuffer]; +} + +- (void)setLayerContentWithIOSurface:(int)ioSurfaceNum { _contentLayer.frame = _containingLayer.bounds; // The surface is an OpenGL texture, which means it has origin in bottom left corner // and needs to be flipped vertically _contentLayer.transform = CATransform3DMakeScale(1, -1, 1); - [_contentLayer setContents:(__bridge id)_ioSurface[kFlutterSurfaceManagerBackBuffer]]; + [_contentLayer setContents:(__bridge id)_ioSurface[ioSurfaceNum]]; +} +- (void)swapBuffers { std::swap(_ioSurface[kFlutterSurfaceManagerBackBuffer], _ioSurface[kFlutterSurfaceManagerFrontBuffer]); std::swap(_frameBufferId[kFlutterSurfaceManagerBackBuffer], @@ -120,11 +135,18 @@ - (void)swapBuffers { _backingTexture[kFlutterSurfaceManagerFrontBuffer]); } -- (uint32_t)glFrameBufferId { +- (uint32_t)glFrameBufferBackId { return _frameBufferId[kFlutterSurfaceManagerBackBuffer]; } +- (uint32_t)glFrameBufferFrontId { + return _frameBufferId[kFlutterSurfaceManagerFrontBuffer]; +} + - (void)dealloc { + [_contentLayer removeFromSuperlayer]; + glDeleteFramebuffers(kFlutterSurfaceManagerBufferCount, _frameBufferId); + glDeleteTextures(kFlutterSurfaceManagerBufferCount, _backingTexture); for (int i = 0; i < kFlutterSurfaceManagerBufferCount; ++i) { if (_ioSurface[i]) { CFRelease(_ioSurface[i]); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterView.mm b/shell/platform/darwin/macos/framework/Source/FlutterView.mm index d6ce889f54e81..a5bfc58b86b7f 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterView.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterView.mm @@ -38,7 +38,8 @@ - (instancetype)initWithFrame:(NSRect)frame _resizeSynchronizer = [[FlutterResizeSynchronizer alloc] initWithDelegate:self]; _surfaceManager = [[FlutterSurfaceManager alloc] initWithLayer:self.layer - openGLContext:self.openGLContext]; + openGLContext:self.openGLContext + numFramebuffers:2]; _reshapeListener = reshapeListener; } @@ -54,6 +55,7 @@ - (void)resizeSynchronizerCommit:(FlutterResizeSynchronizer*)synchronizer { [CATransaction begin]; [CATransaction setDisableActions:YES]; + [_surfaceManager setLayerContent]; [_surfaceManager swapBuffers]; [CATransaction commit]; @@ -63,7 +65,7 @@ - (int)frameBufferIDForSize:(CGSize)size { if ([_resizeSynchronizer shouldEnsureSurfaceForSize:size]) { [_surfaceManager ensureSurfaceSize:size]; } - return [_surfaceManager glFrameBufferId]; + return [_surfaceManager glFrameBufferBackId]; } - (void)present { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm index ab73278d9c5bf..671520f494923 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm @@ -9,34 +9,14 @@ #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h" #include "flutter/testing/testing.h" namespace flutter::testing { -// Returns a mock FlutterViewController that is able to work in environments -// without a real pasteboard. -id mockViewController(NSString* pasteboardString) { - NSString* fixtures = @(testing::GetFixturesPath()); - FlutterDartProject* project = [[FlutterDartProject alloc] - initWithAssetsPath:fixtures - ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; - FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project]; - - // Mock pasteboard so that this test will work in environments without a - // real pasteboard. - id pasteboardMock = OCMClassMock([NSPasteboard class]); - OCMExpect([pasteboardMock stringForType:[OCMArg any]]).andDo(^(NSInvocation* invocation) { - NSString* returnValue = pasteboardString.length > 0 ? pasteboardString : nil; - [invocation setReturnValue:&returnValue]; - }); - id viewControllerMock = OCMPartialMock(viewController); - OCMStub([viewControllerMock pasteboard]).andReturn(pasteboardMock); - return viewControllerMock; -} - TEST(FlutterViewController, HasStringsWhenPasteboardEmpty) { // Mock FlutterViewController so that it behaves like the pasteboard is empty. - id viewControllerMock = mockViewController(nil); + id viewControllerMock = CreateMockViewController(nil); // Call hasStrings and expect it to be false. __block bool calledAfterClear = false; @@ -56,7 +36,7 @@ id mockViewController(NSString* pasteboardString) { TEST(FlutterViewController, HasStringsWhenPasteboardFull) { // Mock FlutterViewController so that it behaves like the pasteboard has a // valid string. - id viewControllerMock = mockViewController(@"some string"); + id viewControllerMock = CreateMockViewController(@"some string"); // Call hasStrings and expect it to be true. __block bool called = false; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h new file mode 100644 index 0000000000000..cc3c7f2bcff17 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h @@ -0,0 +1,18 @@ +// 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 + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" +#import "flutter/testing/testing.h" + +namespace flutter::testing { + +// Returns a mock FlutterViewController that is able to work in environments +// without a real pasteboard. +id CreateMockViewController(NSString* pasteboardString); + +} diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.mm new file mode 100644 index 0000000000000..972e3515283ed --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.mm @@ -0,0 +1,30 @@ +// 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/FlutterViewControllerTestUtils.h" + +namespace flutter::testing { + +id CreateMockViewController(NSString* pasteboardString) { + { + NSString* fixtures = @(testing::GetFixturesPath()); + FlutterDartProject* project = [[FlutterDartProject alloc] + initWithAssetsPath:fixtures + ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project]; + + // Mock pasteboard so that this test will work in environments without a + // real pasteboard. + id pasteboardMock = OCMClassMock([NSPasteboard class]); + OCMExpect([pasteboardMock stringForType:[OCMArg any]]).andDo(^(NSInvocation* invocation) { + NSString* returnValue = pasteboardString.length > 0 ? pasteboardString : nil; + [invocation setReturnValue:&returnValue]; + }); + id viewControllerMock = OCMPartialMock(viewController); + OCMStub([viewControllerMock pasteboard]).andReturn(pasteboardMock); + return viewControllerMock; + } +} + +}