diff --git a/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h b/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h index 12ead38f104f4..49032f87d3a91 100644 --- a/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h +++ b/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h @@ -63,7 +63,10 @@ FLUTTER_DARWIN_EXPORT - (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint; /** - * The `FlutterViewController` associated with this engine, if any. + * The default `FlutterViewController` associated with this engine, if any. + * + * The default view always has ID kFlutterDefaultViewId, and is the view + * operated by the APIs that do not have a view ID specified. */ @property(nonatomic, nullable, weak) FlutterViewController* viewController; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 7009f60ecb968..47f9ca39aed73 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -400,7 +400,6 @@ - (void)loadAOTData:(NSString*)assetsDir { - (void)setViewController:(FlutterViewController*)controller { if (_viewController != controller) { _viewController = controller; - [_renderer setFlutterView:controller.flutterView]; if (_semanticsEnabled && _bridge) { _bridge->UpdateDefaultViewController(_viewController); @@ -425,10 +424,14 @@ - (FlutterCompositor*)createFlutterCompositor { _macOSCompositor = std::make_unique( _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] == YES; + return [weakSelf.renderer present:viewId] == YES; } else { - [weakSelf.renderer presentWithoutContent]; + [weakSelf.renderer presentWithoutContent:viewId]; return true; } }); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h b/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h index fc5d5c04db538..d9cbe1da5abb0 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h @@ -30,30 +30,25 @@ */ - (nullable instancetype)initWithFlutterEngine:(nonnull FlutterEngine*)flutterEngine; -/** - * Sets the FlutterView to render to. - */ -- (void)setFlutterView:(nullable FlutterView*)view; - /** * Creates a FlutterRendererConfig that renders using the appropriate backend. */ - (FlutterRendererConfig)createRendererConfig; /** - * Called by the engine when the context's buffers should be swapped. + * Called by the engine when the given view's buffers should be swapped. */ -- (BOOL)present; +- (BOOL)present:(uint64_t)viewId; /** * Tells the renderer that there is no Flutter content available for this frame. */ -- (void)presentWithoutContent; +- (void)presentWithoutContent:(uint64_t)viewId; /** - * Creates a Metal texture for the given size. + * Creates a Metal texture for the given view with the given size. */ -- (FlutterMetalTexture)createTextureForSize:(CGSize)size; +- (FlutterMetalTexture)createTextureForView:(uint64_t)viewId size:(CGSize)size; /** * Populates the texture registry with the provided metalTexture. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterRenderer.mm b/shell/platform/darwin/macos/framework/Source/FlutterRenderer.mm index 3c756534eaad7..d02de6c5c8eab 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterRenderer.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterRenderer.mm @@ -6,19 +6,29 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTexture.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewEngineProvider.h" #include "flutter/shell/platform/embedder/embedder.h" #pragma mark - Static callbacks that require the engine. -static FlutterMetalTexture OnGetNextDrawable(FlutterEngine* engine, - const FlutterFrameInfo* frameInfo) { +static FlutterMetalTexture OnGetNextDrawableForDefaultView(FlutterEngine* engine, + const FlutterFrameInfo* frameInfo) { + // TODO(dkwingsmt): This callback only supports single-view, therefore it only + // operates on the default view. To support multi-view, we need a new callback + // that also receives a view ID, or pass the ID via FlutterFrameInfo. + uint64_t viewId = kFlutterDefaultViewId; CGSize size = CGSizeMake(frameInfo->size.width, frameInfo->size.height); - return [engine.renderer createTextureForSize:size]; + return [engine.renderer createTextureForView:viewId size:size]; } -static bool OnPresentDrawable(FlutterEngine* engine, const FlutterMetalTexture* texture) { - return [engine.renderer present]; +static bool OnPresentDrawableOfDefaultView(FlutterEngine* engine, + const FlutterMetalTexture* texture) { + // TODO(dkwingsmt): This callback only supports single-view, therefore it only + // 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]; } static bool OnAcquireExternalTexture(FlutterEngine* engine, @@ -33,7 +43,7 @@ static bool OnAcquireExternalTexture(FlutterEngine* engine, #pragma mark - FlutterRenderer implementation @implementation FlutterRenderer { - FlutterView* _flutterView; + FlutterViewEngineProvider* _viewProvider; FlutterDarwinContextMetalSkia* _darwinMetalContext; } @@ -41,6 +51,7 @@ @implementation FlutterRenderer { - (instancetype)initWithFlutterEngine:(nonnull FlutterEngine*)flutterEngine { self = [super initWithDelegate:self engine:flutterEngine]; if (self) { + _viewProvider = [[FlutterViewEngineProvider alloc] initWithEngine:flutterEngine]; _device = MTLCreateSystemDefaultDevice(); if (!_device) { NSLog(@"Could not acquire Metal device."); @@ -59,10 +70,6 @@ - (instancetype)initWithFlutterEngine:(nonnull FlutterEngine*)flutterEngine { return self; } -- (void)setFlutterView:(FlutterView*)view { - _flutterView = view; -} - - (FlutterRendererConfig)createRendererConfig { FlutterRendererConfig config = { .type = FlutterRendererType::kMetal, @@ -70,9 +77,9 @@ - (FlutterRendererConfig)createRendererConfig { .metal.device = (__bridge FlutterMetalDeviceHandle)_device, .metal.present_command_queue = (__bridge FlutterMetalCommandQueueHandle)_commandQueue, .metal.get_next_drawable_callback = - reinterpret_cast(OnGetNextDrawable), + reinterpret_cast(OnGetNextDrawableForDefaultView), .metal.present_drawable_callback = - reinterpret_cast(OnPresentDrawable), + reinterpret_cast(OnPresentDrawableOfDefaultView), .metal.external_texture_frame_callback = reinterpret_cast(OnAcquireExternalTexture), }; @@ -81,9 +88,15 @@ - (FlutterRendererConfig)createRendererConfig { #pragma mark - Embedder callback implementations. -- (FlutterMetalTexture)createTextureForSize:(CGSize)size { +- (FlutterMetalTexture)createTextureForView:(uint64_t)viewId size:(CGSize)size { + FlutterView* view = [_viewProvider getView:viewId]; + NSAssert(view != nil, @"Can't create texture on a non-existent view 0x%llx.", viewId); + if (view == nil) { + // FlutterMetalTexture has texture `null`, therefore is discarded. + return FlutterMetalTexture{}; + } FlutterMetalRenderBackingStore* backingStore = - (FlutterMetalRenderBackingStore*)[_flutterView backingStoreForSize:size]; + (FlutterMetalRenderBackingStore*)[view backingStoreForSize:size]; id texture = backingStore.texture; FlutterMetalTexture embedderTexture; embedderTexture.struct_size = sizeof(FlutterMetalTexture); @@ -92,19 +105,21 @@ - (FlutterMetalTexture)createTextureForSize:(CGSize)size { return embedderTexture; } -- (BOOL)present { - if (!_flutterView) { +- (BOOL)present:(uint64_t)viewId { + FlutterView* view = [_viewProvider getView:viewId]; + if (view == nil) { return NO; } - [_flutterView present]; + [view present]; return YES; } -- (void)presentWithoutContent { - if (!_flutterView) { +- (void)presentWithoutContent:(uint64_t)viewId { + FlutterView* view = [_viewProvider getView:viewId]; + if (view == nil) { return; } - [_flutterView presentWithoutContent]; + [view presentWithoutContent]; } #pragma mark - FlutterTextureRegistrar methods. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterRendererTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterRendererTest.mm index 0c9666273f0fb..6cd57e33d48f9 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterRendererTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterRendererTest.mm @@ -9,6 +9,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/testing/testing.h" @@ -24,21 +25,29 @@ ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; return [[FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true]; } + +void SetEngineDefaultView(FlutterEngine* engine, id flutterView) { + id mockFlutterViewController = OCMClassMock([FlutterViewController class]); + OCMStub([mockFlutterViewController flutterView]).andReturn(flutterView); + [engine setViewController:mockFlutterViewController]; +} + } // namespace 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 setFlutterView:mockFlutterView]; - [renderer present]; + [renderer present:kFlutterDefaultViewId]; } TEST(FlutterRenderer, TextureReturnedByFlutterView) { FlutterEngine* engine = CreateTestEngine(); FlutterRenderer* renderer = [[FlutterRenderer alloc] initWithFlutterEngine:engine]; id mockFlutterView = OCMClassMock([FlutterView class]); + SetEngineDefaultView(engine, mockFlutterView); FlutterFrameInfo frameInfo; frameInfo.struct_size = sizeof(FlutterFrameInfo); FlutterUIntSize dimensions; @@ -47,8 +56,7 @@ frameInfo.size = dimensions; CGSize size = CGSizeMake(dimensions.width, dimensions.height); [[mockFlutterView expect] backingStoreForSize:size]; - [renderer setFlutterView:mockFlutterView]; - [renderer createTextureForSize:size]; + [renderer createTextureForView:kFlutterDefaultViewId size:size]; } } // namespace flutter::testing diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 7bef2e521be5d..e4fd8cc217490 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -448,7 +448,9 @@ typedef struct { /// This information is passed to the embedder when requesting a frame buffer /// object. /// -/// See: \ref FlutterOpenGLRendererConfig.fbo_with_frame_info_callback. +/// See: \ref FlutterOpenGLRendererConfig.fbo_with_frame_info_callback, +/// \ref FlutterMetalRendererConfig.get_next_drawable_callback, +/// and \ref FlutterVulkanRendererConfig.get_next_image_callback. typedef struct { /// The size of this struct. Must be sizeof(FlutterFrameInfo). size_t struct_size; @@ -633,6 +635,8 @@ typedef struct { int64_t texture_id; /// Handle to the MTLTexture that is owned by the embedder. Engine will render /// the frame into this texture. + /// + /// A NULL texture is considered invalid. FlutterMetalTextureHandle texture; /// A baton that is not interpreted by the engine in any way. It will be given /// back to the embedder in the destruction callback below. Embedder resources