From f3db36bee8de5f21cf6d97aa9df451e6adcd047f Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 25 Mar 2020 18:44:18 -0700 Subject: [PATCH 1/6] Reland unobstructed platform views - (Reverts #17326)" This reverts commit b235233e9d396ed240f09ce687ee7d51b72dfce1. --- flow/embedded_views.cc | 5 +- flow/embedded_views.h | 5 +- flow/layers/picture_layer.cc | 2 +- flow/layers/picture_layer_unittests.cc | 3 - shell/common/rasterizer.cc | 12 +- .../framework/Source/FlutterPlatformViews.mm | 321 ++++++++++++------ .../Source/FlutterPlatformViews_Internal.h | 87 ++++- .../Source/FlutterPlatformViews_Internal.mm | 3 +- shell/platform/darwin/ios/ios_surface.h | 5 +- shell/platform/darwin/ios/ios_surface.mm | 12 +- .../embedder_external_view_embedder.cc | 6 +- .../embedder_external_view_embedder.h | 5 +- .../Scenarios.xcodeproj/project.pbxproj | 4 + .../ios/Scenarios/Scenarios/AppDelegate.m | 7 + .../PlatformViewGestureRecognizerTests.m | 6 +- .../UnobstructedPlatformViewTests.m | 254 ++++++++++++++ ...tform_view_clippath_iPhone 8_simulator.png | Bin 30022 -> 20295 bytes ...form_view_transform_iPhone 8_simulator.png | Bin 33727 -> 22360 bytes testing/scenario_app/lib/main.dart | 6 + .../scenario_app/lib/src/platform_view.dart | 227 ++++++++++++- 20 files changed, 844 insertions(+), 126 deletions(-) create mode 100644 testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index c660f4691318b..5234bf1e50c8c 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -6,10 +6,13 @@ namespace flutter { -bool ExternalViewEmbedder::SubmitFrame(GrContext* context) { +bool ExternalViewEmbedder::SubmitFrame(GrContext* context, + SkCanvas* background_canvas) { return false; }; +void ExternalViewEmbedder::FinishFrame(){}; + void MutatorsStack::PushClipRect(const SkRect& rect) { std::shared_ptr element = std::make_shared(rect); vector_.push_back(element); diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 030eb88c8a06d..7a491a8a152ef 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -248,7 +248,10 @@ class ExternalViewEmbedder { // Must be called on the UI thread. virtual SkCanvas* CompositeEmbeddedView(int view_id) = 0; - virtual bool SubmitFrame(GrContext* context); + virtual bool SubmitFrame(GrContext* context, SkCanvas* background_canvas); + + // This is called after submitting the embedder frame and the surface frame. + virtual void FinishFrame(); FML_DISALLOW_COPY_AND_ASSIGN(ExternalViewEmbedder); diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index 3bc7e394c1033..08c09cc9e833b 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -59,7 +59,7 @@ void PictureLayer::Paint(PaintContext& context) const { return; } } - context.leaf_nodes_canvas->drawPicture(picture()); + picture()->playback(context.leaf_nodes_canvas); } } // namespace flutter diff --git a/flow/layers/picture_layer_unittests.cc b/flow/layers/picture_layer_unittests.cc index 687c870eeac66..4f565cf500ecc 100644 --- a/flow/layers/picture_layer_unittests.cc +++ b/flow/layers/picture_layer_unittests.cc @@ -94,9 +94,6 @@ TEST_F(PictureLayerTest, SimplePicture) { 1, MockCanvas::SetMatrixData{RasterCache::GetIntegralTransCTM( layer_offset_matrix)}}, #endif - MockCanvas::DrawCall{ - 1, MockCanvas::DrawPictureData{mock_picture->serialize(), SkPaint(), - SkMatrix()}}, MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); } diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 811898b5a42d2..f7e4350fe9c97 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -342,9 +342,17 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { if (raster_status == RasterStatus::kFailed) { return raster_status; } - frame->Submit(); if (external_view_embedder != nullptr) { - external_view_embedder->SubmitFrame(surface_->GetContext()); + external_view_embedder->SubmitFrame(surface_->GetContext(), + root_surface_canvas); + // The external view embedder may mutate the root surface canvas while + // submitting the frame. + // Therefore, submit the final frame after asking the external view + // embedder to submit the frame. + frame->Submit(); + external_view_embedder->FinishFrame(); + } else { + frame->Submit(); } FireNextFrameCallbackIfPresent(); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index b86c4623fa7a7..32c91c91e4077 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -8,16 +8,77 @@ #import "flutter/shell/platform/darwin/ios/ios_surface.h" #import "flutter/shell/platform/darwin/ios/ios_surface_gl.h" +#include #include #include #include #include "FlutterPlatformViews_Internal.h" +#include "flutter/flow/rtree.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/shell/common/persistent_cache.h" #include "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" namespace flutter { +std::shared_ptr FlutterPlatformViewLayerPool::GetLayer( + GrContext* gr_context, + std::shared_ptr ios_context) { + if (available_layer_index_ >= layers_.size()) { + std::shared_ptr layer; + + if (!gr_context) { + fml::scoped_nsobject overlay_view([[FlutterOverlayView alloc] init]); + overlay_view.get().autoresizingMask = + (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + std::unique_ptr ios_surface = + [overlay_view.get() createSurface:std::move(ios_context)]; + std::unique_ptr surface = ios_surface->CreateGPUSurface(); + + layer = std::make_shared( + std::move(overlay_view), std::move(ios_surface), std::move(surface)); + } else { + CGFloat screenScale = [UIScreen mainScreen].scale; + fml::scoped_nsobject overlay_view( + [[FlutterOverlayView alloc] initWithContentsScale:screenScale]); + overlay_view.get().autoresizingMask = + (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + std::unique_ptr ios_surface = + [overlay_view.get() createSurface:std::move(ios_context)]; + std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); + + layer = std::make_shared( + std::move(overlay_view), std::move(ios_surface), std::move(surface)); + layer->gr_context = gr_context; + } + layers_.push_back(layer); + } + auto layer = layers_[available_layer_index_]; + if (gr_context != layer->gr_context) { + layer->gr_context = gr_context; + // The overlay already exists, but the GrContext was changed so we need to recreate + // the rendering surface with the new GrContext. + IOSSurface* ios_surface = layer->ios_surface.get(); + std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); + layer->surface = std::move(surface); + } + available_layer_index_++; + return layer; +} + +void FlutterPlatformViewLayerPool::RecycleLayers() { + available_layer_index_ = 0; +} + +std::vector> +FlutterPlatformViewLayerPool::GetUnusedLayers() { + std::vector> results; + for (size_t i = available_layer_index_; i < layers_.size(); i++) { + results.push_back(layers_[i]); + } + return results; +} + void FlutterPlatformViewsController::SetFlutterView(UIView* flutter_view) { flutter_view_.reset([flutter_view retain]); } @@ -83,6 +144,9 @@ NSObject* embedded_view = [factory createWithFrame:CGRectZero viewIdentifier:viewId arguments:params]; + // Set a unique view identifier, so the platform view can be identified in unit tests. + [embedded_view view].accessibilityIdentifier = + [NSString stringWithFormat:@"platform_view[%ld]", viewId]; views_[viewId] = fml::scoped_nsobject>([embedded_view retain]); FlutterTouchInterceptingView* touch_interceptor = [[[FlutterTouchInterceptingView alloc] @@ -196,8 +260,11 @@ int view_id, std::unique_ptr params) { picture_recorders_[view_id] = std::make_unique(); - picture_recorders_[view_id]->beginRecording(SkRect::Make(frame_size_)); - picture_recorders_[view_id]->getRecordingCanvas()->clear(SK_ColorTRANSPARENT); + + auto rtree_factory = RTreeFactory(); + platform_view_rtrees_[view_id] = rtree_factory.getInstance(); + picture_recorders_[view_id]->beginRecording(SkRect::Make(frame_size_), &rtree_factory); + composition_order_.push_back(view_id); if (current_composition_params_.count(view_id) == 1 && @@ -361,77 +428,185 @@ composition_order_.clear(); active_composition_order_.clear(); picture_recorders_.clear(); + platform_view_rtrees_.clear(); current_composition_params_.clear(); clip_count_.clear(); views_to_recomposite_.clear(); + layer_pool_->RecycleLayers(); +} + +SkRect FlutterPlatformViewsController::GetPlatformViewRect(int view_id) { + UIView* platform_view = [views_[view_id].get() view]; + UIScreen* screen = [UIScreen mainScreen]; + CGRect platform_view_cgrect = [platform_view convertRect:platform_view.bounds + toView:flutter_view_]; + return SkRect::MakeXYWH(platform_view_cgrect.origin.x * screen.scale, // + platform_view_cgrect.origin.y * screen.scale, // + platform_view_cgrect.size.width * screen.scale, // + platform_view_cgrect.size.height * screen.scale // + ); } bool FlutterPlatformViewsController::SubmitFrame(GrContext* gr_context, - std::shared_ptr ios_context) { + std::shared_ptr ios_context, + SkCanvas* background_canvas) { DisposeViews(); - bool did_submit = true; - for (int64_t view_id : composition_order_) { - EnsureOverlayInitialized(view_id, ios_context, gr_context); - auto frame = overlays_[view_id]->surface->AcquireFrame(frame_size_); - // If frame is null, AcquireFrame already printed out an error message. - if (frame) { - SkCanvas* canvas = frame->SkiaCanvas(); - canvas->drawPicture(picture_recorders_[view_id]->finishRecordingAsPicture()); - canvas->flush(); - did_submit &= frame->Submit(); + // Resolve all pending GPU operations before allocating a new surface. + background_canvas->flush(); + // Clipping the background canvas before drawing the picture recorders requires to + // save and restore the clip context. + SkAutoCanvasRestore save(background_canvas, /*doSave=*/true); + // Maps a platform view id to a vector of `FlutterPlatformViewLayer`. + LayersMap platform_view_layers; + + auto did_submit = true; + auto num_platform_views = composition_order_.size(); + + for (size_t i = 0; i < num_platform_views; i++) { + int64_t platform_view_id = composition_order_[i]; + sk_sp rtree = platform_view_rtrees_[platform_view_id]; + sk_sp picture = picture_recorders_[platform_view_id]->finishRecordingAsPicture(); + + // Check if the current picture contains overlays that intersect with the + // current platform view or any of the previous platform views. + for (size_t j = i + 1; j > 0; j--) { + int64_t current_platform_view_id = composition_order_[j - 1]; + SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id); + std::list intersection_rects = + rtree->searchNonOverlappingDrawnRects(platform_view_rect); + auto allocation_size = intersection_rects.size(); + + // For testing purposes, the overlay id is used to find the overlay view. + // This is the index of the layer for the current platform view. + auto overlay_id = platform_view_layers[current_platform_view_id].size(); + + // If the max number of allocations per platform view is exceeded, + // then join all the rects into a single one. + // + // TODO(egarciad): Consider making this configurable. + // https://github.com/flutter/flutter/issues/52510 + if (allocation_size > kMaxLayerAllocations) { + SkRect joined_rect; + for (const SkRect& rect : intersection_rects) { + joined_rect.join(rect); + } + // Replace the rects in the intersection rects list for a single rect that is + // the union of all the rects in the list. + intersection_rects.clear(); + intersection_rects.push_back(joined_rect); + } + for (SkRect& joined_rect : intersection_rects) { + // Get the intersection rect between the current rect + // and the platform view rect. + joined_rect.intersect(platform_view_rect); + // Clip the background canvas, so it doesn't contain any of the pixels drawn + // on the overlay layer. + background_canvas->clipRect(joined_rect, SkClipOp::kDifference); + // Get a new host layer. + auto layer = GetLayer(gr_context, // + ios_context, // + picture, // + joined_rect, // + current_platform_view_id, // + overlay_id // + ); + did_submit &= layer->did_submit_last_frame; + platform_view_layers[current_platform_view_id].push_back(layer); + overlay_id++; + } } - } - picture_recorders_.clear(); - if (composition_order_ == active_composition_order_) { - composition_order_.clear(); - return did_submit; - } - DetachUnusedLayers(); - active_composition_order_.clear(); - UIView* flutter_view = flutter_view_.get(); + background_canvas->drawPicture(picture); + } + // If a layer was allocated in the previous frame, but it's not used in the current frame, + // then it can be removed from the scene. + RemoveUnusedLayers(); + // Organize the layers by their z indexes. + BringLayersIntoView(platform_view_layers); + // Mark all layers as available, so they can be used in the next frame. + layer_pool_->RecycleLayers(); + // Reset the composition order, so next frame starts empty. + composition_order_.clear(); + + return did_submit; +} +void FlutterPlatformViewsController::BringLayersIntoView(LayersMap layer_map) { + UIView* flutter_view = flutter_view_.get(); + auto zIndex = 0; for (size_t i = 0; i < composition_order_.size(); i++) { - int view_id = composition_order_[i]; - // We added a chain of super views to the platform view to handle clipping. - // The `platform_view_root` is the view at the top of the chain which is a direct subview of the - // `FlutterView`. - UIView* platform_view_root = root_views_[view_id].get(); - UIView* overlay = overlays_[view_id]->overlay_view; - FML_CHECK(platform_view_root.superview == overlay.superview); - if (platform_view_root.superview == flutter_view) { - [flutter_view bringSubviewToFront:platform_view_root]; - [flutter_view bringSubviewToFront:overlay]; - } else { + int64_t platform_view_id = composition_order_[i]; + std::vector> layers = layer_map[platform_view_id]; + UIView* platform_view_root = root_views_[platform_view_id].get(); + + if (platform_view_root.superview != flutter_view) { [flutter_view addSubview:platform_view_root]; - [flutter_view addSubview:overlay]; - overlay.frame = flutter_view.bounds; + } else { + platform_view_root.layer.zPosition = zIndex++; } - - active_composition_order_.push_back(view_id); + for (const std::shared_ptr& layer : layers) { + if ([layer->overlay_view superview] != flutter_view) { + [flutter_view addSubview:layer->overlay_view]; + } else { + layer->overlay_view.get().layer.zPosition = zIndex++; + } + } + active_composition_order_.push_back(platform_view_id); } - composition_order_.clear(); - return did_submit; } -void FlutterPlatformViewsController::DetachUnusedLayers() { +std::shared_ptr FlutterPlatformViewsController::GetLayer( + GrContext* gr_context, + std::shared_ptr ios_context, + sk_sp picture, + SkRect rect, + int64_t view_id, + int64_t overlay_id) { + auto layer = layer_pool_->GetLayer(gr_context, ios_context); + auto screenScale = [UIScreen mainScreen].scale; + // Set the size of the overlay UIView. + layer->overlay_view.get().frame = CGRectMake(rect.x() / screenScale, // + rect.y() / screenScale, // + rect.width() / screenScale, // + rect.height() / screenScale // + ); + // Set a unique view identifier, so the overlay can be identified in unit tests. + layer->overlay_view.get().accessibilityIdentifier = + [NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id]; + + std::unique_ptr frame = + layer->surface->AcquireFrame(SkISize::Make(rect.width(), rect.height())); + // If frame is null, AcquireFrame already printed out an error message. + if (!frame) { + return layer; + } + auto overlay_canvas = frame->SkiaCanvas(); + overlay_canvas->clear(SK_ColorTRANSPARENT); + // Offset the picture since its absolute position on the scene is determined + // by the position of the overlay view. + overlay_canvas->translate(-rect.x(), -rect.y()); + overlay_canvas->drawPicture(picture); + + layer->did_submit_last_frame = frame->Submit(); + return layer; +} + +void FlutterPlatformViewsController::RemoveUnusedLayers() { + auto layers = layer_pool_->GetUnusedLayers(); + for (const std::shared_ptr& layer : layers) { + [layer->overlay_view removeFromSuperview]; + } + std::unordered_set composition_order_set; for (int64_t view_id : composition_order_) { composition_order_set.insert(view_id); } - + // Remove unused platform views. for (int64_t view_id : active_composition_order_) { if (composition_order_set.find(view_id) == composition_order_set.end()) { - if (root_views_.find(view_id) == root_views_.end()) { - continue; - } - // We added a chain of super views to the platform view to handle clipping. - // The `platform_view_root` is the view at the top of the chain which is a direct subview of - // the `FlutterView`. UIView* platform_view_root = root_views_[view_id].get(); [platform_view_root removeFromSuperview]; - [overlays_[view_id]->overlay_view.get() removeFromSuperview]; } } } @@ -458,56 +633,6 @@ views_to_dispose_.clear(); } -void FlutterPlatformViewsController::EnsureOverlayInitialized( - int64_t overlay_id, - std::shared_ptr ios_context, - GrContext* gr_context) { - FML_DCHECK(flutter_view_); - - auto overlay_it = overlays_.find(overlay_id); - - if (!gr_context) { - if (overlays_.count(overlay_id) != 0) { - return; - } - fml::scoped_nsobject overlay_view([[FlutterOverlayView alloc] init]); - overlay_view.get().frame = flutter_view_.get().bounds; - overlay_view.get().autoresizingMask = - (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); - std::unique_ptr ios_surface = - [overlay_view.get() createSurface:std::move(ios_context)]; - std::unique_ptr surface = ios_surface->CreateGPUSurface(); - overlays_[overlay_id] = std::make_unique( - std::move(overlay_view), std::move(ios_surface), std::move(surface)); - return; - } - - if (overlay_it != overlays_.end()) { - FlutterPlatformViewLayer* overlay = overlay_it->second.get(); - if (gr_context != overlay->gr_context) { - overlay->gr_context = gr_context; - // The overlay already exists, but the GrContext was changed so we need to recreate - // the rendering surface with the new GrContext. - IOSSurface* ios_surface = overlay_it->second->ios_surface.get(); - std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); - overlay_it->second->surface = std::move(surface); - } - return; - } - auto contentsScale = flutter_view_.get().layer.contentsScale; - fml::scoped_nsobject overlay_view( - [[FlutterOverlayView alloc] initWithContentsScale:contentsScale]); - overlay_view.get().frame = flutter_view_.get().bounds; - overlay_view.get().autoresizingMask = - (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); - std::unique_ptr ios_surface = - [overlay_view.get() createSurface:std::move(ios_context)]; - std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); - overlays_[overlay_id] = std::make_unique( - std::move(overlay_view), std::move(ios_surface), std::move(surface)); - overlays_[overlay_id]->gr_context = gr_context; -} - } // namespace flutter // This recognizers delays touch events from being dispatched to the responder chain until it failed diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index d135d7d2ac290..59dc940c4d126 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -6,6 +6,7 @@ #define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERPLATFORMVIEWS_INTERNAL_H_ #include "flutter/flow/embedded_views.h" +#include "flutter/flow/rtree.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" #include "flutter/shell/common/shell.h" #include "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" @@ -68,12 +69,52 @@ struct FlutterPlatformViewLayer { std::unique_ptr ios_surface; std::unique_ptr surface; + // Whether a frame for this layer was submitted. + bool did_submit_last_frame; + // The GrContext that is currently used by the overlay surfaces. // We track this to know when the GrContext for the Flutter app has changed // so we can update the overlay with the new context. GrContext* gr_context; }; +// This class isn't thread safe. +class FlutterPlatformViewLayerPool { + public: + FlutterPlatformViewLayerPool() = default; + ~FlutterPlatformViewLayerPool() = default; + + // Gets a layer from the pool if available, or allocates a new one. + // Finally, it marks the layer as used. That is, it increments `available_layer_index_`. + std::shared_ptr GetLayer(GrContext* gr_context, + std::shared_ptr ios_context); + + // Gets the layers in the pool that aren't currently used. + // This method doesn't mark the layers as unused. + std::vector> GetUnusedLayers(); + + // Marks the layers in the pool as available for reuse. + void RecycleLayers(); + + private: + // The index of the entry in the layers_ vector that determines the beginning of the unused + // layers. For example, consider the following vector: + // _____ + // | 0 | + /// |---| + /// | 1 | <-- available_layer_index_ + /// |---| + /// | 2 | + /// |---| + /// + /// This indicates that entries starting from 1 can be reused meanwhile the entry at position 0 + /// cannot be reused. + size_t available_layer_index_ = 0; + std::vector> layers_; + + FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewLayerPool); +}; + class FlutterPlatformViewsController { public: FlutterPlatformViewsController(); @@ -109,14 +150,37 @@ class FlutterPlatformViewsController { SkCanvas* CompositeEmbeddedView(int view_id); + // The rect of the platform view at index view_id. This rect has been translated into the + // host view coordinate system. Units are device screen pixels. + SkRect GetPlatformViewRect(int view_id); + // Discards all platform views instances and auxiliary resources. void Reset(); - bool SubmitFrame(GrContext* gr_context, std::shared_ptr ios_context); + bool SubmitFrame(GrContext* gr_context, + std::shared_ptr ios_context, + SkCanvas* background_canvas); void OnMethodCall(FlutterMethodCall* call, FlutterResult& result); private: + static const size_t kMaxLayerAllocations = 2; + + using LayersMap = std::map>>; + + // The pool of reusable view layers. The pool allows to recycle layer in each frame. + std::unique_ptr layer_pool_; + + // The platform view's R-tree keyed off the view id, which contains any subsequent + // draw operation until the next platform view or the last leaf node in the layer tree. + // + // The R-trees are deleted by the FlutterPlatformViewsController.reset(). + std::map> platform_view_rtrees_; + + // The platform view's picture recorder keyed off the view id, which contains any subsequent + // operation until the next platform view or the end of the last leaf node in the layer tree. + std::map> picture_recorders_; + fml::scoped_nsobject channel_; fml::scoped_nsobject flutter_view_; fml::scoped_nsobject flutter_view_controller_; @@ -163,19 +227,12 @@ class FlutterPlatformViewsController { std::map gesture_recognizers_blocking_policies; - std::map> picture_recorders_; - void OnCreate(FlutterMethodCall* call, FlutterResult& result); void OnDispose(FlutterMethodCall* call, FlutterResult& result); void OnAcceptGesture(FlutterMethodCall* call, FlutterResult& result); void OnRejectGesture(FlutterMethodCall* call, FlutterResult& result); - - void DetachUnusedLayers(); // Dispose the views in `views_to_dispose_`. void DisposeViews(); - void EnsureOverlayInitialized(int64_t overlay_id, - std::shared_ptr ios_context, - GrContext* gr_context); // This will return true after pre-roll if any of the embedded views // have mutated for last layer tree. @@ -215,6 +272,20 @@ class FlutterPlatformViewsController { void ApplyMutators(const MutatorsStack& mutators_stack, UIView* embedded_view); void CompositeWithParams(int view_id, const EmbeddedViewParams& params); + // Allocates a new FlutterPlatformViewLayer if needed, draws the pixels within the rect from + // the picture on the layer's canvas. + std::shared_ptr GetLayer(GrContext* gr_context, + std::shared_ptr ios_context, + sk_sp picture, + SkRect rect, + int64_t view_id, + int64_t overlay_id); + // Removes overlay views and platform views that aren't needed in the current frame. + void RemoveUnusedLayers(); + // Appends the overlay views and platform view and sets their z index based on the composition + // order. + void BringLayersIntoView(LayersMap layer_map); + FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewsController); }; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index 9310fa1803f11..21db9530fe0bb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -20,7 +20,8 @@ FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default; -FlutterPlatformViewsController::FlutterPlatformViewsController() = default; +FlutterPlatformViewsController::FlutterPlatformViewsController() + : layer_pool_(std::make_unique()){}; FlutterPlatformViewsController::~FlutterPlatformViewsController() = default; diff --git a/shell/platform/darwin/ios/ios_surface.h b/shell/platform/darwin/ios/ios_surface.h index 58233d8a2f656..8cba9285cfb02 100644 --- a/shell/platform/darwin/ios/ios_surface.h +++ b/shell/platform/darwin/ios/ios_surface.h @@ -77,7 +77,10 @@ class IOSSurface : public ExternalViewEmbedder { SkCanvas* CompositeEmbeddedView(int view_id) override; // |ExternalViewEmbedder| - bool SubmitFrame(GrContext* context) override; + bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; + + // |ExternalViewEmbedder| + void FinishFrame() override; public: FML_DISALLOW_COPY_AND_ASSIGN(IOSSurface); diff --git a/shell/platform/darwin/ios/ios_surface.mm b/shell/platform/darwin/ios/ios_surface.mm index fe85c77f12c2d..f51160e94e471 100644 --- a/shell/platform/darwin/ios/ios_surface.mm +++ b/shell/platform/darwin/ios/ios_surface.mm @@ -132,12 +132,18 @@ bool IsIosEmbeddedViewsPreviewEnabled() { } // |ExternalViewEmbedder| -bool IOSSurface::SubmitFrame(GrContext* context) { +bool IOSSurface::SubmitFrame(GrContext* context, SkCanvas* background_canvas) { TRACE_EVENT0("flutter", "IOSSurface::SubmitFrame"); FML_CHECK(platform_views_controller_ != nullptr); - bool submitted = platform_views_controller_->SubmitFrame(std::move(context), ios_context_); - [CATransaction commit]; + bool submitted = + platform_views_controller_->SubmitFrame(std::move(context), ios_context_, background_canvas); return submitted; } +// |ExternalViewEmbedder| +void IOSSurface::FinishFrame() { + TRACE_EVENT0("flutter", "IOSSurface::DidSubmitFrame"); + [CATransaction commit]; +} + } // namespace flutter diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index 5e77073e7ff47..23658888f7caa 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -129,7 +129,8 @@ static FlutterBackingStoreConfig MakeBackingStoreConfig( } // |ExternalViewEmbedder| -bool EmbedderExternalViewEmbedder::SubmitFrame(GrContext* context) { +bool EmbedderExternalViewEmbedder::SubmitFrame(GrContext* context, + SkCanvas* background_canvas) { auto [matched_render_targets, pending_keys] = render_target_cache_.GetExistingTargetsInCache(pending_views_); @@ -265,4 +266,7 @@ bool EmbedderExternalViewEmbedder::SubmitFrame(GrContext* context) { return true; } +// |ExternalViewEmbedder| +void EmbedderExternalViewEmbedder::FinishFrame() {} + } // namespace flutter diff --git a/shell/platform/embedder/embedder_external_view_embedder.h b/shell/platform/embedder/embedder_external_view_embedder.h index 7000d2cde04cd..63c944a88d7ef 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.h +++ b/shell/platform/embedder/embedder_external_view_embedder.h @@ -89,7 +89,10 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { SkCanvas* CompositeEmbeddedView(int view_id) override; // |ExternalViewEmbedder| - bool SubmitFrame(GrContext* context) override; + bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; + + // |ExternalViewEmbedder| + void FinishFrame() override; // |ExternalViewEmbedder| SkCanvas* GetRootCanvas() override; diff --git a/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj b/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj index c24333a3a8a7f..818d902b3e2e9 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj +++ b/testing/scenario_app/ios/Scenarios/Scenarios.xcodeproj/project.pbxproj @@ -43,6 +43,7 @@ 3DEF491A23C3BE6500184216 /* golden_platform_view_transform_iPhone 8_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 3DE09E9123C010BD006C9851 /* golden_platform_view_transform_iPhone 8_simulator.png */; }; 59A97FD8236A49D300B4C066 /* golden_platform_view_multiple_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 59A97FD7236A49D300B4C066 /* golden_platform_view_multiple_iPhone SE_simulator.png */; }; 59A97FDA236B984300B4C066 /* golden_platform_view_multiple_background_foreground_iPhone SE_simulator.png in Resources */ = {isa = PBXBuildFile; fileRef = 59A97FD9236B984300B4C066 /* golden_platform_view_multiple_background_foreground_iPhone SE_simulator.png */; }; + 6402EBD124147BDA00987DCB /* UnobstructedPlatformViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6402EBD024147BDA00987DCB /* UnobstructedPlatformViewTests.m */; }; 6816DB9E231750ED00A51400 /* GoldenPlatformViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6816DB9D231750ED00A51400 /* GoldenPlatformViewTests.m */; }; 6816DBA12317573300A51400 /* GoldenImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 6816DBA02317573300A51400 /* GoldenImage.m */; }; 6816DBA42318358200A51400 /* PlatformViewGoldenTestManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 6816DBA32318358200A51400 /* PlatformViewGoldenTestManager.m */; }; @@ -149,6 +150,7 @@ 3DE09E9223C010BD006C9851 /* golden_platform_view_cliprect_iPhone 8_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_cliprect_iPhone 8_simulator.png"; sourceTree = ""; }; 59A97FD7236A49D300B4C066 /* golden_platform_view_multiple_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_multiple_iPhone SE_simulator.png"; sourceTree = ""; }; 59A97FD9236B984300B4C066 /* golden_platform_view_multiple_background_foreground_iPhone SE_simulator.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "golden_platform_view_multiple_background_foreground_iPhone SE_simulator.png"; sourceTree = ""; }; + 6402EBD024147BDA00987DCB /* UnobstructedPlatformViewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UnobstructedPlatformViewTests.m; sourceTree = ""; }; 6816DB9C231750ED00A51400 /* GoldenPlatformViewTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GoldenPlatformViewTests.h; sourceTree = ""; }; 6816DB9D231750ED00A51400 /* GoldenPlatformViewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoldenPlatformViewTests.m; sourceTree = ""; }; 6816DB9F2317573300A51400 /* GoldenImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GoldenImage.h; sourceTree = ""; }; @@ -245,6 +247,7 @@ 248D76ED22E388380012F0C1 /* ScenariosUITests */ = { isa = PBXGroup; children = ( + 6402EBD024147BDA00987DCB /* UnobstructedPlatformViewTests.m */, 0D14A3FD239743190013D873 /* golden_platform_view_rotate_iPhone SE_simulator.png */, 3DE09E8B23C010BC006C9851 /* golden_platform_view_clippath_iPhone 8_simulator.png */, 3DE09E9223C010BD006C9851 /* golden_platform_view_cliprect_iPhone 8_simulator.png */, @@ -488,6 +491,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 6402EBD124147BDA00987DCB /* UnobstructedPlatformViewTests.m in Sources */, 68A5B63423EB71D300BDBCDB /* PlatformViewGestureRecognizerTests.m in Sources */, 6816DBA12317573300A51400 /* GoldenImage.m in Sources */, 6816DB9E231750ED00A51400 /* GoldenPlatformViewTests.m in Sources */, diff --git a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m index 348889b19b856..9bd732f647039 100644 --- a/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m +++ b/testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m @@ -29,6 +29,13 @@ - (BOOL)application:(UIApplication*)application // the launchArgsMap should match the one in the `PlatformVieGoldenTestManager`. NSDictionary* launchArgsMap = @{ @"--platform-view" : @"platform_view", + @"--platform-view-no-overlay-intersection" : @"platform_view_no_overlay_intersection", + @"--platform-view-two-intersecting-overlays" : @"platform_view_two_intersecting_overlays", + @"--platform-view-partial-intersection" : @"platform_view_partial_intersection", + @"--platform-view-one-overlay-two-intersecting-overlays" : + @"platform_view_one_overlay_two_intersecting_overlays", + @"--platform-view-multiple-without-overlays" : @"platform_view_multiple_without_overlays", + @"--platform-view-max-overlays" : @"platform_view_max_overlays", @"--platform-view-multiple" : @"platform_view_multiple", @"--platform-view-multiple-background-foreground" : @"platform_view_multiple_background_foreground", diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m index d791210f22707..3d583e1d5e824 100644 --- a/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m +++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/PlatformViewGestureRecognizerTests.m @@ -25,7 +25,7 @@ - (void)testRejectPolicyUtilTouchesEnded { [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary* _Nullable bindings) { XCUIElement* element = evaluatedObject; - return [element.identifier isEqualToString:@"platform_view"]; + return [element.identifier hasPrefix:@"platform_view"]; }]; XCUIElement* platformView = [app.textViews elementMatchingPredicate:predicateToFindPlatformView]; if (![platformView waitForExistenceWithTimeout:kSecondsToWaitForPlatformView]) { @@ -56,7 +56,7 @@ - (void)testRejectPolicyEager { [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary* _Nullable bindings) { XCUIElement* element = evaluatedObject; - return [element.identifier isEqualToString:@"platform_view"]; + return [element.identifier hasPrefix:@"platform_view"]; }]; XCUIElement* platformView = [app.textViews elementMatchingPredicate:predicateToFindPlatformView]; if (![platformView waitForExistenceWithTimeout:kSecondsToWaitForPlatformView]) { @@ -91,7 +91,7 @@ - (void)testAccept { [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary* _Nullable bindings) { XCUIElement* element = evaluatedObject; - return [element.identifier isEqualToString:@"platform_view"]; + return [element.identifier hasPrefix:@"platform_view"]; }]; XCUIElement* platformView = [app.textViews elementMatchingPredicate:predicateToFindPlatformView]; if (![platformView waitForExistenceWithTimeout:kSecondsToWaitForPlatformView]) { diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m b/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m new file mode 100644 index 0000000000000..02e7eee35f098 --- /dev/null +++ b/testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m @@ -0,0 +1,254 @@ +// 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 + +@interface UnobstructedPlatformViewTests : XCTestCase + +@end + +@implementation UnobstructedPlatformViewTests + +- (void)setUp { + self.continueAfterFailure = NO; +} + +// A is the layer, which z index is higher than the platform view. +// +--------+ +// | PV | +---+ +// +--------+ | A | +// +---+ +- (void)testNoOverlay { + XCUIApplication* app = [[XCUIApplication alloc] init]; + app.launchArguments = @[ @"--platform-view-no-overlay-intersection" ]; + [app launch]; + + XCUIElement* platform_view = app.textViews[@"platform_view[0]"]; + XCTAssertTrue(platform_view.exists); + XCTAssertEqual(platform_view.frame.origin.x, 25); + XCTAssertEqual(platform_view.frame.origin.y, 25); + XCTAssertEqual(platform_view.frame.size.width, 250); + XCTAssertEqual(platform_view.frame.size.height, 250); + + XCUIElement* overlay = app.otherElements[@"platform_view[0].overlay[0]"]; + XCTAssertFalse(overlay.exists); +} + +// A is the layer above the platform view. +// +-----------------+ +// | PV +---+ | +// | | A | | +// | +---+ | +// +-----------------+ +- (void)testOneOverlay { + XCUIApplication* app = [[XCUIApplication alloc] init]; + app.launchArguments = @[ @"--platform-view" ]; + [app launch]; + + XCUIElement* platform_view = app.textViews[@"platform_view[0]"]; + XCTAssertTrue(platform_view.exists); + XCTAssertEqual(platform_view.frame.origin.x, 25); + XCTAssertEqual(platform_view.frame.origin.y, 25); + XCTAssertEqual(platform_view.frame.size.width, 250); + XCTAssertEqual(platform_view.frame.size.height, 250); + + XCUIElement* overlay = app.otherElements[@"platform_view[0].overlay[0]"]; + XCTAssertTrue(overlay.exists); + XCTAssertEqual(overlay.frame.origin.x, 150); + XCTAssertEqual(overlay.frame.origin.y, 150); + XCTAssertEqual(overlay.frame.size.width, 50); + XCTAssertEqual(overlay.frame.size.height, 50); +} + +// A is the layer above the platform view. +// +-----------------+ +// | PV +---+ | +// +-----------| A |-+ +// +---+ +- (void)testOneOverlayPartialIntersection { + XCUIApplication* app = [[XCUIApplication alloc] init]; + app.launchArguments = @[ @"--platform-view-partial-intersection" ]; + [app launch]; + + XCUIElement* platform_view = app.textViews[@"platform_view[0]"]; + XCTAssertTrue(platform_view.exists); + XCTAssertEqual(platform_view.frame.origin.x, 25); + XCTAssertEqual(platform_view.frame.origin.y, 25); + XCTAssertEqual(platform_view.frame.size.width, 250); + XCTAssertEqual(platform_view.frame.size.height, 250); + + XCUIElement* overlay = app.otherElements[@"platform_view[0].overlay[0]"]; + XCTAssertTrue(overlay.exists); + XCTAssertEqual(overlay.frame.origin.x, 200); + XCTAssertEqual(overlay.frame.origin.y, 250); + XCTAssertEqual(overlay.frame.size.width, 50); + // Half the height of the overlay. + XCTAssertEqual(overlay.frame.size.height, 25); +} + +// A and B are the layers above the platform view. +// +--------------------+ +// | PV +------------+ | +// | | B +-----+ | | +// | +---| A |-+ | +// +----------| |---+ +// +-----+ +- (void)testTwoIntersectingOverlays { + XCUIApplication* app = [[XCUIApplication alloc] init]; + app.launchArguments = @[ @"--platform-view-two-intersecting-overlays" ]; + [app launch]; + + XCUIElement* platform_view = app.textViews[@"platform_view[0]"]; + XCTAssertTrue(platform_view.exists); + XCTAssertEqual(platform_view.frame.origin.x, 25); + XCTAssertEqual(platform_view.frame.origin.y, 25); + XCTAssertEqual(platform_view.frame.size.width, 250); + XCTAssertEqual(platform_view.frame.size.height, 250); + + XCUIElement* overlay = app.otherElements[@"platform_view[0].overlay[0]"]; + XCTAssertTrue(overlay.exists); + XCTAssertEqual(overlay.frame.origin.x, 150); + XCTAssertEqual(overlay.frame.origin.y, 150); + XCTAssertEqual(overlay.frame.size.width, 75); + XCTAssertEqual(overlay.frame.size.height, 75); + + XCTAssertFalse(app.otherElements[@"platform_view[0].overlay[1]"].exists); +} + +// A, B, and C are the layers above the platform view. +// +-------------------------+ +// | PV +-----------+ | +// | +---+ | B +-----+ | | +// | | C | +---| A |-+ | +// | +---+ +-----+ | +// +-------------------------+ +- (void)testOneOverlayAndTwoIntersectingOverlays { + XCUIApplication* app = [[XCUIApplication alloc] init]; + app.launchArguments = @[ @"--platform-view-one-overlay-two-intersecting-overlays" ]; + [app launch]; + + XCUIElement* platform_view = app.textViews[@"platform_view[0]"]; + XCTAssertTrue(platform_view.exists); + XCTAssertEqual(platform_view.frame.origin.x, 25); + XCTAssertEqual(platform_view.frame.origin.y, 25); + XCTAssertEqual(platform_view.frame.size.width, 250); + XCTAssertEqual(platform_view.frame.size.height, 250); + + XCUIElement* overlay1 = app.otherElements[@"platform_view[0].overlay[0]"]; + XCTAssertTrue(overlay1.exists); + XCTAssertEqual(overlay1.frame.origin.x, 150); + XCTAssertEqual(overlay1.frame.origin.y, 150); + XCTAssertEqual(overlay1.frame.size.width, 75); + XCTAssertEqual(overlay1.frame.size.height, 75); + + XCUIElement* overlay2 = app.otherElements[@"platform_view[0].overlay[1]"]; + XCTAssertTrue(overlay2.exists); + XCTAssertEqual(overlay2.frame.origin.x, 75); + XCTAssertEqual(overlay2.frame.origin.y, 225); + XCTAssertEqual(overlay2.frame.size.width, 50); + XCTAssertEqual(overlay2.frame.size.height, 50); +} + +// A is the layer, which z index is higher than the platform view. +// +--------+ +// | PV | +---+ +// +--------+ | A | +// +--------+ +---+ +// | PV | +// +--------+ +- (void)testMultiplePlatformViewsWithoutOverlays { + XCUIApplication* app = [[XCUIApplication alloc] init]; + app.launchArguments = @[ @"--platform-view-multiple-without-overlays" ]; + [app launch]; + + XCUIElement* platform_view1 = app.textViews[@"platform_view[0]"]; + XCTAssertTrue(platform_view1.exists); + XCTAssertEqual(platform_view1.frame.origin.x, 25); + XCTAssertEqual(platform_view1.frame.origin.y, 325); + XCTAssertEqual(platform_view1.frame.size.width, 250); + XCTAssertEqual(platform_view1.frame.size.height, 250); + + XCUIElement* platform_view2 = app.textViews[@"platform_view[1]"]; + XCTAssertTrue(platform_view2.exists); + XCTAssertEqual(platform_view2.frame.origin.x, 25); + XCTAssertEqual(platform_view2.frame.origin.y, 25); + XCTAssertEqual(platform_view2.frame.size.width, 250); + XCTAssertEqual(platform_view2.frame.size.height, 250); + + XCTAssertFalse(app.otherElements[@"platform_view[0].overlay[0]"].exists); + XCTAssertFalse(app.otherElements[@"platform_view[1].overlay[0]"].exists); +} + +// A is the layer above both platform view. +// +------------+ +// | PV +----+ | +// +-----| A |-+ +// +-----| |-+ +// | PV +----+ | +// +------------+ +- (void)testMultiplePlatformViewsWithOverlays { + XCUIApplication* app = [[XCUIApplication alloc] init]; + app.launchArguments = @[ @"--platform-view-multiple-background-foreground" ]; + [app launch]; + + XCUIElement* platform_view1 = app.textViews[@"platform_view[8]"]; + XCTAssertTrue(platform_view1.exists); + XCTAssertEqual(platform_view1.frame.origin.x, 25); + XCTAssertEqual(platform_view1.frame.origin.y, 325); + XCTAssertEqual(platform_view1.frame.size.width, 250); + XCTAssertEqual(platform_view1.frame.size.height, 250); + + XCUIElement* platform_view2 = app.textViews[@"platform_view[9]"]; + XCTAssertTrue(platform_view2.exists); + XCTAssertEqual(platform_view2.frame.origin.x, 25); + XCTAssertEqual(platform_view2.frame.origin.y, 25); + XCTAssertEqual(platform_view2.frame.size.width, 250); + XCTAssertEqual(platform_view2.frame.size.height, 250); + + XCUIElement* overlay1 = app.otherElements[@"platform_view[8].overlay[0]"]; + XCTAssertTrue(overlay1.exists); + XCTAssertEqual(overlay1.frame.origin.x, 25); + XCTAssertEqual(overlay1.frame.origin.y, 325); + XCTAssertEqual(overlay1.frame.size.width, 225); + XCTAssertEqual(overlay1.frame.size.height, 175); + + XCUIElement* overlay2 = app.otherElements[@"platform_view[9].overlay[0]"]; + XCTAssertTrue(overlay2.exists); + XCTAssertEqual(overlay2.frame.origin.x, 25); + XCTAssertEqual(overlay2.frame.origin.y, 25); + XCTAssertEqual(overlay2.frame.size.width, 225); + XCTAssertEqual(overlay2.frame.size.height, 250); +} + +// More then two overlays are merged into a single layer. +// +---------------------+ +// | +---+ +---+ +---+ | +// | | A | | B | | C | | +// | +---+ +---+ +---+ | +// | +-------+ | +// +-| D |-----------+ +// +-------+ +- (void)testPlatformViewsMaxOverlays { + XCUIApplication* app = [[XCUIApplication alloc] init]; + app.launchArguments = @[ @"--platform-view-max-overlays" ]; + [app launch]; + + XCUIElement* platform_view = app.textViews[@"platform_view[0]"]; + XCTAssertTrue(platform_view.exists); + XCTAssertEqual(platform_view.frame.origin.x, 25); + XCTAssertEqual(platform_view.frame.origin.y, 25); + XCTAssertEqual(platform_view.frame.size.width, 250); + XCTAssertEqual(platform_view.frame.size.height, 250); + + XCUIElement* overlay = app.otherElements[@"platform_view[0].overlay[0]"]; + XCTAssertTrue(overlay.exists); + XCTAssertEqual(overlay.frame.origin.x, 75); + XCTAssertEqual(overlay.frame.origin.y, 85); + XCTAssertEqual(overlay.frame.size.width, 150); + XCTAssertEqual(overlay.frame.size.height, 190); + + XCTAssertFalse(app.otherElements[@"platform_view[0].overlay[1]"].exists); +} + +@end diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_clippath_iPhone 8_simulator.png b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_clippath_iPhone 8_simulator.png index a193faeb040223e56343903c777f9181edb5bcc0..9ec19ab474f03b6cc7786cb038ee3b3e6d0c51a4 100644 GIT binary patch literal 20295 zcmeHuX*iVa8@HyJnz3Yzr3E$ieMzzvCQH_kA+oDbk8B}3O<5*oDSL~pkUjg(q>u%s=030UI)CT+JD2M|+}G1lqoY1XO+`gTr=hN* zPelcrrJ_PeAz|PX)smMN!DXMTzM2wMUK`sa_>ZTJv4-u13si#O9!Z7RhoRaJJp%qx z?K?(=pxjeYY3^hF_g;S=|DR{Tnjtr+;D4Sm0axfxB=`Z+f3C1(*xx5$v&sAa-Xo-- zN15f^xWEPFtbW;*ii(8?`q`(U&pi)5WV@lFYXGj$#-Yno@DKm)6}p!srTj#Pg6l&K zm2(E3`=@BVmW3Qoo zEAE=V()MiS$ENStBiA{d&TI4$Uu9`u;g8Qjx^L@!$Qxhsl75%>84gx2Zkt9p zRMxIl)orxYi*C=;2XrpKI37oy58lCFSM-=&@xF=oTnrW3#;5$UiQAH?R`gyXGY?cu zrq>A6T!x{2ge|-jD(&p3OzZK`!Cx68%Q=PKgb{}sqw2|M$ zql9LA7VneSbIERwo4HO~Pv=4E+IARkA+J5RTrU5ixIMDd-Gj|3_n70e{kVRPqm%YbIrEf=)%WS0xw@4K>=zfOA>tKgx*wpiLK}#s8`F)Z8tF7a!t7z7<4>CBITKz z!{_m5FWzUiD$~o$dd}A|mHK|YyyH4I(Bw_OI$M<_R7_9vIen|xbfYUnVx`aZZ%(5}4Y;&(4*>&L_jytIj{Ezty%+*$Wvj~__PCB!-R41mQZ@4VB z(*JF1Rc{KP5FV~<_snHf?8gPCb<3b_3%5`07E%0D?E@JS){IU*z3tSj^fc0a9lab$ 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 literal 30022 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ΠAr29j`n?8;-QZE z4jSFDu$|OBK+1@&kY%gDyh4f+et*UW!xwP*gQNm<3K_Yi(Xv2`G0u{G@CDL_`Za|R z0&ba{A^qz!h73(?gBQ zaOaF%@BB*^QHtUx^a&4#swelbeQ@;feaTS45;*zevVw|@B_*Du_fg$vTTApLhT z;(~N8NauodE=cEg)wx}DZdaZ22H-9H%#@2te_y_G)xmx1@#nP+_vs7wCD1Qx;69A{ z-@X&;4$L>JfxjakBUrV-?pPC7bYAj`F8b4z{e%L^L5)Ap&Ej}pv9OANp0FJk6lCu2 z`qwMCQ-p#C$T2zwwupZl2?j`8#a1jqb& z0=E@=#V?uu=M`PWHYtj}0{Y_#&Wfu+&iGHHKM&yc@?V(~vi|c9FG*CCezoXdPx+n% zIcrwd|B-1q@2k?|%s;R2x|fU6rSJXgF(6k7@Me>%1g;WLALUvCG}*aI;3|QWvbdJO zwFIsuaDJV@wFIsua4msbJ8%mq_&GOM30x(hZ4Is^K;6hy0#^x~-^Fq*;eTWaOX`;t XeZBM9{0{PeD?M`X*ny1wR>A)dG2_6W diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_transform_iPhone 8_simulator.png b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_transform_iPhone 8_simulator.png index 793082f8f0f7c4edaa29fd14c5ee5a782d1e69b1..a2e9351d385e0b2f7800ec71448a05e2736e196e 100644 GIT binary patch literal 22360 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 mf=d=$vf%&Cf@1cqGyR|38zWqd;cH7-*4ss literal 33727 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%0D$f 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 HeDr?+Nh376 diff --git a/testing/scenario_app/lib/main.dart b/testing/scenario_app/lib/main.dart index 494d6585aa7fe..0ab3f7c5352f1 100644 --- a/testing/scenario_app/lib/main.dart +++ b/testing/scenario_app/lib/main.dart @@ -19,6 +19,12 @@ import 'src/touches_scenario.dart'; Map _scenarios = { 'animated_color_square': AnimatedColorSquareScenario(window), 'platform_view': PlatformViewScenario(window, 'Hello from Scenarios (Platform View)', id: 0), + 'platform_view_no_overlay_intersection': PlatformViewNoOverlayIntersectionScenario(window, 'Hello from Scenarios (Platform View)', id: 0), + 'platform_view_partial_intersection': PlatformViewPartialIntersectionScenario(window, 'Hello from Scenarios (Platform View)', id: 0), + 'platform_view_two_intersecting_overlays': PlatformViewTwoIntersectingOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: 0), + 'platform_view_one_overlay_two_intersecting_overlays': PlatformViewOneOverlayTwoIntersectingOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: 0), + 'platform_view_multiple_without_overlays': MultiPlatformViewWithoutOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: 0), + 'platform_view_max_overlays': PlatformViewMaxOverlaysScenario(window, 'Hello from Scenarios (Platform View)', id: 0), 'platform_view_cliprect': PlatformViewClipRectScenario(window, 'PlatformViewClipRect', id: 1), 'platform_view_cliprrect': PlatformViewClipRRectScenario(window, 'PlatformViewClipRRect', id: 2), 'platform_view_clippath': PlatformViewClipPathScenario(window, 'PlatformViewClipPath', id: 3), diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart index 0dde98fd9eebb..fcea41a65f7d5 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -48,6 +48,224 @@ class PlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin } } +/// A simple platform view with overlay that doesn't intersect with the platform view. +class PlatformViewNoOverlayIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin { + /// Creates the PlatformView scenario. + /// + /// The [window] parameter must not be null. + PlatformViewNoOverlayIntersectionScenario(Window window, String text, {int id = 0}) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); + } + + @override + void onBeginFrame(Duration duration) { + final SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0, 0); + + finishBuilderByAddingPlatformViewAndPicture( + builder, + 0, + overlayOffset: const Offset(150, 350), + ); + } +} + +/// A simple platform view with an overlay that partially intersects with the platform view. +class PlatformViewPartialIntersectionScenario extends Scenario with _BasePlatformViewScenarioMixin { + /// Creates the PlatformView scenario. + /// + /// The [window] parameter must not be null. + PlatformViewPartialIntersectionScenario(Window window, String text, {int id = 0}) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); + } + + @override + void onBeginFrame(Duration duration) { + final SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0, 0); + + finishBuilderByAddingPlatformViewAndPicture( + builder, + 0, + overlayOffset: const Offset(150, 250), + ); + } +} + +/// A simple platform view with two overlays that intersect with each other and the platform view. +class PlatformViewTwoIntersectingOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { + /// Creates the PlatformView scenario. + /// + /// The [window] parameter must not be null. + PlatformViewTwoIntersectingOverlaysScenario(Window window, String text, {int id = 0}) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); + } + + @override + void onBeginFrame(Duration duration) { + final SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0, 0); + + _addPlatformViewtoScene(builder, 0, 500, 500); + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawCircle( + const Offset(50, 50), + 50, + Paint()..color = const Color(0xFFABCDEF), + ); + canvas.drawCircle( + const Offset(100, 100), + 50, + Paint()..color = const Color(0xFFABCDEF), + ); + final Picture picture = recorder.endRecording(); + builder.addPicture(const Offset(300, 300), picture); + final Scene scene = builder.build(); + window.render(scene); + scene.dispose(); + } +} + +/// A simple platform view with one overlay and two overlays that intersect with each other and the platform view. +class PlatformViewOneOverlayTwoIntersectingOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { + /// Creates the PlatformView scenario. + /// + /// The [window] parameter must not be null. + PlatformViewOneOverlayTwoIntersectingOverlaysScenario(Window window, String text, {int id = 0}) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); + } + + @override + void onBeginFrame(Duration duration) { + final SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0, 0); + + _addPlatformViewtoScene(builder, 0, 500, 500); + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawCircle( + const Offset(50, 50), + 50, + Paint()..color = const Color(0xFFABCDEF), + ); + canvas.drawCircle( + const Offset(100, 100), + 50, + Paint()..color = const Color(0xFFABCDEF), + ); + canvas.drawCircle( + const Offset(-100, 200), + 50, + Paint()..color = const Color(0xFFABCDEF), + ); + final Picture picture = recorder.endRecording(); + builder.addPicture(const Offset(300, 300), picture); + final Scene scene = builder.build(); + window.render(scene); + scene.dispose(); + } +} + +/// Two platform views without an overlay intersecting either platform view. +class MultiPlatformViewWithoutOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { + /// Creates the PlatformView scenario. + /// + /// The [window] parameter must not be null. + MultiPlatformViewWithoutOverlaysScenario(Window window, String text, {int id = 0}) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); + } + + @override + void onBeginFrame(Duration duration) { + final SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0, 0); + + builder.pushOffset(0, 600); + _addPlatformViewtoScene(builder, 0, 500, 500); + builder.pop(); + + _addPlatformViewtoScene(builder, 1, 500, 500); + + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawRect( + const Rect.fromLTRB(0, 0, 100, 1000), + Paint()..color = const Color(0xFFFF0000), + ); + final Picture picture = recorder.endRecording(); + builder.addPicture(const Offset(580, 0), picture); + + builder.pop(); + final Scene scene = builder.build(); + window.render(scene); + scene.dispose(); + } +} + +/// A simple platform view with too many overlays result in a single native view. +class PlatformViewMaxOverlaysScenario extends Scenario with _BasePlatformViewScenarioMixin { + /// Creates the PlatformView scenario. + /// + /// The [window] parameter must not be null. + PlatformViewMaxOverlaysScenario(Window window, String text, {int id = 0}) + : assert(window != null), + super(window) { + createPlatformView(window, text, id); + } + + @override + void onBeginFrame(Duration duration) { + final SceneBuilder builder = SceneBuilder(); + + builder.pushOffset(0, 0); + + _addPlatformViewtoScene(builder, 0, 500, 500); + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawCircle( + const Offset(50, 50), + 50, + Paint()..color = const Color(0xFFABCDEF), + ); + canvas.drawCircle( + const Offset(100, 100), + 50, + Paint()..color = const Color(0xFFABCDEF), + ); + canvas.drawCircle( + const Offset(-100, 200), + 50, + Paint()..color = const Color(0xFFABCDEF), + ); + canvas.drawCircle( + const Offset(-100, -80), + 50, + Paint()..color = const Color(0xFFABCDEF), + ); + final Picture picture = recorder.endRecording(); + builder.addPicture(const Offset(300, 300), picture); + final Scene scene = builder.build(); + window.render(scene); + scene.dispose(); + } +} + /// Builds a scene with 2 platform views. class MultiPlatformViewScenario extends Scenario with _BasePlatformViewScenarioMixin { /// Creates the PlatformView scenario. @@ -424,12 +642,17 @@ mixin _BasePlatformViewScenarioMixin on Scenario { } // Add a platform view and a picture to the scene, then finish the `sceneBuilder`. - void finishBuilderByAddingPlatformViewAndPicture(SceneBuilder sceneBuilder, int viewId) { + void finishBuilderByAddingPlatformViewAndPicture( + SceneBuilder sceneBuilder, + int viewId, { + Offset overlayOffset, + }) { + overlayOffset ??= const Offset(50, 50); _addPlatformViewtoScene(sceneBuilder, viewId, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); canvas.drawCircle( - const Offset(50, 50), + overlayOffset, 50, Paint()..color = const Color(0xFFABCDEF), ); From 20d0b168cb47cdbc5a4ae6f58965181932facdc0 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 25 Mar 2020 13:48:43 -0700 Subject: [PATCH 2/6] Don't change the overlay surface size --- .../framework/Source/FlutterOverlayView.mm | 1 + .../framework/Source/FlutterPlatformViews.mm | 79 +++++++++++------- .../Source/FlutterPlatformViews_Internal.h | 3 +- .../Source/FlutterPlatformViews_Internal.mm | 9 +- ...form_view_transform_iPhone 8_simulator.png | Bin 22360 -> 21838 bytes 5 files changed, 59 insertions(+), 33 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm index 11c0d60618886..4127061f3e7de 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.mm @@ -39,6 +39,7 @@ - (instancetype)init { if (self) { self.layer.opaque = NO; self.userInteractionEnabled = NO; + self.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); } return self; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 32c91c91e4077..9c9357c99d8e1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -26,31 +26,50 @@ std::shared_ptr ios_context) { if (available_layer_index_ >= layers_.size()) { std::shared_ptr layer; + fml::scoped_nsobject overlay_view; + fml::scoped_nsobject overlay_view_wrapper; if (!gr_context) { - fml::scoped_nsobject overlay_view([[FlutterOverlayView alloc] init]); - overlay_view.get().autoresizingMask = - (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + overlay_view.reset([[FlutterOverlayView alloc] init]); + overlay_view_wrapper.reset([[FlutterOverlayView alloc] init]); + std::unique_ptr ios_surface = [overlay_view.get() createSurface:std::move(ios_context)]; std::unique_ptr surface = ios_surface->CreateGPUSurface(); layer = std::make_shared( - std::move(overlay_view), std::move(ios_surface), std::move(surface)); + std::move(overlay_view), std::move(overlay_view_wrapper), std::move(ios_surface), + std::move(surface)); } else { CGFloat screenScale = [UIScreen mainScreen].scale; - fml::scoped_nsobject overlay_view( - [[FlutterOverlayView alloc] initWithContentsScale:screenScale]); - overlay_view.get().autoresizingMask = - (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + overlay_view.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale]); + overlay_view_wrapper.reset([[FlutterOverlayView alloc] initWithContentsScale:screenScale]); + std::unique_ptr ios_surface = [overlay_view.get() createSurface:std::move(ios_context)]; std::unique_ptr surface = ios_surface->CreateGPUSurface(gr_context); layer = std::make_shared( - std::move(overlay_view), std::move(ios_surface), std::move(surface)); + std::move(overlay_view), std::move(overlay_view_wrapper), std::move(ios_surface), + std::move(surface)); layer->gr_context = gr_context; } + // The overlay view wrapper masks the overlay view. + // This is required to keep the backing surface size unchanged between frames. + // + // Otherwise, changing the size of the overlay would require a new surface, + // which can be very expensive. + // + // This is the case of an animation in which the overlay size is changing in every frame. + // + // +------------------------+ + // | overlay_view | + // | +--------------+ | +--------------+ + // | | wrapper | | == mask => | overlay_view | + // | +--------------+ | +--------------+ + // +------------------------+ + overlay_view_wrapper.get().clipsToBounds = YES; + [overlay_view_wrapper.get() addSubview:overlay_view]; layers_.push_back(layer); } auto layer = layers_[available_layer_index_]; @@ -424,7 +443,6 @@ [sub_view removeFromSuperview]; } views_.clear(); - overlays_.clear(); composition_order_.clear(); active_composition_order_.clear(); picture_recorders_.clear(); @@ -500,6 +518,10 @@ // Get the intersection rect between the current rect // and the platform view rect. joined_rect.intersect(platform_view_rect); + // Subpixels in the platform may not align with the canvas subpixels. + // To workaround it, round the rect values. + joined_rect.setLTRB(std::round(joined_rect.left()), std::round(joined_rect.top()), + std::round(joined_rect.right()), std::round(joined_rect.bottom())); // Clip the background canvas, so it doesn't contain any of the pixels drawn // on the overlay layer. background_canvas->clipRect(joined_rect, SkClipOp::kDifference); @@ -545,10 +567,10 @@ platform_view_root.layer.zPosition = zIndex++; } for (const std::shared_ptr& layer : layers) { - if ([layer->overlay_view superview] != flutter_view) { - [flutter_view addSubview:layer->overlay_view]; + if ([layer->overlay_view_wrapper superview] != flutter_view) { + [flutter_view addSubview:layer->overlay_view_wrapper]; } else { - layer->overlay_view.get().layer.zPosition = zIndex++; + layer->overlay_view_wrapper.get().layer.zPosition = zIndex++; } } active_composition_order_.push_back(platform_view_id); @@ -563,19 +585,23 @@ int64_t view_id, int64_t overlay_id) { auto layer = layer_pool_->GetLayer(gr_context, ios_context); + + auto overlay_view_wrapper = layer->overlay_view_wrapper.get(); auto screenScale = [UIScreen mainScreen].scale; - // Set the size of the overlay UIView. - layer->overlay_view.get().frame = CGRectMake(rect.x() / screenScale, // - rect.y() / screenScale, // - rect.width() / screenScale, // - rect.height() / screenScale // - ); - // Set a unique view identifier, so the overlay can be identified in unit tests. - layer->overlay_view.get().accessibilityIdentifier = + // Set the size of the overlay view wrapper. + // This wrapper view masks the overlay view. + overlay_view_wrapper.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale, + rect.width() / screenScale, rect.height() / screenScale); + // Set a unique view identifier, so the overlay wrapper can be identified in unit tests. + overlay_view_wrapper.accessibilityIdentifier = [NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id]; - std::unique_ptr frame = - layer->surface->AcquireFrame(SkISize::Make(rect.width(), rect.height())); + auto overlay_view = layer->overlay_view.get(); + // Set the size of the overlay view. + // This size is equal to the the device screen size. + overlay_view.frame = flutter_view_.get().bounds; + + std::unique_ptr frame = layer->surface->AcquireFrame(frame_size_); // If frame is null, AcquireFrame already printed out an error message. if (!frame) { return layer; @@ -594,11 +620,10 @@ void FlutterPlatformViewsController::RemoveUnusedLayers() { auto layers = layer_pool_->GetUnusedLayers(); for (const std::shared_ptr& layer : layers) { - [layer->overlay_view removeFromSuperview]; + [layer->overlay_view_wrapper removeFromSuperview]; } std::unordered_set composition_order_set; - for (int64_t view_id : composition_order_) { composition_order_set.insert(view_id); } @@ -622,10 +647,6 @@ views_.erase(viewId); touch_interceptors_.erase(viewId); root_views_.erase(viewId); - if (overlays_.find(viewId) != overlays_.end()) { - [overlays_[viewId]->overlay_view.get() removeFromSuperview]; - } - overlays_.erase(viewId); current_composition_params_.erase(viewId); clip_count_.erase(viewId); views_to_recomposite_.erase(viewId); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 59dc940c4d126..c5ebd9a479b1f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -60,12 +60,14 @@ class IOSSurface; struct FlutterPlatformViewLayer { FlutterPlatformViewLayer(fml::scoped_nsobject overlay_view, + fml::scoped_nsobject overlay_view_wrapper, std::unique_ptr ios_surface, std::unique_ptr surface); ~FlutterPlatformViewLayer(); fml::scoped_nsobject overlay_view; + fml::scoped_nsobject overlay_view_wrapper; std::unique_ptr ios_surface; std::unique_ptr surface; @@ -198,7 +200,6 @@ class FlutterPlatformViewsController { // Mapping a platform view ID to the count of the clipping operations that were applied to the // platform view last time it was composited. std::map clip_count_; - std::map> overlays_; SkISize frame_size_; // This is the number of frames the task runners will stay diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index 21db9530fe0bb..551535a2c7faf 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -11,10 +11,13 @@ namespace flutter { -FlutterPlatformViewLayer::FlutterPlatformViewLayer(fml::scoped_nsobject overlay_view, - std::unique_ptr ios_surface, - std::unique_ptr surface) +FlutterPlatformViewLayer::FlutterPlatformViewLayer( + fml::scoped_nsobject overlay_view, + fml::scoped_nsobject overlay_view_wrapper, + std::unique_ptr ios_surface, + std::unique_ptr surface) : overlay_view(std::move(overlay_view)), + overlay_view_wrapper(std::move(overlay_view_wrapper)), ios_surface(std::move(ios_surface)), surface(std::move(surface)){}; diff --git a/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_transform_iPhone 8_simulator.png b/testing/scenario_app/ios/Scenarios/ScenariosUITests/golden_platform_view_transform_iPhone 8_simulator.png index a2e9351d385e0b2f7800ec71448a05e2736e196e..0db030ed20983c5e65e59f07d139ba527cefb8ef 100644 GIT binary patch literal 21838 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 literal 22360 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 mf=d=$vf%&Cf@1cqGyR|38zWqd;cH7-*4ss From 72e26d61ea1897469ea3ba75990ef13e38b698d8 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 27 Mar 2020 13:01:15 -0700 Subject: [PATCH 3/6] Make rect slighly larger --- .../darwin/ios/framework/Source/FlutterPlatformViews.mm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 9c9357c99d8e1..b998caa89f3c1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -519,8 +519,9 @@ // and the platform view rect. joined_rect.intersect(platform_view_rect); // Subpixels in the platform may not align with the canvas subpixels. - // To workaround it, round the rect values. - joined_rect.setLTRB(std::round(joined_rect.left()), std::round(joined_rect.top()), + // To workaround it, round the floating point bounds and make the rect slighly larger. + // For example, {0.3, 0.5, 3.5, 4.7} becomes {0, 0, 4, 5}. + joined_rect.setLTRB(std::nearbyint(joined_rect.left()), std::nearbyint(joined_rect.top()), std::round(joined_rect.right()), std::round(joined_rect.bottom())); // Clip the background canvas, so it doesn't contain any of the pixels drawn // on the overlay layer. From 0403cf3f4b167abd1374229ad96ef24cb0c520df Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 27 Mar 2020 13:07:49 -0700 Subject: [PATCH 4/6] Ceil right and bottom bounds --- .../darwin/ios/framework/Source/FlutterPlatformViews.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index b998caa89f3c1..1fc27438e9881 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -520,9 +520,9 @@ joined_rect.intersect(platform_view_rect); // Subpixels in the platform may not align with the canvas subpixels. // To workaround it, round the floating point bounds and make the rect slighly larger. - // For example, {0.3, 0.5, 3.5, 4.7} becomes {0, 0, 4, 5}. + // For example, {0.3, 0.5, 3.1, 4.7} becomes {0, 0, 4, 5}. joined_rect.setLTRB(std::nearbyint(joined_rect.left()), std::nearbyint(joined_rect.top()), - std::round(joined_rect.right()), std::round(joined_rect.bottom())); + std::ceil(joined_rect.right()), std::ceil(joined_rect.bottom())); // Clip the background canvas, so it doesn't contain any of the pixels drawn // on the overlay layer. background_canvas->clipRect(joined_rect, SkClipOp::kDifference); From bb135c411f71a39eb41a4376afd340cd9525a851 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 27 Mar 2020 14:45:34 -0700 Subject: [PATCH 5/6] nearbyint -> floor --- .../darwin/ios/framework/Source/FlutterPlatformViews.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 1fc27438e9881..bda39017959f8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -521,7 +521,7 @@ // Subpixels in the platform may not align with the canvas subpixels. // To workaround it, round the floating point bounds and make the rect slighly larger. // For example, {0.3, 0.5, 3.1, 4.7} becomes {0, 0, 4, 5}. - joined_rect.setLTRB(std::nearbyint(joined_rect.left()), std::nearbyint(joined_rect.top()), + joined_rect.setLTRB(std::floor(joined_rect.left()), std::floor(joined_rect.top()), std::ceil(joined_rect.right()), std::ceil(joined_rect.bottom())); // Clip the background canvas, so it doesn't contain any of the pixels drawn // on the overlay layer. From 1e6e4d6391959909361026450782fe211463add6 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 27 Mar 2020 15:12:40 -0700 Subject: [PATCH 6/6] Remove auto --- .../framework/Source/FlutterPlatformViews.mm | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index bda39017959f8..c2cbef18cc167 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -72,7 +72,7 @@ [overlay_view_wrapper.get() addSubview:overlay_view]; layers_.push_back(layer); } - auto layer = layers_[available_layer_index_]; + std::shared_ptr layer = layers_[available_layer_index_]; if (gr_context != layer->gr_context) { layer->gr_context = gr_context; // The overlay already exists, but the GrContext was changed so we need to recreate @@ -527,12 +527,12 @@ // on the overlay layer. background_canvas->clipRect(joined_rect, SkClipOp::kDifference); // Get a new host layer. - auto layer = GetLayer(gr_context, // - ios_context, // - picture, // - joined_rect, // - current_platform_view_id, // - overlay_id // + std::shared_ptr layer = GetLayer(gr_context, // + ios_context, // + picture, // + joined_rect, // + current_platform_view_id, // + overlay_id // ); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); @@ -585,9 +585,9 @@ SkRect rect, int64_t view_id, int64_t overlay_id) { - auto layer = layer_pool_->GetLayer(gr_context, ios_context); + std::shared_ptr layer = layer_pool_->GetLayer(gr_context, ios_context); - auto overlay_view_wrapper = layer->overlay_view_wrapper.get(); + UIView* overlay_view_wrapper = layer->overlay_view_wrapper.get(); auto screenScale = [UIScreen mainScreen].scale; // Set the size of the overlay view wrapper. // This wrapper view masks the overlay view. @@ -597,7 +597,7 @@ overlay_view_wrapper.accessibilityIdentifier = [NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id]; - auto overlay_view = layer->overlay_view.get(); + UIView* overlay_view = layer->overlay_view.get(); // Set the size of the overlay view. // This size is equal to the the device screen size. overlay_view.frame = flutter_view_.get().bounds; @@ -607,7 +607,7 @@ if (!frame) { return layer; } - auto overlay_canvas = frame->SkiaCanvas(); + SkCanvas* overlay_canvas = frame->SkiaCanvas(); overlay_canvas->clear(SK_ColorTRANSPARENT); // Offset the picture since its absolute position on the scene is determined // by the position of the overlay view. @@ -619,7 +619,7 @@ } void FlutterPlatformViewsController::RemoveUnusedLayers() { - auto layers = layer_pool_->GetUnusedLayers(); + std::vector> layers = layer_pool_->GetUnusedLayers(); for (const std::shared_ptr& layer : layers) { [layer->overlay_view_wrapper removeFromSuperview]; }