From ad666943d6b40ca68116316ad5513b900f4b4b8d Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 12 Feb 2020 14:52:50 -0800 Subject: [PATCH 01/44] Implement unobstructed Platform Views --- flow/layers/child_scene_layer.cc | 5 +- flow/layers/image_filter_layer.cc | 19 +- flow/layers/layer.h | 3 + flow/layers/layer_tree.cc | 4 +- flow/layers/opacity_layer.cc | 9 +- flow/layers/performance_overlay_layer.cc | 18 +- .../performance_overlay_layer_unittests.cc | 5 +- flow/layers/physical_shape_layer.cc | 16 +- flow/layers/picture_layer.cc | 32 +- flow/layers/platform_view_layer_unittests.cc | 2 +- flow/layers/shader_mask_layer.cc | 10 +- flow/layers/texture_layer.cc | 6 +- flow/raster_cache.cc | 1 + flow/scene_update_context.cc | 1 + flow/testing/layer_test.h | 1 + flow/testing/mock_layer.cc | 2 +- lib/ui/BUILD.gn | 3 + lib/ui/painting/flutter_rtree.cc | 236 ++++++++++++++ lib/ui/painting/flutter_rtree.h | 84 +++++ lib/ui/painting/flutter_rtree_unittests.cc | 59 ++++ shell/common/persistent_cache.cc | 1 + .../framework/Source/FlutterPlatformViews.mm | 296 +++++++++++------- .../Source/FlutterPlatformViews_Internal.h | 55 +++- .../Source/FlutterPlatformViews_Internal.mm | 7 +- 24 files changed, 725 insertions(+), 150 deletions(-) create mode 100644 lib/ui/painting/flutter_rtree.cc create mode 100644 lib/ui/painting/flutter_rtree.h create mode 100644 lib/ui/painting/flutter_rtree_unittests.cc diff --git a/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc index 4a5358053928f..b17d10b4b819c 100644 --- a/flow/layers/child_scene_layer.cc +++ b/flow/layers/child_scene_layer.cc @@ -39,7 +39,10 @@ void ChildSceneLayer::Paint(PaintContext& context) const { SkPaint paint; paint.setColor(SK_ColorTRANSPARENT); paint.setBlendMode(SkBlendMode::kSrc); - context.leaf_nodes_canvas->drawRect(paint_bounds(), paint); + context.background_canvas->drawRect(paint_bounds(), paint); + if (context.leaf_nodes_canvas != nullptr) { + context.leaf_nodes_canvas->drawRect(paint_bounds(), paint); + } } void ChildSceneLayer::UpdateScene(SceneUpdateContext& context) { diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index 119fddc181fb8..09b68f7cdf72a 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -30,17 +30,26 @@ void ImageFilterLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting()); #ifndef SUPPORT_FRACTIONAL_TRANSLATION - SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); - context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); + SkAutoCanvasRestore save(context.background_canvas, true); + context.background_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.background_canvas->getTotalMatrix())); + + if (context.leaf_nodes_canvas != nullptr) { + SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); + context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); + } #endif if (context.raster_cache) { - const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix(); + const SkMatrix& ctm = context.background_canvas->getTotalMatrix(); RasterCacheResult layer_cache = context.raster_cache->Get((Layer*)this, ctm); if (layer_cache.is_valid()) { - layer_cache.draw(*context.leaf_nodes_canvas); + layer_cache.draw(*context.background_canvas); + if (context.leaf_nodes_canvas != nullptr) { + layer_cache.draw(*context.leaf_nodes_canvas); + } return; } } diff --git a/flow/layers/layer.h b/flow/layers/layer.h index 63c7cdb316d7e..074eebfd22e00 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -114,8 +114,11 @@ class Layer { // and applies the operations to all canvases. // The leaf_nodes_canvas is the "current" canvas and is used by leaf // layers. + // The background_canvas is the default canvas used by all leaf + // layers. SkCanvas* internal_nodes_canvas; SkCanvas* leaf_nodes_canvas; + SkCanvas* background_canvas; GrContext* gr_context; ExternalViewEmbedder* view_embedder; const Stopwatch& raster_time; diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index 37c28e861bf8a..5f07bb913d0fe 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -115,6 +115,7 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame, Layer::PaintContext context = { (SkCanvas*)&internal_nodes_canvas, + nullptr, frame.canvas(), frame.gr_context(), frame.view_embedder(), @@ -169,7 +170,8 @@ sk_sp LayerTree::Flatten(const SkRect& bounds) { Layer::PaintContext paint_context = { (SkCanvas*)&internal_nodes_canvas, - canvas, // canvas + nullptr, // leaf node canvas + canvas, // background canvas nullptr, nullptr, unused_stopwatch, // frame time (dont care) diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index e6ca90a66a42f..cfff1455f718d 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -88,15 +88,18 @@ void OpacityLayer::Paint(PaintContext& context) const { #ifndef SUPPORT_FRACTIONAL_TRANSLATION context.internal_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); + context.background_canvas->getTotalMatrix())); #endif if (context.raster_cache) { ContainerLayer* container = GetChildContainer(); - const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix(); + const SkMatrix& ctm = context.background_canvas->getTotalMatrix(); RasterCacheResult child_cache = context.raster_cache->Get(container, ctm); if (child_cache.is_valid()) { - child_cache.draw(*context.leaf_nodes_canvas, &paint); + child_cache.draw(*context.background_canvas, &paint); + if (context.leaf_nodes_canvas != nullptr) { + child_cache.draw(*context.leaf_nodes_canvas, &paint); + } return; } } diff --git a/flow/layers/performance_overlay_layer.cc b/flow/layers/performance_overlay_layer.cc index ef7b6f2c6194c..852b1824305f1 100644 --- a/flow/layers/performance_overlay_layer.cc +++ b/flow/layers/performance_overlay_layer.cc @@ -84,17 +84,29 @@ void PerformanceOverlayLayer::Paint(PaintContext& context) const { SkScalar y = paint_bounds().y() + padding; SkScalar width = paint_bounds().width() - (padding * 2); SkScalar height = paint_bounds().height() / 2; - SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); + SkAutoCanvasRestore save(context.background_canvas, true); VisualizeStopWatch( - *context.leaf_nodes_canvas, context.raster_time, x, y, width, + *context.background_canvas, context.raster_time, x, y, width, height - padding, options_ & kVisualizeRasterizerStatistics, options_ & kDisplayRasterizerStatistics, "GPU", font_path_); - VisualizeStopWatch(*context.leaf_nodes_canvas, context.ui_time, x, y + height, + VisualizeStopWatch(*context.background_canvas, context.ui_time, x, y + height, width, height - padding, options_ & kVisualizeEngineStatistics, options_ & kDisplayEngineStatistics, "UI", font_path_); + + if (context.leaf_nodes_canvas != nullptr) { + VisualizeStopWatch( + *context.leaf_nodes_canvas, context.raster_time, x, y, width, + height - padding, options_ & kVisualizeRasterizerStatistics, + options_ & kDisplayRasterizerStatistics, "GPU", font_path_); + + VisualizeStopWatch(*context.leaf_nodes_canvas, context.ui_time, x, + y + height, width, height - padding, + options_ & kVisualizeEngineStatistics, + options_ & kDisplayEngineStatistics, "UI", font_path_); + } } } // namespace flutter diff --git a/flow/layers/performance_overlay_layer_unittests.cc b/flow/layers/performance_overlay_layer_unittests.cc index 769f80803a8fa..52989a4bd04e8 100644 --- a/flow/layers/performance_overlay_layer_unittests.cc +++ b/flow/layers/performance_overlay_layer_unittests.cc @@ -63,8 +63,9 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { flutter::TextureRegistry unused_texture_registry; flutter::Layer::PaintContext paintContext = { - nullptr, surface->getCanvas(), nullptr, nullptr, mock_stopwatch, - mock_stopwatch, unused_texture_registry, nullptr, false}; + nullptr, nullptr, surface->getCanvas(), nullptr, nullptr, + mock_stopwatch, mock_stopwatch, unused_texture_registry, nullptr, false, + }; // Specify font file to ensure the same font across different operation // systems. diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index c64049222a8e7..350c3b36573ad 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -111,8 +111,12 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting()); if (elevation_ != 0) { - DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation_, + DrawShadow(context.background_canvas, path_, shadow_color_, elevation_, SkColorGetA(color_) != 0xff, context.frame_device_pixel_ratio); + if (context.leaf_nodes_canvas != nullptr) { + DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation_, + SkColorGetA(color_) != 0xff, context.frame_device_pixel_ratio); + } } // Call drawPath without clip if possible for better performance. @@ -120,7 +124,10 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { paint.setColor(color_); paint.setAntiAlias(true); if (clip_behavior_ != Clip::antiAliasWithSaveLayer) { - context.leaf_nodes_canvas->drawPath(path_, paint); + context.background_canvas->drawPath(path_, paint); + if (context.leaf_nodes_canvas != nullptr) { + context.leaf_nodes_canvas->drawPath(path_, paint); + } } int saveCount = context.internal_nodes_canvas->save(); @@ -144,7 +151,10 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { // (https://github.com/flutter/flutter/issues/18057#issue-328003931) // using saveLayer, we have to call drawPaint instead of drawPath as // anti-aliased drawPath will always have such artifacts. - context.leaf_nodes_canvas->drawPaint(paint); + context.background_canvas->drawPaint(paint); + if (context.leaf_nodes_canvas != nullptr) { + context.leaf_nodes_canvas->drawPaint(paint); + } } PaintChildren(context); diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index 3bc7e394c1033..d309e8a0d02d8 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -42,24 +42,42 @@ void PictureLayer::Paint(PaintContext& context) const { FML_DCHECK(picture_.get()); FML_DCHECK(needs_painting()); - SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); - context.leaf_nodes_canvas->translate(offset_.x(), offset_.y()); + SkAutoCanvasRestore save(context.background_canvas, true); + context.background_canvas->translate(offset_.x(), offset_.y()); + + if (context.leaf_nodes_canvas != nullptr) { + SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); + context.leaf_nodes_canvas->translate(offset_.x(), offset_.y()); + } + #ifndef SUPPORT_FRACTIONAL_TRANSLATION - context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); + context.background_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.background_canvas->getTotalMatrix())); + if (context.leaf_nodes_canvas != nullptr) { + context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); + } #endif if (context.raster_cache) { - const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix(); + const SkMatrix& ctm = context.background_canvas->getTotalMatrix(); RasterCacheResult result = context.raster_cache->Get(*picture(), ctm); if (result.is_valid()) { TRACE_EVENT_INSTANT0("flutter", "raster cache hit"); - result.draw(*context.leaf_nodes_canvas); + result.draw(*context.background_canvas); + if (context.leaf_nodes_canvas != nullptr) { + result.draw(*context.leaf_nodes_canvas); + } return; } } - context.leaf_nodes_canvas->drawPicture(picture()); + + context.background_canvas->drawPicture(picture()); + if (context.leaf_nodes_canvas != nullptr) { + picture()->playback(context.leaf_nodes_canvas); + } + // } } // namespace flutter diff --git a/flow/layers/platform_view_layer_unittests.cc b/flow/layers/platform_view_layer_unittests.cc index 123f9ab9925f6..ff7f30c69eedc 100644 --- a/flow/layers/platform_view_layer_unittests.cc +++ b/flow/layers/platform_view_layer_unittests.cc @@ -30,7 +30,7 @@ TEST_F(PlatformViewLayerTest, NullViewEmbedderDoesntPrerollCompositeOrPaint) { EXPECT_FALSE(layer->needs_system_composite()); layer->Paint(paint_context()); - EXPECT_EQ(paint_context().leaf_nodes_canvas, &mock_canvas()); + EXPECT_EQ(paint_context().background_canvas, &mock_canvas()); EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); } diff --git a/flow/layers/shader_mask_layer.cc b/flow/layers/shader_mask_layer.cc index 157354f7314b4..fe90a2fd3b170 100644 --- a/flow/layers/shader_mask_layer.cc +++ b/flow/layers/shader_mask_layer.cc @@ -28,9 +28,15 @@ void ShaderMaskLayer::Paint(PaintContext& context) const { SkPaint paint; paint.setBlendMode(blend_mode_); paint.setShader(shader_); - context.leaf_nodes_canvas->translate(mask_rect_.left(), mask_rect_.top()); - context.leaf_nodes_canvas->drawRect( + context.background_canvas->translate(mask_rect_.left(), mask_rect_.top()); + context.background_canvas->drawRect( SkRect::MakeWH(mask_rect_.width(), mask_rect_.height()), paint); + + if (context.leaf_nodes_canvas != nullptr) { + context.leaf_nodes_canvas->translate(mask_rect_.left(), mask_rect_.top()); + context.leaf_nodes_canvas->drawRect( + SkRect::MakeWH(mask_rect_.width(), mask_rect_.height()), paint); + } } } // namespace flutter diff --git a/flow/layers/texture_layer.cc b/flow/layers/texture_layer.cc index e5df2b795dd34..e227a3b669aaf 100644 --- a/flow/layers/texture_layer.cc +++ b/flow/layers/texture_layer.cc @@ -30,8 +30,12 @@ void TextureLayer::Paint(PaintContext& context) const { TRACE_EVENT_INSTANT0("flutter", "null texture"); return; } - texture->Paint(*context.leaf_nodes_canvas, paint_bounds(), freeze_, + texture->Paint(*context.background_canvas, paint_bounds(), freeze_, context.gr_context); + if (context.leaf_nodes_canvas != nullptr) { + texture->Paint(*context.leaf_nodes_canvas, paint_bounds(), freeze_, + context.gr_context); + } } } // namespace flutter diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index f179905ed8e53..9aa8109befe33 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -147,6 +147,7 @@ void RasterCache::Prepare(PrerollContext* context, internal_nodes_canvas.addCanvas(canvas); Layer::PaintContext paintContext = { (SkCanvas*)&internal_nodes_canvas, + nullptr, canvas, context->gr_context, nullptr, diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc index 59524bc9c6e56..20eadfee3f16a 100644 --- a/flow/scene_update_context.cc +++ b/flow/scene_update_context.cc @@ -201,6 +201,7 @@ SceneUpdateContext::ExecutePaintTasks(CompositorContext::ScopedFrame& frame) { FML_DCHECK(task.surface); SkCanvas* canvas = task.surface->GetSkiaSurface()->getCanvas(); Layer::PaintContext context = {canvas, + nullptr, canvas, frame.gr_context(), nullptr, diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h index 593dec1836823..9e5018c66c6d6 100644 --- a/flow/testing/layer_test.h +++ b/flow/testing/layer_test.h @@ -48,6 +48,7 @@ class LayerTestBase : public CanvasTestBase { paint_context_({ TestT::mock_canvas().internal_canvas(), /* internal_nodes_canvas */ &TestT::mock_canvas(), /* leaf_nodes_canvas */ + &TestT::mock_canvas(), /* background_canvas */ nullptr, /* gr_context */ nullptr, /* external_view_embedder */ raster_time_, ui_time_, texture_registry_, diff --git a/flow/testing/mock_layer.cc b/flow/testing/mock_layer.cc index 5fe1b98088af1..64bb963ad2d5b 100644 --- a/flow/testing/mock_layer.cc +++ b/flow/testing/mock_layer.cc @@ -36,7 +36,7 @@ void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { void MockLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting()); - context.leaf_nodes_canvas->drawPath(fake_paint_path_, fake_paint_); + context.background_canvas->drawPath(fake_paint_path_, fake_paint_); } } // namespace testing diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 4d3210d3bef37..32c182f00e2cd 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -30,6 +30,8 @@ source_set("ui") { "painting/color_filter.h", "painting/engine_layer.cc", "painting/engine_layer.h", + "painting/flutter_rtree.cc", + "painting/flutter_rtree.h", "painting/frame_info.cc", "painting/frame_info.h", "painting/gradient.cc", @@ -168,6 +170,7 @@ if (current_toolchain == host_toolchain) { "painting/image_decoder_test.cc", "painting/image_decoder_test.h", "painting/image_decoder_unittests.cc", + "painting/flutter_rtree_unittests.cc", "window/pointer_data_packet_converter_unittests.cc", ] diff --git a/lib/ui/painting/flutter_rtree.cc b/lib/ui/painting/flutter_rtree.cc new file mode 100644 index 0000000000000..e0beaae57b80a --- /dev/null +++ b/lib/ui/painting/flutter_rtree.cc @@ -0,0 +1,236 @@ +// 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 "flutter/lib/ui/painting/flutter_rtree.h" +#include "flutter/fml/logging.h" + +namespace flutter { + +FlutterRTree::FlutterRTree() : fCount(0) {} + +void FlutterRTree::insert(const SkRect boundsArray[], + const SkBBoxHierarchy::Metadata metadata[], + int N) { + SkASSERT(0 == fCount); + + std::vector branches; + branches.reserve(N); + + for (int i = 0; i < N; i++) { + const SkRect& bounds = boundsArray[i]; + if (bounds.isEmpty()) { + continue; + } + Branch b; + b.fBounds = bounds; + b.fOpIndex = i; + b.isDraw = metadata[i].isDraw; + branches.push_back(b); + } + + fCount = (int)branches.size(); + if (fCount) { + if (1 == fCount) { + fNodes.reserve(1); + Node* n = this->allocateNodeAtLevel(0); + n->fNumChildren = 1; + n->fChildren[0] = branches[0]; + fRoot.fSubtree = n; + fRoot.fBounds = branches[0].fBounds; + } else { + fNodes.reserve(CountNodes(fCount)); + fRoot = this->bulkLoad(&branches); + } + } +} + +void FlutterRTree::insert(const SkRect[], int N) { + SkASSERT(false); +} + +FlutterRTree::Node* FlutterRTree::allocateNodeAtLevel(uint16_t level) { + SkDEBUGCODE(Node* p = fNodes.data()); + fNodes.push_back(Node{}); + Node& out = fNodes.back(); + SkASSERT(fNodes.data() == p); // If this fails, we didn't reserve() enough. + out.fNumChildren = 0; + out.fLevel = level; + return &out; +} + +// This function parallels bulkLoad, but just counts how many nodes bulkLoad +// would allocate. +int FlutterRTree::CountNodes(int branches) { + if (branches == 1) { + return 1; + } + int numBranches = branches / kMaxChildren; + int remainder = branches % kMaxChildren; + if (remainder > 0) { + numBranches++; + if (remainder >= kMinChildren) { + remainder = 0; + } else { + remainder = kMinChildren - remainder; + } + } + int currentBranch = 0; + int nodes = 0; + while (currentBranch < branches) { + int incrementBy = kMaxChildren; + if (remainder != 0) { + if (remainder <= kMaxChildren - kMinChildren) { + incrementBy -= remainder; + remainder = 0; + } else { + incrementBy = kMinChildren; + remainder -= kMaxChildren - kMinChildren; + } + } + nodes++; + currentBranch++; + for (int k = 1; k < incrementBy && currentBranch < branches; ++k) { + currentBranch++; + } + } + return nodes + CountNodes(nodes); +} + +FlutterRTree::Branch FlutterRTree::bulkLoad(std::vector* branches, + int level) { + if (branches->size() == 1) { // Only one branch. It will be the root. + return (*branches)[0]; + } + + // We might sort our branches here, but we expect Blink gives us a reasonable + // x,y order. Skipping a call to sort (in Y) here resulted in a 17% win for + // recording with negligible difference in playback speed. + int numBranches = (int)branches->size() / kMaxChildren; + int remainder = (int)branches->size() % kMaxChildren; + int newBranches = 0; + + if (remainder > 0) { + ++numBranches; + // If the remainder isn't enough to fill a node, we'll add fewer nodes to + // other branches. + if (remainder >= kMinChildren) { + remainder = 0; + } else { + remainder = kMinChildren - remainder; + } + } + + int currentBranch = 0; + while (currentBranch < (int)branches->size()) { + int incrementBy = kMaxChildren; + if (remainder != 0) { + // if need be, omit some nodes to make up for remainder + if (remainder <= kMaxChildren - kMinChildren) { + incrementBy -= remainder; + remainder = 0; + } else { + incrementBy = kMinChildren; + remainder -= kMaxChildren - kMinChildren; + } + } + Node* n = allocateNodeAtLevel(level); + n->fNumChildren = 1; + n->fChildren[0] = (*branches)[currentBranch]; + Branch b; + b.fBounds = (*branches)[currentBranch].fBounds; + b.fSubtree = n; + ++currentBranch; + for (int k = 1; k < incrementBy && currentBranch < (int)branches->size(); + ++k) { + b.fBounds.join((*branches)[currentBranch].fBounds); + n->fChildren[k] = (*branches)[currentBranch]; + ++n->fNumChildren; + ++currentBranch; + } + (*branches)[newBranches] = b; + ++newBranches; + } + branches->resize(newBranches); + return this->bulkLoad(branches, level + 1); +} + +void FlutterRTree::search(const SkRect& query, + std::vector* results) const { + if (fCount > 0 && SkRect::Intersects(fRoot.fBounds, query)) { + this->search(fRoot.fSubtree, query, results); + } +} + +void FlutterRTree::search(Node* node, + const SkRect& query, + std::vector* results) const { + for (int i = 0; i < node->fNumChildren; ++i) { + if (SkRect::Intersects(node->fChildren[i].fBounds, query)) { + if (0 == node->fLevel) { + results->push_back(node->fChildren[i].fOpIndex); + } else { + this->search(node->fChildren[i].fSubtree, query, results); + } + } + } +} + +void FlutterRTree::searchRects(const SkRect& query, + std::vector* results) const { + if (fCount > 0 && SkRect::Intersects(fRoot.fBounds, query)) { + this->searchRects(fRoot.fSubtree, query, results); + } +} + +void FlutterRTree::searchRects(Node* node, + const SkRect& query, + std::vector* results) const { + if (!SkRect::Intersects(fRoot.fBounds, query)) { + return; + } + for (int i = 0; i < node->fNumChildren; ++i) { + if (SkRect::Intersects(node->fChildren[i].fBounds, query)) { + // Non-leaf node + if (0 != node->fLevel) { + this->searchRects(node->fChildren[i].fSubtree, query, results); + continue; + } + // Ignore records that don't draw anything. + // TODO(egarciad): Figure out if this can be moved to insert(). + if (!node->fChildren[i].isDraw) { + continue; + } + SkRect* currentRecordRect = &node->fChildren[i].fBounds; + bool replacedExistingRect = false; + + std::vector currentResults = *results; + // If the current record rect intersects with any of the rects in the + // result, then join them, and update the rect in results. + for (size_t j = 0; !replacedExistingRect && j < results->size(); j++) { + if (SkRect::Intersects(*currentResults[j], *currentRecordRect)) { + currentResults[j]->join(*currentRecordRect); + replacedExistingRect = true; + } + } + if (!replacedExistingRect) { + results->push_back(currentRecordRect); + } + } + } +} + +size_t FlutterRTree::bytesUsed() const { + size_t byteCount = sizeof(FlutterRTree); + byteCount += fNodes.capacity() * sizeof(Node); + return byteCount; +} + +FlutterRTreeFactory::FlutterRTreeFactory(sk_sp& r_tree) + : r_tree_(r_tree) {} + +sk_sp FlutterRTreeFactory::operator()() const { + return r_tree_; +} + +} // namespace flutter diff --git a/lib/ui/painting/flutter_rtree.h b/lib/ui/painting/flutter_rtree.h new file mode 100644 index 0000000000000..a254a7912bef0 --- /dev/null +++ b/lib/ui/painting/flutter_rtree.h @@ -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. + +#ifndef FLUTTER_LIB_UI_PAINTING_PATCHED_R_TREE_H_ +#define FLUTTER_LIB_UI_PAINTING_PATCHED_R_TREE_H_ + +#include "third_party/skia/include/core/SkBBHFactory.h" +#include "third_party/skia/include/core/SkTypes.h" + +namespace flutter { + +class FlutterRTree : public SkBBoxHierarchy { + public: + FlutterRTree(); + + void insert(const SkRect[], + const SkBBoxHierarchy::Metadata[], + int N) override; + void insert(const SkRect[], int N) override; + void search(const SkRect& query, std::vector* results) const override; + void searchRects(const SkRect& query, std::vector* results) const; + size_t bytesUsed() const override; + + // Methods and constants below here are only public for tests. + + // Return the depth of the tree structure. + int getDepth() const { return fCount ? fRoot.fSubtree->fLevel + 1 : 0; } + // Insertion count (not overall node count, which may be greater). + int getCount() const { return fCount; } + + // These values were empirically determined to produce reasonable performance + // in most cases. + static const int kMinChildren = 6, kMaxChildren = 11; + + private: + struct Node; + + struct Branch { + union { + Node* fSubtree; + int fOpIndex; + }; + bool isDraw; + SkRect fBounds; + }; + + struct Node { + uint16_t fNumChildren; + uint16_t fLevel; + Branch fChildren[kMaxChildren]; + }; + + void search(Node* root, const SkRect& query, std::vector* results) const; + void searchRects(Node* root, + const SkRect& query, + std::vector* results) const; + + // Consumes the input array. + Branch bulkLoad(std::vector* branches, int level = 0); + + // How many times will bulkLoad() call allocateNodeAtLevel()? + static int CountNodes(int branches); + + Node* allocateNodeAtLevel(uint16_t level); + + // This is the count of data elements (rather than total nodes in the tree) + int fCount; + Branch fRoot; + std::vector fNodes; +}; + +class FlutterRTreeFactory : public SkBBHFactory { + public: + FlutterRTreeFactory(sk_sp& r_tree); + sk_sp operator()() const override; + + private: + sk_sp r_tree_; +}; + +} // namespace flutter + +#endif // FLUTTER_LIB_UI_PAINTING_PATCHED_R_TREE_H_ diff --git a/lib/ui/painting/flutter_rtree_unittests.cc b/lib/ui/painting/flutter_rtree_unittests.cc new file mode 100644 index 0000000000000..7a4a145d98e38 --- /dev/null +++ b/lib/ui/painting/flutter_rtree_unittests.cc @@ -0,0 +1,59 @@ +// 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 "flutter/lib/ui/painting/flutter_rtree.h" +#include "flutter/testing/testing.h" +#include "flutter/testing/thread_test.h" + +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" + +namespace flutter { +namespace testing { + +using FlutterRTreeTest = ThreadTest; + +TEST_F(FlutterRTreeTest, DetectsIntersection) { + // https://fiddle.skia.org/c/02b6328629ade6240dc4a830a4cc4cbb + + auto r_tree = sk_make_sp(); + auto rtree_factory = FlutterRTreeFactory(r_tree); + + auto recorder = std::make_unique(); + + auto recording_window = SkRect::MakeIWH(1000, 1000); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + SkCanvas* recording_canvas = + recorder->beginRecording(recording_window, &rtree_factory); + recording_canvas->drawRect(SkRect::MakeXYWH(20, 20, 20, 20), rect_paint); + + recording_canvas->save(); + recording_canvas->translate(-20, -20); + recording_canvas->drawRect(SkRect::MakeXYWH(100, 100, 30, 30), rect_paint); + recording_canvas->restore(); + + recording_canvas->drawRect(SkRect::MakeXYWH(120, 120, 30, 30), rect_paint); + + recorder->finishRecordingAsPicture(); + + auto hits = std::vector(); + auto unobstructed = SkRect::MakeXYWH(50, 50, 20, 20); + r_tree->searchRects(unobstructed, &hits); + ASSERT_TRUE(hits.empty()); + hits.clear(); + + // Hits that partially overlap with a drawn area return bboxes describing the + // intersection of the query and the drawn area. + auto one_hit = SkRect::MakeXYWH(140, 140, 30, 30); + r_tree->searchRects(one_hit, &hits); + ASSERT_EQ(1UL, hits.size()); + ASSERT_EQ(*hits[0], SkRect::MakeXYWH(120, 120, 30, 30)); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/common/persistent_cache.cc b/shell/common/persistent_cache.cc index 66690971ace46..f1076ea4c3cdb 100644 --- a/shell/common/persistent_cache.cc +++ b/shell/common/persistent_cache.cc @@ -240,6 +240,7 @@ void PersistentCache::store(const SkData& key, const SkData& data) { } void PersistentCache::DumpSkp(const SkData& data) { + FML_LOG(ERROR) << "PersistentCache::DumpSkp"; if (is_read_only_ || !IsValid()) { FML_LOG(ERROR) << "Could not dump SKP from read-only or invalid persistent " "cache."; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index b86c4623fa7a7..b2f03dd29cec3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -14,10 +14,69 @@ #include "FlutterPlatformViews_Internal.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/lib/ui/painting/flutter_rtree.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_ >= layer_.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:nil]; + 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; + } + layer_.push_back(layer); + } + auto layer = layer_[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 < layer_.size(); i++) { + results.push_back(layer_[i]); + } + return results; +} + void FlutterPlatformViewsController::SetFlutterView(UIView* flutter_view) { flutter_view_.reset([flutter_view retain]); } @@ -195,17 +254,20 @@ void FlutterPlatformViewsController::PrerollCompositeEmbeddedView( 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); + platform_views_recorder_[view_id] = std::make_unique(); + platform_views_bbh_[view_id] = sk_make_sp(); + + auto bbh_factory = FlutterRTreeFactory(platform_views_bbh_[view_id]); + platform_views_recorder_[view_id]->beginRecording(SkRect::Make(frame_size_), &bbh_factory); + composition_order_.push_back(view_id); - if (current_composition_params_.count(view_id) == 1 && - current_composition_params_[view_id] == *params.get()) { + if (platform_views_params_.count(view_id) == 1 && + platform_views_params_[view_id] == *params.get()) { // Do nothing if the params didn't change. return; } - current_composition_params_[view_id] = EmbeddedViewParams(*params.get()); + platform_views_params_[view_id] = EmbeddedViewParams(*params.get()); views_to_recomposite_.insert(view_id); } @@ -220,7 +282,7 @@ std::vector canvases; for (size_t i = 0; i < composition_order_.size(); i++) { int64_t view_id = composition_order_[i]; - canvases.push_back(picture_recorders_[view_id]->getRecordingCanvas()); + canvases.push_back(platform_views_recorder_[view_id]->getRecordingCanvas()); } return canvases; } @@ -344,11 +406,11 @@ // Do nothing if the view doesn't need to be composited. if (views_to_recomposite_.count(view_id) == 0) { - return picture_recorders_[view_id]->getRecordingCanvas(); + return platform_views_recorder_[view_id]->getRecordingCanvas(); } - CompositeWithParams(view_id, current_composition_params_[view_id]); + CompositeWithParams(view_id, platform_views_params_[view_id]); views_to_recomposite_.erase(view_id); - return picture_recorders_[view_id]->getRecordingCanvas(); + return platform_views_recorder_[view_id]->getRecordingCanvas(); } void FlutterPlatformViewsController::Reset() { @@ -360,10 +422,12 @@ overlays_.clear(); composition_order_.clear(); active_composition_order_.clear(); - picture_recorders_.clear(); - current_composition_params_.clear(); + platform_views_recorder_.clear(); + platform_views_bbh_.clear(); + platform_views_params_.clear(); clip_count_.clear(); views_to_recomposite_.clear(); + layer_pool_->RecycleLayers(); } bool FlutterPlatformViewsController::SubmitFrame(GrContext* gr_context, @@ -371,71 +435,141 @@ 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(); + CGFloat screenScale = [UIScreen mainScreen].scale; + + // Maps a platform view id to a vector of `FlutterPlatformViewLayer`. + LayersMap platform_view_layers; + + size_t num_platform_views = composition_order_.size(); + for (size_t i = 0; i < num_platform_views; i++) { + int platform_view_id = composition_order_[i]; + + FlutterRTree* bbh = platform_views_bbh_[platform_view_id].get(); + sk_sp picture = + platform_views_recorder_[platform_view_id]->finishRecordingAsPicture(); + + for (size_t j = i + 1; j > 0; j--) { + int current_platform_view_id = composition_order_[j - 1]; + EmbeddedViewParams params = platform_views_params_[current_platform_view_id]; + SkRect platform_view_rect = SkRect::MakeXYWH( + /*x=*/params.offsetPixels.x(), + /*y=*/params.offsetPixels.y(), + /*width=*/params.sizePoints.width() * screenScale, + /*height=*/params.sizePoints.height() * screenScale); + + std::vector intersection_rects; + bbh->searchRects(platform_view_rect, &intersection_rects); + + size_t allocation_size = intersection_rects.size(); + if (allocation_size > kMaxLayerAllocations) { + // If the max number of allocations per platform view is exceeded, + // then join all the rects into a single one. + SkRect joined_rect; + for (SkRect* rect : intersection_rects) { + joined_rect.join(*rect); + } + // Get the intersection rect between the current joined rect + // and the platform view rect. + joined_rect.intersect(platform_view_rect); + auto layer = GetLayer(gr_context, ios_context, picture, joined_rect); + did_submit &= layer->did_submit_last_frame; + platform_view_layers[current_platform_view_id].push_back(layer); + } else if (allocation_size > 0) { + for (SkRect* rect : intersection_rects) { + // Get the intersection rect between the current rect + // and the platform view rect. + SkRect joined_rect = *rect; + joined_rect.intersect(platform_view_rect); + auto layer = GetLayer(gr_context, ios_context, picture, joined_rect); + did_submit &= layer->did_submit_last_frame; + platform_view_layers[current_platform_view_id].push_back(layer); + } + } } } - 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(); + RemoveUnusedLayers(); + BringLayersIntoView(platform_view_layers); + + composition_order_.clear(); + layer_pool_->RecycleLayers(); + return did_submit; +} +void FlutterPlatformViewsController::BringLayersIntoView(LayersMap layer_map) { + UIView* flutter_view = flutter_view_.get(); + int 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 { + int platform_view_id = composition_order_[i]; + auto 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 (auto 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() { +void FlutterPlatformViewsController::RemoveUnusedLayers() { + auto layers = layer_pool_->GetUnusedLayers(); + for (auto 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]; } } } +std::shared_ptr FlutterPlatformViewsController::GetLayer( + GrContext* gr_context, + std::shared_ptr ios_context, + sk_sp picture, + SkRect rect) { + std::shared_ptr layer = layer_pool_->GetLayer(gr_context, ios_context); + UIView* overlay_view = layer->overlay_view; + + overlay_view.backgroundColor = [UIColor blueColor]; + + CGFloat screenScale = [UIScreen mainScreen].scale; + // Set the size of the overlay UIView. + overlay_view.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale, + rect.width() / screenScale, rect.height() / screenScale); + + SkISize rect_size = SkISize::Make(rect.width(), rect.height()); + std::unique_ptr frame = layer->surface->AcquireFrame(rect_size); + + // If frame is null, AcquireFrame already printed out an error message. + if (frame == nullptr) { + return layer; + } + SkCanvas* overlay_canvas = frame->SkiaCanvas(); + overlay_canvas->clear(SK_ColorTRANSPARENT); + overlay_canvas->translate(-rect.x(), -rect.y()); + overlay_canvas->drawPicture(picture); + + layer->did_submit_last_frame = frame->Submit(); + return layer; +} + void FlutterPlatformViewsController::DisposeViews() { if (views_to_dispose_.empty()) { return; @@ -451,63 +585,13 @@ [overlays_[viewId]->overlay_view.get() removeFromSuperview]; } overlays_.erase(viewId); - current_composition_params_.erase(viewId); + platform_views_params_.erase(viewId); clip_count_.erase(viewId); views_to_recomposite_.erase(viewId); } 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..34280bd997cc6 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -7,6 +7,7 @@ #include "flutter/flow/embedded_views.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/lib/ui/painting/flutter_rtree.h" #include "flutter/shell/common/shell.h" #include "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #include "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" @@ -62,11 +63,12 @@ struct FlutterPlatformViewLayer { std::unique_ptr ios_surface, std::unique_ptr surface); - ~FlutterPlatformViewLayer(); + ~FlutterPlatformViewLayer() = default; fml::scoped_nsobject overlay_view; std::unique_ptr ios_surface; std::unique_ptr surface; + 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 @@ -74,11 +76,31 @@ struct FlutterPlatformViewLayer { GrContext* gr_context; }; +class FlutterPlatformViewLayerPool { + public: + FlutterPlatformViewLayerPool() = default; + ~FlutterPlatformViewLayerPool() = default; + + // Gets a view layer from an internal pool. + // If no availble, it allocates a new layer. + std::shared_ptr GetLayer(GrContext* gr_context, + std::shared_ptr ios_context); + + std::vector> GetUnusedLayers(); + + // Makes all the layers in the pool available for reuse. + void RecycleLayers(); + + private: + size_t available_layer_index_ = 0; + std::vector> layer_; +}; + class FlutterPlatformViewsController { public: FlutterPlatformViewsController(); - ~FlutterPlatformViewsController(); + ~FlutterPlatformViewsController() = default; void SetFlutterView(UIView* flutter_view); @@ -117,6 +139,16 @@ class FlutterPlatformViewsController { void OnMethodCall(FlutterMethodCall* call, FlutterResult& result); private: + static const size_t kMaxLayerAllocations = 2; + + using LayersMap = std::map>>; + + // The pool of reusable view layers. + std::unique_ptr layer_pool_; + std::map> platform_views_bbh_; + std::map platform_views_params_; + std::map> platform_views_recorder_; + fml::scoped_nsobject channel_; fml::scoped_nsobject flutter_view_; fml::scoped_nsobject flutter_view_controller_; @@ -163,19 +195,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 +240,18 @@ 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); + // 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..8450568bc7358 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -18,11 +18,8 @@ ios_surface(std::move(ios_surface)), surface(std::move(surface)){}; -FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default; - -FlutterPlatformViewsController::FlutterPlatformViewsController() = default; - -FlutterPlatformViewsController::~FlutterPlatformViewsController() = default; +FlutterPlatformViewsController::FlutterPlatformViewsController() + : layer_pool_(std::make_unique()){}; CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) { // Skia only supports 2D transform so we don't map z. From 1fde093777db2c1dcce08b55de354c7ad5ff8398 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 2 Mar 2020 14:43:20 -0800 Subject: [PATCH 02/44] Comments --- .../ios/framework/Source/FlutterPlatformViews_Internal.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 34280bd997cc6..712375b9bf86a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -81,14 +81,15 @@ class FlutterPlatformViewLayerPool { FlutterPlatformViewLayerPool() = default; ~FlutterPlatformViewLayerPool() = default; - // Gets a view layer from an internal pool. - // If no availble, it allocates a new layer. + // Gets a layer from the pool if available, or allocates a new one. + // Finally, it marks the layer as used. std::shared_ptr GetLayer(GrContext* gr_context, std::shared_ptr ios_context); + // Gets the layers in the pool that aren't currently used. std::vector> GetUnusedLayers(); - // Makes all the layers in the pool available for reuse. + // Marks the layers in the pool as available for reuse. void RecycleLayers(); private: From 9b747a3d650291707e8945b2930404849d5ab5fb Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 2 Mar 2020 15:07:55 -0800 Subject: [PATCH 03/44] Format gn --- lib/ui/BUILD.gn | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 32c182f00e2cd..0c08356bd9e12 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -167,10 +167,11 @@ if (current_toolchain == host_toolchain) { configs += [ "//flutter:export_dynamic_symbols" ] sources = [ + "painting/flutter_rtree_unittests.cc", "painting/image_decoder_test.cc", "painting/image_decoder_test.h", "painting/image_decoder_unittests.cc", - "painting/flutter_rtree_unittests.cc", + "painting/image_decoder_unittests.cc", "window/pointer_data_packet_converter_unittests.cc", ] From e8cfa5d60821407df6f683df2e09b62ce1329dcc Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 2 Mar 2020 15:09:21 -0800 Subject: [PATCH 04/44] Remove log --- shell/common/persistent_cache.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/common/persistent_cache.cc b/shell/common/persistent_cache.cc index f1076ea4c3cdb..66690971ace46 100644 --- a/shell/common/persistent_cache.cc +++ b/shell/common/persistent_cache.cc @@ -240,7 +240,6 @@ void PersistentCache::store(const SkData& key, const SkData& data) { } void PersistentCache::DumpSkp(const SkData& data) { - FML_LOG(ERROR) << "PersistentCache::DumpSkp"; if (is_read_only_ || !IsValid()) { FML_LOG(ERROR) << "Could not dump SKP from read-only or invalid persistent " "cache."; From ae64ac3740118b461d433d2295c8829e1565ccce Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 2 Mar 2020 15:38:38 -0800 Subject: [PATCH 05/44] Add FlutterRTree unit test --- lib/ui/painting/flutter_rtree.cc | 71 +++++++++++----------- lib/ui/painting/flutter_rtree_unittests.cc | 69 +++++++++++++++------ 2 files changed, 87 insertions(+), 53 deletions(-) diff --git a/lib/ui/painting/flutter_rtree.cc b/lib/ui/painting/flutter_rtree.cc index e0beaae57b80a..dd65cfc85cb64 100644 --- a/lib/ui/painting/flutter_rtree.cc +++ b/lib/ui/painting/flutter_rtree.cc @@ -25,7 +25,7 @@ void FlutterRTree::insert(const SkRect boundsArray[], Branch b; b.fBounds = bounds; b.fOpIndex = i; - b.isDraw = metadata[i].isDraw; + b.isDraw = (metadata == nullptr) ? false : metadata[i].isDraw; branches.push_back(b); } @@ -45,8 +45,9 @@ void FlutterRTree::insert(const SkRect boundsArray[], } } -void FlutterRTree::insert(const SkRect[], int N) { - SkASSERT(false); +void FlutterRTree::insert(const SkRect boundsArray[], int N) { + // TODO(egarciad): Ask Skia about removing this method signature. + insert(boundsArray, nullptr, N); } FlutterRTree::Node* FlutterRTree::allocateNodeAtLevel(uint16_t level) { @@ -166,13 +167,14 @@ void FlutterRTree::search(Node* node, const SkRect& query, std::vector* results) const { for (int i = 0; i < node->fNumChildren; ++i) { - if (SkRect::Intersects(node->fChildren[i].fBounds, query)) { - if (0 == node->fLevel) { - results->push_back(node->fChildren[i].fOpIndex); - } else { - this->search(node->fChildren[i].fSubtree, query, results); - } + if (!SkRect::Intersects(node->fChildren[i].fBounds, query)) { + continue; + } + if (0 != node->fLevel) { + this->search(node->fChildren[i].fSubtree, query, results); + continue; } + results->push_back(node->fChildren[i].fOpIndex); } } @@ -190,33 +192,34 @@ void FlutterRTree::searchRects(Node* node, return; } for (int i = 0; i < node->fNumChildren; ++i) { - if (SkRect::Intersects(node->fChildren[i].fBounds, query)) { - // Non-leaf node - if (0 != node->fLevel) { - this->searchRects(node->fChildren[i].fSubtree, query, results); - continue; - } - // Ignore records that don't draw anything. - // TODO(egarciad): Figure out if this can be moved to insert(). - if (!node->fChildren[i].isDraw) { - continue; - } - SkRect* currentRecordRect = &node->fChildren[i].fBounds; - bool replacedExistingRect = false; - - std::vector currentResults = *results; - // If the current record rect intersects with any of the rects in the - // result, then join them, and update the rect in results. - for (size_t j = 0; !replacedExistingRect && j < results->size(); j++) { - if (SkRect::Intersects(*currentResults[j], *currentRecordRect)) { - currentResults[j]->join(*currentRecordRect); - replacedExistingRect = true; - } - } - if (!replacedExistingRect) { - results->push_back(currentRecordRect); + if (!SkRect::Intersects(node->fChildren[i].fBounds, query)) { + continue; + } + // Non-leaf node + if (0 != node->fLevel) { + this->searchRects(node->fChildren[i].fSubtree, query, results); + continue; + } + // Ignore records that don't draw anything. + // TODO(egarciad): Figure out if this can be moved to insert(). + if (!node->fChildren[i].isDraw) { + continue; + } + SkRect* currentRecordRect = &node->fChildren[i].fBounds; + bool replacedExistingRect = false; + + std::vector currentResults = *results; + // If the current record rect intersects with any of the rects in the + // result, then join them, and update the rect in results. + for (size_t j = 0; !replacedExistingRect && j < results->size(); j++) { + if (SkRect::Intersects(*currentResults[j], *currentRecordRect)) { + currentResults[j]->join(*currentRecordRect); + replacedExistingRect = true; } } + if (!replacedExistingRect) { + results->push_back(currentRecordRect); + } } } diff --git a/lib/ui/painting/flutter_rtree_unittests.cc b/lib/ui/painting/flutter_rtree_unittests.cc index 7a4a145d98e38..7106b6318e8f7 100644 --- a/lib/ui/painting/flutter_rtree_unittests.cc +++ b/lib/ui/painting/flutter_rtree_unittests.cc @@ -14,45 +14,76 @@ namespace testing { using FlutterRTreeTest = ThreadTest; -TEST_F(FlutterRTreeTest, DetectsIntersection) { - // https://fiddle.skia.org/c/02b6328629ade6240dc4a830a4cc4cbb - +TEST_F(FlutterRTreeTest, NoIntersection) { auto r_tree = sk_make_sp(); auto rtree_factory = FlutterRTreeFactory(r_tree); - auto recorder = std::make_unique(); - - auto recording_window = SkRect::MakeIWH(1000, 1000); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); auto rect_paint = SkPaint(); rect_paint.setColor(SkColors::kCyan); rect_paint.setStyle(SkPaint::Style::kFill_Style); - SkCanvas* recording_canvas = - recorder->beginRecording(recording_window, &rtree_factory); recording_canvas->drawRect(SkRect::MakeXYWH(20, 20, 20, 20), rect_paint); + recorder->finishRecordingAsPicture(); - recording_canvas->save(); - recording_canvas->translate(-20, -20); - recording_canvas->drawRect(SkRect::MakeXYWH(100, 100, 30, 30), rect_paint); - recording_canvas->restore(); + auto hits = std::vector(); + auto unobstructed = SkRect::MakeXYWH(40, 40, 20, 20); - recording_canvas->drawRect(SkRect::MakeXYWH(120, 120, 30, 30), rect_paint); + r_tree->searchRects(unobstructed, &hits); + ASSERT_TRUE(hits.empty()); +} + +TEST_F(FlutterRTreeTest, Intersection) { + auto r_tree = sk_make_sp(); + auto rtree_factory = FlutterRTreeFactory(r_tree); + auto recorder = std::make_unique(); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + recording_canvas->drawRect(SkRect::MakeXYWH(120, 120, 40, 40), rect_paint); recorder->finishRecordingAsPicture(); + // Hits that partially overlap with a drawn area return bboxes describing the + // intersection of the query and the drawn area. + auto one_hit = SkRect::MakeXYWH(140, 140, 40, 40); auto hits = std::vector(); - auto unobstructed = SkRect::MakeXYWH(50, 50, 20, 20); - r_tree->searchRects(unobstructed, &hits); - ASSERT_TRUE(hits.empty()); - hits.clear(); + + r_tree->searchRects(one_hit, &hits); + ASSERT_EQ(1UL, hits.size()); + ASSERT_EQ(*hits[0], SkRect::MakeXYWH(120, 120, 40, 40)); +} + +TEST_F(FlutterRTreeTest, JoinRectsWhenIntersected) { + auto r_tree = sk_make_sp(); + auto rtree_factory = FlutterRTreeFactory(r_tree); + auto recorder = std::make_unique(); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + recording_canvas->drawRect(SkRect::MakeXYWH(120, 120, 40, 40), rect_paint); + recording_canvas->drawRect(SkRect::MakeXYWH(140, 140, 40, 40), rect_paint); + + recorder->finishRecordingAsPicture(); // Hits that partially overlap with a drawn area return bboxes describing the // intersection of the query and the drawn area. - auto one_hit = SkRect::MakeXYWH(140, 140, 30, 30); + auto one_hit = SkRect::MakeXYWH(142, 142, 10, 10); + auto hits = std::vector(); + r_tree->searchRects(one_hit, &hits); ASSERT_EQ(1UL, hits.size()); - ASSERT_EQ(*hits[0], SkRect::MakeXYWH(120, 120, 30, 30)); + ASSERT_EQ(*hits[0], SkRect::MakeXYWH(120, 120, 60, 60)); } } // namespace testing From e722620b9a4a53b20a2a1531318150a52b6aea1a Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 2 Mar 2020 16:19:20 -0800 Subject: [PATCH 06/44] Remove blue UIView --- .../darwin/ios/framework/Source/FlutterPlatformViews.mm | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index b2f03dd29cec3..abcead7c49291 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -545,14 +545,11 @@ sk_sp picture, SkRect rect) { std::shared_ptr layer = layer_pool_->GetLayer(gr_context, ios_context); - UIView* overlay_view = layer->overlay_view; - - overlay_view.backgroundColor = [UIColor blueColor]; - CGFloat screenScale = [UIScreen mainScreen].scale; // Set the size of the overlay UIView. - overlay_view.frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale, - rect.width() / screenScale, rect.height() / screenScale); + layer->overlay_view.get().frame = + CGRectMake(rect.x() / screenScale, rect.y() / screenScale, rect.width() / screenScale, + rect.height() / screenScale); SkISize rect_size = SkISize::Make(rect.width(), rect.height()); std::unique_ptr frame = layer->surface->AcquireFrame(rect_size); From bce3c8a9f44d22f7dfa7e114313f294ca5875dd7 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 2 Mar 2020 16:19:37 -0800 Subject: [PATCH 07/44] Fix header --- lib/ui/painting/flutter_rtree.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ui/painting/flutter_rtree.h b/lib/ui/painting/flutter_rtree.h index a254a7912bef0..2f527ac84f0df 100644 --- a/lib/ui/painting/flutter_rtree.h +++ b/lib/ui/painting/flutter_rtree.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_LIB_UI_PAINTING_PATCHED_R_TREE_H_ -#define FLUTTER_LIB_UI_PAINTING_PATCHED_R_TREE_H_ +#ifndef FLUTTER_LIB_UI_PAINTING_FLUTTER_RTREE_H_ +#define FLUTTER_LIB_UI_PAINTING_FLUTTER_RTREE_H_ #include "third_party/skia/include/core/SkBBHFactory.h" #include "third_party/skia/include/core/SkTypes.h" From b1975e136ba243cc29570f486cbe1aef08a75d99 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 2 Mar 2020 16:19:56 -0800 Subject: [PATCH 08/44] Update licenses_flutter --- ci/licenses_golden/licenses_flutter | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 2f6c5c31fccef..b3342763fc373 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -301,6 +301,9 @@ FILE: ../../../flutter/lib/ui/painting/color_filter.cc FILE: ../../../flutter/lib/ui/painting/color_filter.h FILE: ../../../flutter/lib/ui/painting/engine_layer.cc FILE: ../../../flutter/lib/ui/painting/engine_layer.h +FILE: ../../../flutter/lib/ui/painting/flutter_rtree.cc +FILE: ../../../flutter/lib/ui/painting/flutter_rtree.h +FILE: ../../../flutter/lib/ui/painting/flutter_rtree_unittests.cc FILE: ../../../flutter/lib/ui/painting/frame_info.cc FILE: ../../../flutter/lib/ui/painting/frame_info.h FILE: ../../../flutter/lib/ui/painting/gradient.cc From 43183063e2595c3aa296f5d644c8f7092f159e62 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 2 Mar 2020 16:30:32 -0800 Subject: [PATCH 09/44] Update unit tests --- flow/testing/layer_test.h | 2 +- shell/platform/embedder/tests/embedder_unittests.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h index 9e5018c66c6d6..2c4d81c8091b9 100644 --- a/flow/testing/layer_test.h +++ b/flow/testing/layer_test.h @@ -47,7 +47,7 @@ class LayerTestBase : public CanvasTestBase { }), paint_context_({ TestT::mock_canvas().internal_canvas(), /* internal_nodes_canvas */ - &TestT::mock_canvas(), /* leaf_nodes_canvas */ + nullptr, /* leaf_nodes_canvas */ &TestT::mock_canvas(), /* background_canvas */ nullptr, /* gr_context */ nullptr, /* external_view_embedder */ diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 8e2cd17e05170..a9c6767099aa5 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -3529,7 +3529,7 @@ TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) { fml::AutoResetWaitableEvent latch; context.GetCompositor().SetNextPresentCallback( [&](const FlutterLayer** layers, size_t layers_count) { - ASSERT_EQ(layers_count, 2u); + ASSERT_EQ(layers_count, 3u); { FlutterPlatformView platform_view = *layers[0]->platform_view; From 84c3b0fb58c25d432b43b22526bc6c962c77907a Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 2 Mar 2020 22:24:50 -0800 Subject: [PATCH 10/44] test (delete) --- lib/ui/painting/flutter_rtree.cc | 4 ++ lib/ui/painting/flutter_rtree.h | 1 + .../framework/Source/FlutterPlatformViews.mm | 69 ++++++++++++++++--- .../ios/Flutter/Generated.xcconfig | 12 ++++ .../ios/Flutter/flutter_export_environment.sh | 13 ++++ testing/scenario_app/run_ios_tests.sh | 2 +- 6 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 testing/scenario_app/ios/Flutter/Generated.xcconfig create mode 100755 testing/scenario_app/ios/Flutter/flutter_export_environment.sh diff --git a/lib/ui/painting/flutter_rtree.cc b/lib/ui/painting/flutter_rtree.cc index dd65cfc85cb64..8475c7dea6f16 100644 --- a/lib/ui/painting/flutter_rtree.cc +++ b/lib/ui/painting/flutter_rtree.cc @@ -185,6 +185,10 @@ void FlutterRTree::searchRects(const SkRect& query, } } +void FlutterRTree::getAll(std::vector* results) const { + searchRects(fRoot.fBounds, results); +} + void FlutterRTree::searchRects(Node* node, const SkRect& query, std::vector* results) const { diff --git a/lib/ui/painting/flutter_rtree.h b/lib/ui/painting/flutter_rtree.h index 2f527ac84f0df..0b4203261e377 100644 --- a/lib/ui/painting/flutter_rtree.h +++ b/lib/ui/painting/flutter_rtree.h @@ -20,6 +20,7 @@ class FlutterRTree : public SkBBoxHierarchy { void insert(const SkRect[], int N) override; void search(const SkRect& query, std::vector* results) const override; void searchRects(const SkRect& query, std::vector* results) const; + void getAll(std::vector* results) const; size_t bytesUsed() const override; // Methods and constants below here are only public for tests. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index abcead7c49291..38de1d78c6987 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -448,6 +448,58 @@ sk_sp picture = platform_views_recorder_[platform_view_id]->finishRecordingAsPicture(); + std::shared_ptr layer = layer_pool_->GetLayer(gr_context, gl_context); + std::unique_ptr frame = layer->surface->AcquireFrame(frame_size_); + + layer->overlay_view.get().frame = flutter_view_.get().bounds; + + std::vector rects; + bbh->getAll(&rects); + + SkCanvas* overlay_canvas = frame->SkiaCanvas(); + overlay_canvas->clear(SK_ColorTRANSPARENT); + + auto paint = SkPaint(); + paint.setColor(SkColors::kRed); + paint.setStyle(SkPaint::Style::kStroke_Style); + paint.setStrokeWidth(5); + + + for (SkRect* rect : rects) { + FML_DLOG(ERROR) << " rect: " << rect->x() << "x" << rect->y(); + overlay_canvas->drawRect(*rect, paint); + } + + frame->Submit(); + + platform_view_layers[platform_view_id].push_back(layer); + + + + // std::shared_ptr layer = layer_pool_->GetLayer(gr_context, gl_context); + // CGFloat screenScale = [UIScreen mainScreen].scale; + // // Set the size of the overlay UIView. + // layer->overlay_view.get().backgroundColor = [UIColor blueColor]; + // layer->overlay_view.get().frame = + // CGRectMake(rect.x() / screenScale, rect.y() / screenScale, rect.width() / screenScale, + // rect.height() / screenScale); + + // SkISize rect_size = SkISize::Make(rect.width(), rect.height()); + // std::unique_ptr frame = layer->surface->AcquireFrame(rect_size); + + // // If frame is null, AcquireFrame already printed out an error message. + // if (frame == nullptr) { + // return layer; + // } + // SkCanvas* overlay_canvas = frame->SkiaCanvas(); + // overlay_canvas->clear(SK_ColorTRANSPARENT); + // overlay_canvas->translate(-rect.x(), -rect.y()); + // overlay_canvas->drawPicture(picture); + + // layer->did_submit_last_frame = frame->Submit(); + + continue; + for (size_t j = i + 1; j > 0; j--) { int current_platform_view_id = composition_order_[j - 1]; EmbeddedViewParams params = platform_views_params_[current_platform_view_id]; @@ -470,7 +522,7 @@ } // Get the intersection rect between the current joined rect // and the platform view rect. - joined_rect.intersect(platform_view_rect); + // joined_rect.intersect(platform_view_rect); auto layer = GetLayer(gr_context, ios_context, picture, joined_rect); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); @@ -479,7 +531,7 @@ // Get the intersection rect between the current rect // and the platform view rect. SkRect joined_rect = *rect; - joined_rect.intersect(platform_view_rect); + // joined_rect.intersect(platform_view_rect); auto layer = GetLayer(gr_context, ios_context, picture, joined_rect); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); @@ -501,13 +553,13 @@ for (size_t i = 0; i < composition_order_.size(); i++) { int platform_view_id = composition_order_[i]; auto layers = layer_map[platform_view_id]; - UIView* platform_view_root = root_views_[platform_view_id].get(); + // UIView* platform_view_root = root_views_[platform_view_id].get(); - if (platform_view_root.superview != flutter_view) { - [flutter_view addSubview:platform_view_root]; - } else { - platform_view_root.layer.zPosition = zIndex++; - } + // if (platform_view_root.superview != flutter_view) { + // [flutter_view addSubview:platform_view_root]; + // } else { + // platform_view_root.layer.zPosition = zIndex++; + // } for (auto layer : layers) { if ([layer->overlay_view superview] != flutter_view) { [flutter_view addSubview:layer->overlay_view]; @@ -547,6 +599,7 @@ std::shared_ptr layer = layer_pool_->GetLayer(gr_context, ios_context); CGFloat screenScale = [UIScreen mainScreen].scale; // Set the size of the overlay UIView. + layer->overlay_view.get().backgroundColor = [UIColor blueColor]; layer->overlay_view.get().frame = CGRectMake(rect.x() / screenScale, rect.y() / screenScale, rect.width() / screenScale, rect.height() / screenScale); diff --git a/testing/scenario_app/ios/Flutter/Generated.xcconfig b/testing/scenario_app/ios/Flutter/Generated.xcconfig new file mode 100644 index 0000000000000..7f6ea808ddb56 --- /dev/null +++ b/testing/scenario_app/ios/Flutter/Generated.xcconfig @@ -0,0 +1,12 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=/Users/egarciad/p/flutter +FLUTTER_APPLICATION_PATH=/Users/egarciad/p/engine/src/flutter/testing/scenario_app +FLUTTER_TARGET=lib/main.dart +FLUTTER_BUILD_DIR=build +SYMROOT=${SOURCE_ROOT}/../build/ios +FLUTTER_FRAMEWORK_DIR=/Users/egarciad/p/engine/src/out/ios_debug_sim_unopt +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +FLUTTER_ENGINE=/Users/egarciad/p/engine/src +LOCAL_ENGINE=ios_debug_sim_unopt +ARCHS=arm64 diff --git a/testing/scenario_app/ios/Flutter/flutter_export_environment.sh b/testing/scenario_app/ios/Flutter/flutter_export_environment.sh new file mode 100755 index 0000000000000..fd3c31a7a179c --- /dev/null +++ b/testing/scenario_app/ios/Flutter/flutter_export_environment.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=/Users/egarciad/p/flutter" +export "FLUTTER_APPLICATION_PATH=/Users/egarciad/p/engine/src/flutter/testing/scenario_app" +export "FLUTTER_TARGET=lib/main.dart" +export "FLUTTER_BUILD_DIR=build" +export "SYMROOT=${SOURCE_ROOT}/../build/ios" +export "FLUTTER_FRAMEWORK_DIR=/Users/egarciad/p/engine/src/out/ios_debug_sim_unopt" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" +export "FLUTTER_ENGINE=/Users/egarciad/p/engine/src" +export "LOCAL_ENGINE=ios_debug_sim_unopt" +export "ARCHS=arm64" diff --git a/testing/scenario_app/run_ios_tests.sh b/testing/scenario_app/run_ios_tests.sh index cff055d8d2142..0b43d03f954d0 100755 --- a/testing/scenario_app/run_ios_tests.sh +++ b/testing/scenario_app/run_ios_tests.sh @@ -19,7 +19,7 @@ pushd ios/Scenarios set -o pipefail && xcodebuild -sdk iphonesimulator \ -scheme Scenarios \ - -destination 'platform=iOS Simulator,OS=13.0,name=iPhone 8' \ + -destination 'platform=iOS Simulator,OS=13.3,name=iPhone 8' \ test \ FLUTTER_ENGINE=$FLUTTER_ENGINE | $PRETTY From 3635a706bd5318bbacd9dde86c6efe5ff06cfc92 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 3 Mar 2020 20:37:09 -0800 Subject: [PATCH 11/44] Fix platform view scenarios tests --- flow/embedded_views.h | 3 + flow/layers/image_filter_layer.cc | 12 +- flow/layers/performance_overlay_layer.cc | 2 +- flow/layers/picture_layer.cc | 9 +- flow/layers/platform_view_layer.cc | 6 + .../framework/Source/FlutterPlatformViews.mm | 141 ++++++------------ .../Source/FlutterPlatformViews_Internal.h | 2 + shell/platform/darwin/ios/ios_surface_gl.h | 28 ++++ shell/platform/darwin/ios/ios_surface_gl.mm | 85 ++++++++++- shell/platform/darwin/ios/ios_surface_metal.h | 33 +++- .../platform/darwin/ios/ios_surface_metal.mm | 18 ++- .../darwin/ios/ios_surface_software.h | 25 ++++ .../darwin/ios/ios_surface_software.mm | 67 ++++++++- .../ios/Flutter/Generated.xcconfig | 12 -- .../ios/Flutter/flutter_export_environment.sh | 13 -- testing/scenario_app/run_ios_tests.sh | 2 +- 16 files changed, 315 insertions(+), 143 deletions(-) delete mode 100644 testing/scenario_app/ios/Flutter/Generated.xcconfig delete mode 100755 testing/scenario_app/ios/Flutter/flutter_export_environment.sh diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 030eb88c8a06d..fc476e071c16e 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -248,6 +248,9 @@ class ExternalViewEmbedder { // Must be called on the UI thread. virtual SkCanvas* CompositeEmbeddedView(int view_id) = 0; + // Must be called on the UI thread. + virtual SkRect GetPlatformViewRect(int view_id) = 0; + virtual bool SubmitFrame(GrContext* context); FML_DISALLOW_COPY_AND_ASSIGN(ExternalViewEmbedder); diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index 09b68f7cdf72a..a3e996b1622d0 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -30,15 +30,9 @@ void ImageFilterLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting()); #ifndef SUPPORT_FRACTIONAL_TRANSLATION - SkAutoCanvasRestore save(context.background_canvas, true); - context.background_canvas->setMatrix(RasterCache::GetIntegralTransCTM( - context.background_canvas->getTotalMatrix())); - - if (context.leaf_nodes_canvas != nullptr) { - SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); - context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); - } + SkAutoCanvasRestore save(context.internal_nodes_canvas, true); + context.internal_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.internal_nodes_canvas->getTotalMatrix())); #endif if (context.raster_cache) { diff --git a/flow/layers/performance_overlay_layer.cc b/flow/layers/performance_overlay_layer.cc index 852b1824305f1..ac85e47996097 100644 --- a/flow/layers/performance_overlay_layer.cc +++ b/flow/layers/performance_overlay_layer.cc @@ -84,7 +84,7 @@ void PerformanceOverlayLayer::Paint(PaintContext& context) const { SkScalar y = paint_bounds().y() + padding; SkScalar width = paint_bounds().width() - (padding * 2); SkScalar height = paint_bounds().height() / 2; - SkAutoCanvasRestore save(context.background_canvas, true); + SkAutoCanvasRestore save(context.internal_nodes_canvas, true); VisualizeStopWatch( *context.background_canvas, context.raster_time, x, y, width, diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index d309e8a0d02d8..4b94bdecb3ccb 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -42,13 +42,8 @@ void PictureLayer::Paint(PaintContext& context) const { FML_DCHECK(picture_.get()); FML_DCHECK(needs_painting()); - SkAutoCanvasRestore save(context.background_canvas, true); - context.background_canvas->translate(offset_.x(), offset_.y()); - - if (context.leaf_nodes_canvas != nullptr) { - SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); - context.leaf_nodes_canvas->translate(offset_.x(), offset_.y()); - } + SkAutoCanvasRestore save(context.internal_nodes_canvas, true); + context.internal_nodes_canvas->translate(offset_.x(), offset_.y()); #ifndef SUPPORT_FRACTIONAL_TRANSLATION context.background_canvas->setMatrix(RasterCache::GetIntegralTransCTM( diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 81541b7a0cde8..4d71522b0767d 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -40,5 +40,11 @@ void PlatformViewLayer::Paint(PaintContext& context) const { } SkCanvas* canvas = context.view_embedder->CompositeEmbeddedView(view_id_); context.leaf_nodes_canvas = canvas; + // Don't draw on the area covered by the platform view, since these drawings + // are on an overlay on top of the platform view. This prevent to see drawings + // on the background canvas if the platform view has opacity. + context.background_canvas->clipRect( + context.view_embedder->GetPlatformViewRect(view_id_), + SkClipOp::kDifference); } } // namespace flutter diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 38de1d78c6987..119624dfa675c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -370,7 +370,7 @@ // // The UIKit frame is set based on the logical resolution instead of physical. // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html). - // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals + // However, flow is based on the physical resol ution. For eaxmple, 1000 pixels in flow equals // 500 points in UIKit. And until this point, we did all the calculation based on the flow // resolution. So we need to scale down to match UIKit's logical resolution. CGFloat screenScale = [UIScreen mainScreen].scale; @@ -430,12 +430,23 @@ 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 + toCoordinateSpace:screen.coordinateSpace]; + 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) { DisposeViews(); bool did_submit = true; - CGFloat screenScale = [UIScreen mainScreen].scale; // Maps a platform view id to a vector of `FlutterPlatformViewLayer`. LayersMap platform_view_layers; @@ -448,66 +459,10 @@ sk_sp picture = platform_views_recorder_[platform_view_id]->finishRecordingAsPicture(); - std::shared_ptr layer = layer_pool_->GetLayer(gr_context, gl_context); - std::unique_ptr frame = layer->surface->AcquireFrame(frame_size_); - - layer->overlay_view.get().frame = flutter_view_.get().bounds; - - std::vector rects; - bbh->getAll(&rects); - - SkCanvas* overlay_canvas = frame->SkiaCanvas(); - overlay_canvas->clear(SK_ColorTRANSPARENT); - - auto paint = SkPaint(); - paint.setColor(SkColors::kRed); - paint.setStyle(SkPaint::Style::kStroke_Style); - paint.setStrokeWidth(5); - - - for (SkRect* rect : rects) { - FML_DLOG(ERROR) << " rect: " << rect->x() << "x" << rect->y(); - overlay_canvas->drawRect(*rect, paint); - } - - frame->Submit(); - - platform_view_layers[platform_view_id].push_back(layer); - - - - // std::shared_ptr layer = layer_pool_->GetLayer(gr_context, gl_context); - // CGFloat screenScale = [UIScreen mainScreen].scale; - // // Set the size of the overlay UIView. - // layer->overlay_view.get().backgroundColor = [UIColor blueColor]; - // layer->overlay_view.get().frame = - // CGRectMake(rect.x() / screenScale, rect.y() / screenScale, rect.width() / screenScale, - // rect.height() / screenScale); - - // SkISize rect_size = SkISize::Make(rect.width(), rect.height()); - // std::unique_ptr frame = layer->surface->AcquireFrame(rect_size); - - // // If frame is null, AcquireFrame already printed out an error message. - // if (frame == nullptr) { - // return layer; - // } - // SkCanvas* overlay_canvas = frame->SkiaCanvas(); - // overlay_canvas->clear(SK_ColorTRANSPARENT); - // overlay_canvas->translate(-rect.x(), -rect.y()); - // overlay_canvas->drawPicture(picture); - - // layer->did_submit_last_frame = frame->Submit(); - - continue; - for (size_t j = i + 1; j > 0; j--) { int current_platform_view_id = composition_order_[j - 1]; - EmbeddedViewParams params = platform_views_params_[current_platform_view_id]; - SkRect platform_view_rect = SkRect::MakeXYWH( - /*x=*/params.offsetPixels.x(), - /*y=*/params.offsetPixels.y(), - /*width=*/params.sizePoints.width() * screenScale, - /*height=*/params.sizePoints.height() * screenScale); + + SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id); std::vector intersection_rects; bbh->searchRects(platform_view_rect, &intersection_rects); @@ -522,7 +477,7 @@ } // Get the intersection rect between the current joined rect // and the platform view rect. - // joined_rect.intersect(platform_view_rect); + joined_rect.intersect(platform_view_rect); auto layer = GetLayer(gr_context, ios_context, picture, joined_rect); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); @@ -531,7 +486,7 @@ // Get the intersection rect between the current rect // and the platform view rect. SkRect joined_rect = *rect; - // joined_rect.intersect(platform_view_rect); + joined_rect.intersect(platform_view_rect); auto layer = GetLayer(gr_context, ios_context, picture, joined_rect); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); @@ -544,6 +499,7 @@ composition_order_.clear(); layer_pool_->RecycleLayers(); + return did_submit; } @@ -553,13 +509,13 @@ for (size_t i = 0; i < composition_order_.size(); i++) { int platform_view_id = composition_order_[i]; auto layers = layer_map[platform_view_id]; - // UIView* platform_view_root = root_views_[platform_view_id].get(); + UIView* platform_view_root = root_views_[platform_view_id].get(); - // if (platform_view_root.superview != flutter_view) { - // [flutter_view addSubview:platform_view_root]; - // } else { - // platform_view_root.layer.zPosition = zIndex++; - // } + if (platform_view_root.superview != flutter_view) { + [flutter_view addSubview:platform_view_root]; + } else { + platform_view_root.layer.zPosition = zIndex++; + } for (auto layer : layers) { if ([layer->overlay_view superview] != flutter_view) { [flutter_view addSubview:layer->overlay_view]; @@ -571,26 +527,6 @@ } } -void FlutterPlatformViewsController::RemoveUnusedLayers() { - auto layers = layer_pool_->GetUnusedLayers(); - for (auto 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()) { - UIView* platform_view_root = root_views_[view_id].get(); - [platform_view_root removeFromSuperview]; - } - } -} - std::shared_ptr FlutterPlatformViewsController::GetLayer( GrContext* gr_context, std::shared_ptr ios_context, @@ -599,10 +535,11 @@ std::shared_ptr layer = layer_pool_->GetLayer(gr_context, ios_context); CGFloat screenScale = [UIScreen mainScreen].scale; // Set the size of the overlay UIView. - layer->overlay_view.get().backgroundColor = [UIColor blueColor]; - layer->overlay_view.get().frame = - CGRectMake(rect.x() / screenScale, rect.y() / screenScale, rect.width() / screenScale, - rect.height() / screenScale); + layer->overlay_view.get().frame = CGRectMake(rect.x() / screenScale, // + rect.y() / screenScale, // + rect.width() / screenScale, // + rect.height() / screenScale // + ); SkISize rect_size = SkISize::Make(rect.width(), rect.height()); std::unique_ptr frame = layer->surface->AcquireFrame(rect_size); @@ -620,6 +557,26 @@ return layer; } +void FlutterPlatformViewsController::RemoveUnusedLayers() { + auto layers = layer_pool_->GetUnusedLayers(); + for (auto 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()) { + UIView* platform_view_root = root_views_[view_id].get(); + [platform_view_root removeFromSuperview]; + } + } +} + void FlutterPlatformViewsController::DisposeViews() { if (views_to_dispose_.empty()) { return; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 712375b9bf86a..7997893f780c3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -132,6 +132,8 @@ class FlutterPlatformViewsController { SkCanvas* CompositeEmbeddedView(int view_id); + SkRect GetPlatformViewRect(int view_id); + // Discards all platform views instances and auxiliary resources. void Reset(); diff --git a/shell/platform/darwin/ios/ios_surface_gl.h b/shell/platform/darwin/ios/ios_surface_gl.h index 3cbc6ebd047ad..a00ac47141c56 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.h +++ b/shell/platform/darwin/ios/ios_surface_gl.h @@ -51,6 +51,34 @@ class IOSSurfaceGL final : public IOSSurface, public GPUSurfaceGLDelegate { // |GPUSurfaceGLDelegate| ExternalViewEmbedder* GetExternalViewEmbedder() override; + // |ExternalViewEmbedder| + SkCanvas* GetRootCanvas() override; + + // |ExternalViewEmbedder| + void CancelFrame() override; + + // |ExternalViewEmbedder| + void BeginFrame(SkISize frame_size, GrContext* context, double device_pixel_ratio) override; + + // |ExternalViewEmbedder| + void PrerollCompositeEmbeddedView(int view_id, + std::unique_ptr params) override; + + // |ExternalViewEmbedder| + PostPrerollResult PostPrerollAction(fml::RefPtr gpu_thread_merger) override; + + // |ExternalViewEmbedder| + std::vector GetCurrentCanvases() override; + + // |ExternalViewEmbedder| + SkCanvas* CompositeEmbeddedView(int view_id) override; + + // |ExternalViewEmbedder| + SkRect GetPlatformViewRect(int view_id) override; + + // |ExternalViewEmbedder| + bool SubmitFrame(GrContext* context) override; + private: std::unique_ptr render_target_; diff --git a/shell/platform/darwin/ios/ios_surface_gl.mm b/shell/platform/darwin/ios/ios_surface_gl.mm index b3aa25a4e66cd..78ebdd565fa67 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/shell/platform/darwin/ios/ios_surface_gl.mm @@ -78,9 +78,88 @@ return IsValid() && render_target_->PresentRenderBuffer(); } -// |GPUSurfaceGLDelegate| -ExternalViewEmbedder* IOSSurfaceGL::GetExternalViewEmbedder() { - return GetExternalViewEmbedderIfEnabled(); +// |ExternalViewEmbedder| +SkCanvas* IOSSurfaceGL::GetRootCanvas() { + // On iOS, the root surface is created from the on-screen render target. Only the surfaces for the + // various overlays are controlled by this class. + return nullptr; +} + +// |ExternalViewEmbedder| +flutter::ExternalViewEmbedder* IOSSurfaceGL::GetExternalViewEmbedder() { + if (IsIosEmbeddedViewsPreviewEnabled()) { + return this; + } else { + return nullptr; + } +} + +// |ExternalViewEmbedder| +void IOSSurfaceGL::CancelFrame() { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + platform_views_controller->CancelFrame(); + // Committing the current transaction as |BeginFrame| will create a nested + // CATransaction otherwise. + [CATransaction commit]; +} + +// |ExternalViewEmbedder| +void IOSSurfaceGL::BeginFrame(SkISize frame_size, GrContext* context, double device_pixel_ratio) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + platform_views_controller->SetFrameSize(frame_size); + [CATransaction begin]; +} + +// |ExternalViewEmbedder| +void IOSSurfaceGL::PrerollCompositeEmbeddedView( + int view_id, + std::unique_ptr params) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + platform_views_controller->PrerollCompositeEmbeddedView(view_id, std::move(params)); +} + +// |ExternalViewEmbedder| +SkRect IOSSurfaceGL::GetPlatformViewRect(int view_id) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + return platform_views_controller->GetPlatformViewRect(view_id); +} + +// |ExternalViewEmbedder| +PostPrerollResult IOSSurfaceGL::PostPrerollAction( + fml::RefPtr gpu_thread_merger) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + return platform_views_controller->PostPrerollAction(gpu_thread_merger); +} + +// |ExternalViewEmbedder| +std::vector IOSSurfaceGL::GetCurrentCanvases() { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + return platform_views_controller->GetCurrentCanvases(); +} + +// |ExternalViewEmbedder| +SkCanvas* IOSSurfaceGL::CompositeEmbeddedView(int view_id) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + return platform_views_controller->CompositeEmbeddedView(view_id); +} + +// |ExternalViewEmbedder| +bool IOSSurfaceGL::SubmitFrame(GrContext* context) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + if (platform_views_controller == nullptr) { + return true; + } + + bool submitted = platform_views_controller->SubmitFrame(std::move(context), context_); + [CATransaction commit]; + return submitted; } } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface_metal.h b/shell/platform/darwin/ios/ios_surface_metal.h index e700bc02a3eb2..678644c429d30 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.h +++ b/shell/platform/darwin/ios/ios_surface_metal.h @@ -36,7 +36,38 @@ class IOSSurfaceMetal final : public IOSSurface, public GPUSurfaceDelegate { std::unique_ptr CreateGPUSurface(GrContext* gr_context) override; // |GPUSurfaceDelegate| - ExternalViewEmbedder* GetExternalViewEmbedder() override; + flutter::ExternalViewEmbedder* GetExternalViewEmbedder() override; + + // |ExternalViewEmbedder| + SkCanvas* GetRootCanvas() override; + + // |ExternalViewEmbedder| + void CancelFrame() override; + + // |ExternalViewEmbedder| + void BeginFrame(SkISize frame_size, GrContext* context, double device_pixel_ratio) override; + + // |ExternalViewEmbedder| + void PrerollCompositeEmbeddedView(int view_id, + std::unique_ptr params) override; + + // |ExternalViewEmbedder| + PostPrerollResult PostPrerollAction(fml::RefPtr gpu_thread_merger) override; + + // |ExternalViewEmbedder| + std::vector GetCurrentCanvases() override; + + // |ExternalViewEmbedder| + SkCanvas* CompositeEmbeddedView(int view_id) override; + + // |ExternalViewEmbedder| + SkRect GetPlatformViewRect(int view_id) override; + + // |ExternalViewEmbedder| + bool SubmitFrame(GrContext* context) override; + + private: + fml::scoped_nsobject layer_; FML_DISALLOW_COPY_AND_ASSIGN(IOSSurfaceMetal); }; diff --git a/shell/platform/darwin/ios/ios_surface_metal.mm b/shell/platform/darwin/ios/ios_surface_metal.mm index db345b383ef7b..faad0b68c2fab 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.mm +++ b/shell/platform/darwin/ios/ios_surface_metal.mm @@ -53,9 +53,21 @@ ); } -// |GPUSurfaceDelegate| -ExternalViewEmbedder* IOSSurfaceMetal::GetExternalViewEmbedder() { - return GetExternalViewEmbedderIfEnabled(); +SkRect IOSSurfaceMetal::GetPlatformViewRect(int view_id) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + return platform_views_controller->GetPlatformViewRect(view_id); +} + +bool IOSSurfaceMetal::SubmitFrame(GrContext* context) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + if (platform_views_controller == nullptr) { + return true; + } + + bool submitted = platform_views_controller->SubmitFrame(context, nullptr); + [CATransaction commit]; + return submitted; } } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface_software.h b/shell/platform/darwin/ios/ios_surface_software.h index 86e6b6b209785..2b8cf28add33b 100644 --- a/shell/platform/darwin/ios/ios_surface_software.h +++ b/shell/platform/darwin/ios/ios_surface_software.h @@ -42,6 +42,31 @@ class IOSSurfaceSoftware final : public IOSSurface, public GPUSurfaceSoftwareDel // |GPUSurfaceSoftwareDelegate| ExternalViewEmbedder* GetExternalViewEmbedder() override; + // |ExternalViewEmbedder| + SkCanvas* GetRootCanvas() override; + + // |ExternalViewEmbedder| + void CancelFrame() override; + + // |ExternalViewEmbedder| + void BeginFrame(SkISize frame_size, GrContext* context, double device_pixel_ratio) override; + + // |ExternalViewEmbedder| + void PrerollCompositeEmbeddedView(int view_id, + std::unique_ptr params) override; + + // |ExternalViewEmbedder| + std::vector GetCurrentCanvases() override; + + // |ExternalViewEmbedder| + SkCanvas* CompositeEmbeddedView(int view_id) override; + + // |ExternalViewEmbedder| + SkRect GetPlatformViewRect(int view_id) override; + + // |ExternalViewEmbedder| + bool SubmitFrame(GrContext* context) override; + private: fml::scoped_nsobject layer_; sk_sp sk_surface_; diff --git a/shell/platform/darwin/ios/ios_surface_software.mm b/shell/platform/darwin/ios/ios_surface_software.mm index 06abe29c661e8..5d05f379c469a 100644 --- a/shell/platform/darwin/ios/ios_surface_software.mm +++ b/shell/platform/darwin/ios/ios_surface_software.mm @@ -124,7 +124,72 @@ // |GPUSurfaceSoftwareDelegate| ExternalViewEmbedder* IOSSurfaceSoftware::GetExternalViewEmbedder() { - return GetExternalViewEmbedderIfEnabled(); + if (IsIosEmbeddedViewsPreviewEnabled()) { + return this; + } else { + return nullptr; + } +} + +// |ExternalViewEmbedder| +SkCanvas* IOSSurfaceSoftware::GetRootCanvas() { + // On iOS, the root surface is created using a managed allocation that is submitted to the + // platform. Only the surfaces for the various overlays are controlled by this class. + return nullptr; +} + +// |ExternalViewEmbedder| +void IOSSurfaceSoftware::CancelFrame() { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + platform_views_controller->CancelFrame(); +} + +// |ExternalViewEmbedder| +void IOSSurfaceSoftware::BeginFrame(SkISize frame_size, + GrContext* context, + double device_pixel_ratio) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + platform_views_controller->SetFrameSize(frame_size); +} + +// |ExternalViewEmbedder| +void IOSSurfaceSoftware::PrerollCompositeEmbeddedView(int view_id, + std::unique_ptr params) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + platform_views_controller->PrerollCompositeEmbeddedView(view_id, std::move(params)); +} + +// |ExternalViewEmbedder| +std::vector IOSSurfaceSoftware::GetCurrentCanvases() { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + return platform_views_controller->GetCurrentCanvases(); +} + +// |ExternalViewEmbedder| +SkCanvas* IOSSurfaceSoftware::CompositeEmbeddedView(int view_id) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + return platform_views_controller->CompositeEmbeddedView(view_id); +} + +// |ExternalViewEmbedder| +SkRect IOSSurfaceSoftware::GetPlatformViewRect(int view_id) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + FML_CHECK(platform_views_controller != nullptr); + return platform_views_controller->GetPlatformViewRect(view_id); +} + +// |ExternalViewEmbedder| +bool IOSSurfaceSoftware::SubmitFrame(GrContext* context) { + FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); + if (platform_views_controller == nullptr) { + return true; + } + return platform_views_controller->SubmitFrame(nullptr, nullptr); } } // namespace flutter diff --git a/testing/scenario_app/ios/Flutter/Generated.xcconfig b/testing/scenario_app/ios/Flutter/Generated.xcconfig deleted file mode 100644 index 7f6ea808ddb56..0000000000000 --- a/testing/scenario_app/ios/Flutter/Generated.xcconfig +++ /dev/null @@ -1,12 +0,0 @@ -// This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=/Users/egarciad/p/flutter -FLUTTER_APPLICATION_PATH=/Users/egarciad/p/engine/src/flutter/testing/scenario_app -FLUTTER_TARGET=lib/main.dart -FLUTTER_BUILD_DIR=build -SYMROOT=${SOURCE_ROOT}/../build/ios -FLUTTER_FRAMEWORK_DIR=/Users/egarciad/p/engine/src/out/ios_debug_sim_unopt -FLUTTER_BUILD_NAME=1.0.0 -FLUTTER_BUILD_NUMBER=1 -FLUTTER_ENGINE=/Users/egarciad/p/engine/src -LOCAL_ENGINE=ios_debug_sim_unopt -ARCHS=arm64 diff --git a/testing/scenario_app/ios/Flutter/flutter_export_environment.sh b/testing/scenario_app/ios/Flutter/flutter_export_environment.sh deleted file mode 100755 index fd3c31a7a179c..0000000000000 --- a/testing/scenario_app/ios/Flutter/flutter_export_environment.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/Users/egarciad/p/flutter" -export "FLUTTER_APPLICATION_PATH=/Users/egarciad/p/engine/src/flutter/testing/scenario_app" -export "FLUTTER_TARGET=lib/main.dart" -export "FLUTTER_BUILD_DIR=build" -export "SYMROOT=${SOURCE_ROOT}/../build/ios" -export "FLUTTER_FRAMEWORK_DIR=/Users/egarciad/p/engine/src/out/ios_debug_sim_unopt" -export "FLUTTER_BUILD_NAME=1.0.0" -export "FLUTTER_BUILD_NUMBER=1" -export "FLUTTER_ENGINE=/Users/egarciad/p/engine/src" -export "LOCAL_ENGINE=ios_debug_sim_unopt" -export "ARCHS=arm64" diff --git a/testing/scenario_app/run_ios_tests.sh b/testing/scenario_app/run_ios_tests.sh index 0b43d03f954d0..cff055d8d2142 100755 --- a/testing/scenario_app/run_ios_tests.sh +++ b/testing/scenario_app/run_ios_tests.sh @@ -19,7 +19,7 @@ pushd ios/Scenarios set -o pipefail && xcodebuild -sdk iphonesimulator \ -scheme Scenarios \ - -destination 'platform=iOS Simulator,OS=13.3,name=iPhone 8' \ + -destination 'platform=iOS Simulator,OS=13.0,name=iPhone 8' \ test \ FLUTTER_ENGINE=$FLUTTER_ENGINE | $PRETTY From 39f0bad72a7c124f41ff88e172eb8271096bcb51 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 3 Mar 2020 20:41:58 -0800 Subject: [PATCH 12/44] typo --- flow/layers/platform_view_layer.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 4d71522b0767d..11798ea90e960 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -41,8 +41,8 @@ void PlatformViewLayer::Paint(PaintContext& context) const { SkCanvas* canvas = context.view_embedder->CompositeEmbeddedView(view_id_); context.leaf_nodes_canvas = canvas; // Don't draw on the area covered by the platform view, since these drawings - // are on an overlay on top of the platform view. This prevent to see drawings - // on the background canvas if the platform view has opacity. + // are on an overlay on top of the platform view. This prevent visible drawings + // on the background canvas when the platform view has opacity. context.background_canvas->clipRect( context.view_embedder->GetPlatformViewRect(view_id_), SkClipOp::kDifference); From 62d8d8de636ff4e42bbefcf95bf1b9e590e68906 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 3 Mar 2020 20:58:44 -0800 Subject: [PATCH 13/44] Update golden platform view --- ...tform_view_clippath_iPhone 8_simulator.png | Bin 30022 -> 20295 bytes ...form_view_transform_iPhone 8_simulator.png | Bin 33727 -> 22360 bytes 2 files changed, 0 insertions(+), 0 deletions(-) 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 zcmeHwcT|&E^M8OKAo_x!U=M3Ur36J$dK48E5F5P&8=&-F0|XTdD7#7#=_)EHMd=+x zLFpDiAS56HN=c9wTF7snCzgHB`TqWW|H_`TXHD+Cb7$sG`OMrK_r~#K+DjL&UW~zD zmL55L;3NjaOUGdNl>`^TH??hw$bYal`}Fo6 zEPNI84TJGez+eWgFc_747>uNIT+siF3^$q9@75FpqHGYE>I>p^i zeS@*yaVae)*9%g+<#)*|Y|vONB_*Zidfr;)VJCj)PC1w%=jP++e#TqQ(QPA#$S*nvF1Vd@z3A+I(aBK?p?l`6lZU(d zh7HI@zyIOz>3-4r&rXhRKg9w8@yG~%hr9y*H*Hv|hJ34{?|Sh9?2ORY*rA5b{IBnR z?xTiBHvbnh97)k{K~#;!YWUx3(^#zgO4xKgc26y8F)dWSQ2tgPgUaX+#$}?oUL*9;$5B>ZM0~LO} z!Npo+&NzG~@L{pcMY4CciTz$BN?FGTi+_GzEhSa3$Lb&5e||3s>ZY&!Jz1(5!&6zx z6MFvUpCwqYn%|Q#XsbWLS29#x#aAnty-;)VR zJiNhvd=l@L3;kjV2;TlPnRltEC}mC7;m3!6OG$9{TJn3csBKVCH#O+s&Vz_bIE)7u zpZh(Ts{*bSfK_lEfvWs%@fu+-R`(sjtNDJe%)PVUKriXeVp7dS3g*{8m zuTN#aA^Ja`{q)2#W}G?^+EtvHSvGn#@qU2vDU$dtvLe9kX#cTs@9@lbn#efY}MxW|^?!shf z`E+2t4+F<~sGTOT-ka3uXXN{1U)z^caYdy0yMbeOTgIC*6SuL&1HZHkG8Vct6MRUy z(t&eMY;%vVZ*RuDEFYCj$Xxh9ob9O}l2M=e5UoK~-jdf45z(8v)+ms{VAw)&yBcLi`1OIEd$zkA~rc3=5?{-yU1-YGLkeKKA~<>RW%`jUM6_BVsH zh0aU^8KZ<)pUhZk+I00uOXX!5o1^(onx>?F#c^D3$~Q{k!4}%|n#9=I?2@uMdR2>L z`Si#jZlNkMV~TEJL`|a?^rjCcvd5i{sV6ct1IG6RKAL6G${hwDrs&{3Z}(&xvS&1D zW~?T*PmS(O=6vntmh}8G^>N>1=?fZx1B~Zsx7W=L@83+7M$@P8;=R3PQhL<_4wG*;nn_H%FXT(g6}h(yNFS3e9WGU&O@EapKFcVyc1)7lw=h3L z7pZ!ZNZl))6Fs9oT2WcI-!FdLanM-vhqxQxNN>1Pnx*#{R$Q~??aP`o(#34yvzZc_ zQ|B@p+9!tF54J>?`?KSi8BrCq5J>m-;*BmcMOSD8VuY63mF|bHgDqJK8eTPdRjYPBvRAZ5=l$p29WR4+){! z|GJ=i;=K(I(?(8o57^|*J=E+t8LvU=JAU$JUHfO^Oe8K--ZD(CFSpI|-uK zu>mT7Yi1VHbtg}0=6W@L&MYrWn{8vy)~NK5-p4Tq{Ck|xlTmstZOqBzF)m!ADCItj7P)ib_1wq%^0CY&b-$_8b5rU{3>zv3OBsn@ zPbqzNtA9L!Kcm#s&!*GQCJ%2zVYigep7)seHWtIQ*4)O^|6bcEW1&)WI%aUR$4)hR zZ5$)+-H=8Q>6_M*G%pXwhkI~-%Xr9FgfC+@7WkvjuTAq{!*Q4ms6MfZVzq# zn8B;58{&13Gk(amdE+$Z0|e)u1x~(8!!pj*M0xCv6zkCGmWw{7t67^D$Y!v=H+ob! zc{YAZh*J?>Xm6sYI+nB2zuzfio0rY+A6QMRQQgP-=(ZqNDUA1jI-5eAJr%FKZp2*1 zKc^$B=e$9qXmpZ^e7Lfwy`vRgd|TUc_I#6OCo`_vCrFGE$n^4REDn8U7(W!}Kwt1> z`v?e?6gqZ&AIxOs;@pF0DcK8sD)X*Wtc^IIhaE+epFg=+%BO6r@hX(^`e0lR4K_x_fNubh<n~8xTO%`>H zQ^^U^fGRB};JZGGj723xDOKk$w|CnF^e;5VluM_9=mP;h+2*I_8l1`kL!8U#feTHV zMAldRvRx}E19Se#%z)V^%tj%4N=95)z}UMr)=D$)ab23@B^lCv_j`%6DVon~qjke^ ziF1;-)cslw`Vyb1*P1krZEg}-FuVNBp)IdzkAsTFq-3T=u{=9}RbS{!ov3 zZNyD-XP7irad^Xwqn7ezi{Kybd&@qk)e<+Ak6zX8H!pKdnH@7xwp{P~blxnzFuc7j zvx#+{{%lM#4j<@458R`3aK_a>KYBHZHAG~XKTsT69bYI{vH`UdqZQ7n^#(T<5wp%z zg&f0twj@nu7e90DFX>b;DEl_GhuzP7Rz4qHTwM27fcYrymK4;u>;=)HKFOQhI;i*3S#Xx#f zX>*}{zettW!AEpCIKQU}dwv#kPqnD@~t z>Iyc_MZ{(4^377S*NElnM#Vc^G_@v_Wcn< zF7iIv=pU8c%IwNd_Z_Y5OdIQKYHy=^?l?f)yociaa#oW4MI>$PAjMT_qTQygX=bcv zxU$ZHPxHqOmcftqmwjWH-Nmirg}i18U&}}{g@ukx(z(H6 z;Xb9uxRL;pXS8cfU5#d0`D}Y)mqnNR#~bZ957vAYPGgrxXl;A+gAvM%^*6@@eb46> zSa$sr!%78#CJXapzOhhDqZ57fIE&NE)gAX`c#B-7kaT zzt7{%r`(}FjlRvpQO%0ibq>_-?(3_|dZLeyQCVY>L6#>{Wo!zs{n+c{dT{_RV)snz znxWy7V{+ezd(Rv6JC8TisIb>2-v6l3pIkG_UmJb9F5Z;tYxm~U52M?YuEUWG(+GxF zMEQB8$rJM7GxGzQw3)I)UTv@TcxX1nA_uVuFI6ByoxZuwFw?SYh9{QxjY6Xs=onoY z`->d1SnbDa@%Xw|0fougdmPZ(do$0)9X`7?X5WV|9(iY%q)y(qSZ%ud^5wPE&qy8G zyn&}u-RFho)Qj-VO$0xnzj<`3)5(a&USJCBE^$Gs-J}ap`hE$+gbd3bjVM|9Kop|$Ry zm?&lF&hVKyS5mP64{z_bEW5Uh!3r{8Z=u`NO+>SmFDJGe50|cfqnR-mxGY# z%AATS5lyvk64eN(AV&!a(vJw2IdtYV;~(GIbd!9m$0T7=eMU_x%W zC`DX?)Np@y%hX7xxQgA8po%2^+6z0w)u%oub*@ZA7&YB0RUj=p%VN?PZ70PkxtkQ0 zkOva{GE@TQrxi@UqdUlnBZTJ30_zWdOWVxDTO~S8_bOKnQ}RW604Rfh;e892J|gO@ z+gap^4cR(c${;yi@5(^}1X&5>)Uq5|r|Nn7nPK{BSECVVr-Tq@DpM{Ci2UbacODU3a-uu+Ny6&0d(x2} zM3Mt<@SLcTe_w5+LE-7@%NX(x@dEE3XD5fVn{Lx?w{Ju(XcbVuuH(WeWfm>{Dq5Pm zK|z;{fB>7uxK-9Vr6?h=kU3Gwd4Q8^P}(aWS12~ADb{=XF8eK}d{MW?0sC~pB_`c7 z1Bp0SZIIPIiM z5wysWB1mZ^`R|yFG_rhWn=Xw2!5m$JGwxNeFdZ==!kvm_t!wk6C-0Qz!4cWU26bz6 zufVtE(@4xNKKfa2UhGS(C26wddFw3F@*>m$13dWY)^xLH2G0za7KHW)iM|!{9?5Nc zX*#5X93)~%0wvvzc_J%6k7DoeTL!Wwzpk>?N5&kMA!8qc=Yq&f^`%U@!JXbIHYXrP z+OYx7-#f*NPEqduPlz-@>Qr}k<>i=>$t{Rf5@26^=EDuKdx`_+Fr=k=QfA%tF^VlO z&egnplB|i!bpYh*-cR#rv*26X)eL$OjxgJYnlFZY23a2=K@qraXUn@$%6WRUXW6~0 z+fds!Lqy8R>DC~aYl*MqA@8C2yJ4-*EJjUM0*s0vQ|j1NG#IkRab@$jkj(_EI79h6wfz2S&rHBA~Zt%2jac{V|%_zOiE!p zulDV=t0GhHL12LcfsP)od4p=NhLmxY*vh|n@`KkVzfoB6Yp7xa648n#Tg?O%Uj79k zW;4)>Fsw2Mi%?hqbHlWHcjh@52r*#0u*{0DU(b7dd41zY*FHbAletkEG;4JPFxV7N z*rmg~*h5FUC%%7dc@rwBeer1je&k@z$s=>f{7jyt)pshc3+?UHI4gWs1u+RSm_*P) zDrg#WX!j-_-iMo9K{)IeVM%%OmH$P!w>HBpKVv)F`=^qoGhe8KgH${POW2)pz3%*_ z*SnUJmr06JwEfAt;fM|(Otuv~&4)C1>*1=K81e~;dvS4V`@YAlLD@|Pc6%Q`~%{!ow<<>#SW+Sp^~GCdIe?i2RnO5Ua5B5PhAe|!X}?trjwXIgLOSeWwo zpCwvB1aYhc-jZ_V;{A;%NB;mvAI~nP@7}jMgrEfn5h5FL5!}=&3^^2uQ_h(}9~7{w zH)Oeg{}59DX%`Y=tju9-wc$(iA>Z+aJ#7ki{(>{U2`%4<$ejqV4GRaNRi;$$-dmAu z@l2i%qS)1&vixVp7B#em*7{yl{v<_hM5FBP{^s35PD8Nr$1#(&h7sU=I=&=E(_ z;zv+Nb4;ufqNiOakl_|3Cn{w?P)VAQl*((@`uyVHCk|3s4d>1*w1q8#l%$)jpg&(3 zYBPwKeiEo<;DJL&PHNLK`(r_bQ~Vjl^rFYdx=K)n$tYX>VgJ8{fb8^0x2@bjMcY9K z9)0x)_Gkc8nKRDMwQDn!G0GS|hm94JYCcc)7;4?^GuwX*@y4YPLg$PXm`)77rpMT7 zLig-YChl@+mM|KUmIH_3k*+biIB%uRh;HUq%093_g7aliy@o{HDyGJ${ZXqW*xHnx z;^N}xvZOuH8;tvKmB4&)G}!Ow!cYmhv^Wpk<*PXeVcY8V_bt^SWFN;HM~8zV;y3rS-@`Zmx7GkpX*#z-)`243TwA) zM~2a8QCefLjPi5Kg8ut<+oS8k+8=P1KSh?mD)4d(cUIb{5W*X)Vued^ zvf(lt3Yda6ifCL2?|IO%8QB#viK++BjbA+2&U6776GQ-iT_=F9S^@_n&?f?&0(JWf z;=I^ugYN!Vr}b?X*+g`FKTs73{i}c(WG&8nPBzPDynhE(wHOW34T{gvj~sfIpnT+ao`x;hYIDdn}1z=dDtPjvO@bFZR;4E@drE@*@)Ul+4yq)<@ zPVcE52Iv6-2kVzhQZ8H73gsN`&g*ov`@e^h%zMkce|(V`d10{`!3t-0EGdm_5!Vc1gi=azttORt7HMk8Bdku7Bl1S%uMZ6WJW zUmeV^L-q@X{RoyX&mYN!fL@t_0mf1{5YJiWi zMzk^*vr>RRR~R^pSZ&+f>`GRJE?pn5Ct6nwLW1Ud5bTH1*+z~Mwx^b~i|ojqUzs(B z94v%nk?ZorGPUFB5l|lpM-NJM?=S-7 z23G*p1e7WdZ{yFq&m==oNkyT3`%6OZNdSxT#{>3xynk@e!6u81==wMu%laVqTL>>a zyaqFM3waKmhmjD1A)7)}#5`0)#TL{Rq5weL1WSMmIp8Ab1S%TA>ZsK~WBk7dT%bxr z*%N)b-7CsvIl9CGmJm+0g`B?jso_zW=f#ip(&j}^5*b+@Eo&n7YedQZ^YL_I`Fa#H zihk1p{f;X4a`sxwW79R@?2T4(>Tuu7v#U}aOaMbx=Jf(3AJ=BdVGx4edQmfQ(Iit) z6P5~LnU+5qhS-iT&*YD zxrXql0Pssgq;|iP5MB74cOr)Tw4%$4wyx`dI&ug(5WYp$VkGgFOAtQ|s(1z2|CN#k zYGgBDeKQvQBqQg<=r^%g!3n8u73TV8djR<0o`Q5TA1nqhCUmDE#}|2+2O(Jx7}MsA z`N;RH!jc(=UjVEKy31v`4K!xht>8Rzs8n}e0wx7PL(tryVioh5*f!L!Oki)>uxIM( zfeZ63NBu#I%G&uZ&aml1Cy0 z53dmzW2WOH)Y_;>4A2hdSR26zbZMk}6Y<%kjbJqY;rnUb8CWHUj??KD#d*(65B_Vr zpH^%kOz|0rw`kjr7RFPM7Rb4pepB|G6Dble7|JLvk~xjGAJUOnR)_z|XD-;@?#TU! z#!W$Kvg{Z|8{QT$V5#Q#iu8P{shnBf^Ww7x=$@vCh!ej=%&mBEFfLtwTQcZ4~oc7@+uUVKY7 za@?kyfte^XjvYwJI?s*dU(0`i8bXdXNcw#8-Cb!uR8mpICH**Y<0_E5dcNO>=$t6< z#tibzmg5;N__m^-!!s~Eb6UciF+VfjQs_1yG5x6%#Q3J4L@}uD+sRthf%Im30qF@C zF<;*huat{`%#!2>DbG(P5)l~s4(rWbD%Axa1B&&-mCLU zNZgkJe!F*;k2k7vIFZ_NTs|7QEr?m-AP#75b&YHbo-+vY*9{`jU?19*MKD@n498V@ zVFjA-MFoZJp%gSN@pkc6U4y8p!W*2da#G!M*w|UP5B9$<6wzD;SnTtmH00Qk4n@Fv5}dT=Ty$}6ftYR0y1_9%R)z?1E2wO z%~RAlULXM7_{&4>@TsgwJ>UpVKmY0kMdY{ zH#FKvyv>rmkRfYfVbC}Jy(UH2z=J+L@$AyrCqG(zOaObLm>5he@o~%bW71<(^wIcH z%A37Fd{ip6%UK#M>ZJ4hPMTgk4TCE|_J zM+pq`#ftSc*BncH?u|^K&5&gfpZv_@#3)XMeNpT4^H%Zj-V)s!{pxwwEPd5vd)pHc zmod9mhqXUDrdAQinHUX>_Ef1mUQ~qMie(aZsE@uF zYI~kGsKdal(0ZtrH$Hn%e2C%C?3i>lslTq~SUy*_Eat{V#Lx+kAv``@fX1OO<%2a8 zMGpvt0i-Wc`UVDtyR2WY&!wD}ESzWDOVGBY9OS?`<6&r3Prb<1gAQ8}VW8&j7A2DXH_Zp17yyvkRnXZ z`8oMiQe`sf{`b6MdVIhjdW&eA3aYGmRlYDgM6!1{#fuHups*;pbZ=MDxq;&ayU@J; z9+Kgl@fsV-Tt)Ua@)^OfBewX{(7;#~au4kQ~eEEkO4OG1nfaXZ@V+*Xg zxwV!35b5Op$faZ59NI8MP^NlP1NAGkaI=ZN&>VSL@w)cbtyz~8 z$HI)xf|Sma9^ffb3Xg%gMByP$ljQgl@HT_o7j71FD>Z6K6m`@myO1eE_yVSotsC#| z#kvvA)C!#OV2IVbmyN`u=nKuH7GzEx(NV~!=hamW?H3RPNy5*mrw!_Am@1=hWNN%< zvzC5*U~);D(H$)Uf+;M@+<_crasYpAI6b~TC`)Hd8U%+Hx@Iw_ZB#XMTetn zKLyze@|Fs^H|FU_;8XIxzJRJOYB;qEjmVsw8qA2qKS=mTk=sGS4fDt$mT!?mRyAq64RjBJ&*phx&7^ChPF!CEYd}pan~^Qi20M zxGr;&Pj_jJ?-`#ELsk&yjxAl=$-`RLSTACF@t#Ei=vll7|?SPuo5w^$k-`w)9K zHYPX|KW~}8p-4ZobB*YA;peta&YEt62Pk!LZvw`t1bVrpQ|QH@uGUtBzU{b>Zq9U= z-^`0WBB=XjA}!wzoVsrgr;#j09=og~S5}5t5nu3bS;RVa?ISp6`b16*x1Son9s-T8 zLsl2(=v^AwjCg72O5bCBKrpqaZ4A&F4=y%;Y<;|H**&@DFK|ZtoD)e3-UxD@LfX=q zg^Kjumsj7ekR{)U5?}%dZ}>2ZU}J0tB5lc+VlPI9&w(n?#m43@_{S1_m8AZ|qex0m zM;g$UQ@u6eMouL^4mCVneH*?tn4EgQX9pUGLqVH7vSrzHJ_6FkZ=u@+5YVomDZnxj zs~=t?9lL{0EQS7b?(;6MNM)B-A__B32r}$ICrong1_;ZxGF_a0^a7lfqxu!2r4!sh z+$Kj80}55362Kf%0`d+k3Y-T=E&(b@FLo+NtsaoONVme)WELFg>Wa4^n+Qqiypm1v z7l$%Boe&Lq>m&Cd$)jIF@tk{-=0iN1WuK}I@|$4v9#Y{=J{@Th#+MxLG+wlU(5+6^ zku=qDnf>~sm0r9K&0W1A{Tnud4gsPMlj!gA=tV5_B8mxi z%d>tli0rbo>lj&4yR(oMUrH?=YX5H@)(df_93{USA7Sx4kW2`85NYT51W`pdiKmi9 z3hhW7p_P~W!nM#PTw4&ytZ^2o61V-Q zqR^9S&`@l z4Ta+zY4H~l0_J-R@rCh!l#CO)d-rZ6nA0I8|6;7Ex6WJX_2QIHkH0(Nid(QAZvUGY zCx?og)W&twdsl@Kp7GblUodr#_bcS@uk-WD_kA~_2D=KDz`2U_92TWq3wnOA7<(IM z+pv(J=9;yg-QUP*YQfF&-bHW+X~1;D1!qf)EW2la!dbJx$;eNlH311s6)WJ>WyU^@ zpHViOf~)g3#d{~91C`d;xACq`r;9!lr!jTSFb)wC`aeKUMKoZ#3iqGXyGmHo4b6A5 z>EY^Q$8{t=Iil2B;E10IwM-OtTZ%tABd7~+0xSdmz8Hp+)W$$o9lHHDYs>Vbb{_*Y32OT)r8|n5i$&y_YaJ7BY z@j(KHEF%~;7k!!-P`TOD$T$FpG!8rfY>P&lJL5Lw8IFixIJ|T zk)a6CO7YpWCr>FR{U1_vCYM+*hgQyty+vNKn`=ez>SN7&s$FTr=rz;?#0_SD>qkRr z{eOJk(EB5oo2dUm}E5a#fJt>0T>3+Wv5-=!=bQ?9k9t<3NCw+}_LY21{ zui$gO>2uea{u|l>f%B4$>(NAT7%D{CWYpYWwsy0zT&-mEpb(-jOKG|myeg$-s@BG%D-B&_)fUc#8w5LvEvUJJKerh;N=jz z9Bu)|I*|_8IXUE&1OpnJr$*q$otbc~`_r9@3kd1EHsQpn0CDQx=`pv3v$QK$cWQ&S zHUV{^Zz+AuayIKxV~3dSjS4bDX=h2`h$f3BXChUMw!lt6I^1q}ip4BrmfkTGp=Tbqw6;H*mcU27hQ$-U*W_{Z5^KxALKL?GJHcV z*wVJ({V3qk_fZ{v0|CpIA`Vx}NGBOCjgeCcHzNs}}& z_=Yd7z zTO0e7fuqO}2UcW&!w~gs1rs$FrhB!%J>ZANl-NC$xrM=p1|IH=$l5{HbGK5K)BGUY zfN>9jG8pm;0O;CQ7e#Oy=3qImvlMve>=$DXolFvcBpp7Sx`nBJQg6Mc!ss^oEM2Q5 z_0*F^RBcX)D+0V7X#Jr|n`yjW|9Te$EtTk|)7KJW7LT5?>8^{n9Sn%b9$}B++SJp2 zAo{WjgHX53#2l7MyxJvLFR`_)JX&!yQ_Wm2N{UG5BYx~{u7g*oBkcwsZ9~xVK}k^7 zvFe!88Jx)4y~p7)J+q_8!%qCM-EC~g=Hrb8(KJ%ZWH40T;@X+I zlU>@l1SfX}5+E^TeqfjXVhFqPVYr4V>lJ?f)##d`!$AZmsMw~*^p(YlhtQ&4$4H-l z_J-JC-T0WLPedCdKAAnpo@1BHC9n}R%)`q=zHuylji5jUG)Π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 zcmeIbi96Km8$ZrOS+b?5WND*_Q7UDfQlzq_2pOERlo9|u!C1b}%;=oY_qu-n!1s4K*LAMLc)y?Je(vRUzwY~)^F&`)i-&7J7Yhpu zkM@;IH&|F8Bo-F-vmD#OC-ry*`hVDLFX&ugVabo=UbSKezf0V`azlrO#aEJrB`Ac2 zWd(c`G{M5+A3qe+jfF+%DE(hn?HfmCSy)(2?%%xa zep~0d(p@J9Ijeh4|5(d;J2-=6ul0zcC~h8~h& zJmPMzdg!)}zJ!L8tF^=#xzlp;ht#+vBqUT^@7XBbxTLxHI`~cXke$1`vl0~Q<>e*k zr6A|zY70Gi{``5U{3+?&$=dC%>wRbU`%aD$^l`2J zaq@6iJ#>gZ(ZByN*Xe%W=D(R7-8Q!cHVCEv1$t6W9{TUt;8hj+r%HOR_pQOq^zqeB zsxY4XzdqZXM+His{C^h1+$rNzuvIlK73jZWQ{%dhl`>&rfw5>`x^UB*b-ItU{DAd4 z=m2@--a#v!i?DmzEYNe(kGY$Jxa?q_d+rHfI9@;w9deGoutU_tvWA@v!&QH|=E6PU z-3D5hx_V*k2HOQ7S1eyP@n5`hk)y8ETomoPGI9cQ(*tk3wm?o#&nSyQB~pV|mv5w_ zp&|16%Spnl5H=2e30M#dD}-HWukk?l8%_Gp%nz%$A&<&Z7=L7ZAOT_hrus79GR?r^~k@kNR)_d`*#AiL;U=`hwWbP;{5j&4pPm(W3eDP*x0BX z?>}AoZ%vQNPX0R0;cuxP)10;p?_VFX<#x85?pDaz45M2CcMEPX5z`jf+Jc%} zK%1G&Y^520Q@X7rYb(XvO0fS@9$WOnzhuuAxw1vBY>_Kl3Vr`39`+t~NOAdy_ntPh;z>VExo+IoXD7K#RXe-)n3bTW{BX(;sQUNY# zi}4rd-V4WLEnHx5~iE|!#MJsleEc=Tk!i43A)ygBh zIB&9Ra!=Q7C;N@FTK#xBBK|QtH`jkA+-havN4rE)xm)*reAqjMB;Ucdnt-<+PK|X& zj6Edd!Fj=nB}9iSmRVH+t8<8u%5{&Kh1&?Szuvj&JoQgR#a?-tBMNhM4YCTyBiNZm z^kg1ZRDB`R7VpaSY)^NTg_`JrZk+l?nMaDML6>#x4(v!B&KTLO;uh|Du~d5VosVE& z%Jnn%E(00liMxC^tA4~D_3PTXvA6vp30{eDIeg}~E6Sk;+%xRReC*s{c%|wIxXQuQbE9vF*fpviPygyzCRR)BE25g3i%Tejdrw~5h!LxW zY?4~ZCg6CRo_1B7hI|aASgbv0n>vAzE!0a`d3ffN6K1hIZ#({R6aYB z-(|zSzuR#_^JDutcQfLqmX@-!QC+F{cAmp8Lk9C9b1#(D-jIB1_TfX^noE_;^@QX~ z2FltO`&Vs-<-JH~kEC`ZHA=p@eS}s08|z8e0`9x*-P3aq^_Zggeq5rYwoCCA>R8Kl z4&8v1)64ymaVo0QU4(_S9U0^#?;$g+_(q8px(PPgK^YN>8;N;8;_=&{@|t?V-uYHb>_BnF7F;74ydNmY!N5rPdbTRu&UDoQCbkZ9~ z{Pf{N?9?~SOrcI_gO{ax2qMCLPg>M&y4JnsjE2HZ*8(UhxS+~etPD1UQzne0Hin*6 zlYGU)w9ETIfWL~1y`WhlNdF@<;dW$VuEWs=kDW$;=U8>R+GPOQ7bL{=JupiRxX=RF2vey=A;u=;EYv!S5KMyzJk#9 z*-rC2R`^7{p*lFx+`Oq}5*P+HJ-5baywO(nJ-S3tBp& zY}H#*|L35aHz)agveFTT{v68-GPCCDJ&^ibPTtgVV@TL5OGV2cauf;6kFuBTSA&Mdyq?hxxJ{pO(DZJmWlKOFAujAsc~--w$u(U^ zs1p`ZrF5E3B7wfu%isSs@2*M=<@2}8on0EXPYYMwSS9n&j{O{t>_5D^m}{Tjli*sv zXhqE4h{-{fNtB~x>;f4Iz#kqoAf}b%TT$N%q4jy`@g=Q}#A%&Yn)=)y+EuV|YG^z= zK))_;+si^<2z7^V zA8OOv(C&O#z5AxTx829kXr(}vrR)cvH)g}BSrxI?I#`5g!5*e@D6#5AA2+|&;Bw(`C}o8E`D^Ie z{j2DS4>Cq^i#h%kHT!XRq3YL11;a2qRQr;jhmJPXwu1*_4R=WEA^k>P6=}Fe(qbzL zrOPWPF5_+n`kYw?cGST^%%(mRu`QO_43{#HVc*e<=C%hQ+Lq$S3#E*mf9!!?gWii& zC1pQEEI&?B?VAk^1yTuJN=Ygz3#dB^)#H2h`;Cf;gePI`9B0IO);C=j6<8jL3S)Bm02kZ~xb3|GZFkjGQ`K7kG1L zfa;&d@AZNA%4OFcI-6^tM9VEQ8%Q2bx0(Z(>fQ;WOGIDN4dYlgipoXW@_6N`oxHVe z=}=zaF+_ng9mlFtujd9(z6%0IhP7vj7$=#;DM;hK5XHOXhjqgGVJ zNUXZV(BllTizJm4e?V;(8QuS3r4+Yz`eFW$)hQn#+4p-1G(xljsYq#i63te<=TN*` zvpse!PJNFWwbWiFGFv4N-BqhbiI|--R=?4c`X$MxFK+1TNyf21z6jdjKIn`dKNVS( zIN>)Jp&Vl7XtKziTq_>v*6C6k9u}%R^3p40RL<+*niG zffs7Nqu2c%g1;%u7g0@@kC&r9*oZR?!TKg-Dx^z2Xxify5;ZRaI@$= zQWE)ddK=SGKL?IlC-bR=9dLCCUd^8y;p7`TVn=0$|JF*0;~St#P(Jw<5=}py{j`ztD-dH<$bC zq4p0()h{-d<6oH`WMC2lPJoVY&Pc=aFYlZwdKqu$P_eu^yNuf?ly`m}LaAJ}eqe5w zq~c?84g{%GJoroH!CqgY<|O(r990B?gfQVYW^D4k3SRaTLwPS4u<&tXk6ig|eLX5o zE*E3|IH_`dG0iGmG;HIa&!4KIbh~9&N1?>_cJ#fv5cFb{?#kspUj-TM{zkOoVH-21 zx*kBf{5XF3Xg^#V;o=bIla>nC6r@cH}we+O$Mf9{(Il0iNrtFHfDq zKos7kcINza0AZ8j@YSkPC1Ki##kqwONX!g(<1)*(ZT=~_PL@RlM`d_K=d;hnoKA{5FU^c0`dk3{ zq}tt86FyitKD<(p=WScEgObU6mU&1qom%s2*SMQM}ktE z(usmna(w;kl;}y{GHxuQ>=8wZvEl^qqVn*8A0})R5^8cRPLb0#K3~K1XNMV%J3Zdj z++!^=uvZ=g1>N@1eF{S1z*4Nx=E~fU5X!!kqs$=$!4S6Q6AGL&eNMP)zFa}wb5_*C zm#=I_?5VYZ^HGXn4#hqyeH97pgBVd&+Aaq8Xy)J`CAi2HX^0sVlj_z>G@q{+^2ssjs2$>ZgvD+a8@f$A~o&;;a@`ZzAn?YwhWdXiK{sA!($6 zxY25tqPncI?3y-G^w1=5F@57O^F(|#fWbdk!<$?0>I|hQz0S#9U8h1bI^;d3UZ1qr zwi*57y-(2Rvyvp^V1DYtoN>*ko*w43{qfLnCeBc?->+RU?ZwSWy+HJjE8;#$n<;62 z&;WdBjJM)+!F2GMr>Jsb4%IjUJyZO==0py~Aya&%#i^#ZxQk~s+`QnQ;!RmI1l3oh zzhC3`C%n`EnH37l~7(6J&qwC zs8~o9$2;Jso=PZou@0-jmu=RULmA72FWQYRlhkvwZRQQVaUrc2nCXQifXR~2 z`qUp+5!S@qzdri-qFb>ssj?<7U1jRCS@m?6OkkHsC(nCtLuV&QLmE>7Y(dAm?SSq9 zxE(pf^<=XP-kcb&=Rc)$|74t#n))iSlE5#iH#`yjQR_J-0m4)$S0E*sr6pW1rRGL4UTr zPRv(%fvJhY9I3k@!0s^x8!fZ}D0_Z)WazF!^a(q?4V#GRh#Yi<*JtlU;}62=65$3Z@MJg3>=M4?x+(wKs*S%)}b zjkHFT8;t8;h2M5-vTmooQ1!$uM8v-@Rj(igX&A9l4@wv@Xc<^I*b(mqMc@5MjKT39 z^MK_B>}YJIN@%A8iiQ=?>W#_43q3&Lri**lqH#{yn7$6Z!T*w%81QyX=X{s89j>p{jP4}jzh- zjN>}|fA?1&I$=>U5A7=M8k?J~(<9ub`ee#HLS(v9n7*!wlY`XcQ&dz`MG#6#5aIWp zNbz7WEPXM-fd@S-lc5LvW?9>DQSYdz$~BxvPciyXIqI)lXekLAAe7u{Fyoq*ZR^VC zMC$nsH4P>r($W#D4VArn;d4)G+dzJHH!^$A>+v$F+;1rr6rW7>P^!`BG?QoCFF1Si zT}`|x;i{d}yXF@>Ty&T)_$Mh)1HGszBD>RKeLk{nF@;;iajCHdHPsP$-iDml=NV~3 zX{4fq&6uH#%~S}C5;LjUf0A?hB5gubtx0o0O5I1>H@Qu>%=33kqT@L%_PJs+m!8VW zqjwN9LI-`;5em>@%s$y3W^5S)QXFfl>D36@2l&c;T#T@pi|DIv?}5KguX(nZ;U6{C zGDFV@nw9KnQ$5OL6~*gs^ZX_x@8*cm?(XVhCLM(*2E?c_lScgpb|!t_m467lRj~C1 zcBYul?!QJ<=V(il)yD7GvEoVc_sT_FeSl>8X=#ZP+waEq3=&w&pzQ9@;-!1X`N*w3(;X3b14ha5Efi<1lNl$(F zwXSXOnL>^>J}NXbmmtkEGoG}8LF2ME^SK`a^1}p8Xb9!(?DYGxs=rj5DlSwQUfS(X zzfhU)aQC0RI8#A~_MscAxM|y-Q0lkt7^Wp7=u|LP&yv0ybHSym9Rac1 zsS(hMLkra0y~Q!Wr>C#1m$+;z4WU@+P3bVaAS~G$Kp>u9oHINZyR!Hpda6a5x1BJy zT3MqXB8oZtX@qN;Nw#whpARQ~o=2KcTiyMbmzeg0wbthC>a1m%cX5a4sjD6@-&m6& z+iy(U1_~AT&e!$kUhu{xbaK+IIC%#fprWrvg;ETh{dQ-=WD9YL{(%|*D*TgnP0r_X z$z<9)tGd^7gQL*ELw-|+ikA>;T+C>l&-K=DbI)iUa4|ba>BP_t^ZZb#4j@RETEZPUdmjE-dfcfGERip2TZ#5#{BD1@mlFU`qd0qQ(nzL@P5ey{WuJ|&za9wRry z*T2%?RlVO{rT?eg7B;OxVUZ+p|P9P-9@a zT(e|bn_hNG{*GW-j-7kXkxEptha~SjNM|_J8EReC2}$K8L1>-Zs)X3WxDI&yR*SYp>*@CE zR(w9bj7=+tV-fJLFRsxgY%O?fCi~PyL|0e_vCeGZy*+$avoiVCy2ZLxh0ttUQ{j($y^NF$S|QGk^CgIy{7l-+?i@$qN+%7 z52^J!^mi5_EuNQQ14s89({UE=yry)~+$aZPn}qQ%84NR-Jnz}JxDHAbs5D|-?1`fm z#ix7ir+yzDjCTu=@70pUNR^|$+ep!^Z{i^G2Afw3CR{V?TGO3miuKjlJM#(hj(4md ztcBuR^pnalO=ly+cZlCnr0YDoz3~1uEWR>P$6L79B8VEy@N>^3_&vZXv?el8oTm@Y z##UwtpY31$f>za4^YH|p(sYIJ=6C}(aDANmYw<6oh>uiTK@I9S`AV#1{ZBps%0D$f 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 HeDr?+Nh376 From 11242e9b5bfef846004fef87083d7fe22409deb2 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 3 Mar 2020 22:58:21 -0800 Subject: [PATCH 14/44] Fix FlutterRTree bug --- lib/ui/painting/flutter_rtree.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/painting/flutter_rtree.cc b/lib/ui/painting/flutter_rtree.cc index 8475c7dea6f16..483072d2f0009 100644 --- a/lib/ui/painting/flutter_rtree.cc +++ b/lib/ui/painting/flutter_rtree.cc @@ -215,7 +215,7 @@ void FlutterRTree::searchRects(Node* node, std::vector currentResults = *results; // If the current record rect intersects with any of the rects in the // result, then join them, and update the rect in results. - for (size_t j = 0; !replacedExistingRect && j < results->size(); j++) { + for (size_t j = 0; j < results->size(); j++) { if (SkRect::Intersects(*currentResults[j], *currentRecordRect)) { currentResults[j]->join(*currentRecordRect); replacedExistingRect = true; From fcaa14234b9b2b44cc83dbcc6fa6ef2b03a65e5c Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 3 Mar 2020 22:59:25 -0800 Subject: [PATCH 15/44] Clip platform view in the background canvas --- flow/layers/platform_view_layer.cc | 11 ++++++++--- flow/layers/platform_view_layer.h | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 11798ea90e960..178cfc4124ab2 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -21,6 +21,7 @@ void PlatformViewLayer::Preroll(PrerollContext* context, "does not support embedding"; return; } + matrix_ = matrix; context->has_platform_view = true; std::unique_ptr params = std::make_unique(); @@ -40,11 +41,15 @@ void PlatformViewLayer::Paint(PaintContext& context) const { } SkCanvas* canvas = context.view_embedder->CompositeEmbeddedView(view_id_); context.leaf_nodes_canvas = canvas; - // Don't draw on the area covered by the platform view, since these drawings - // are on an overlay on top of the platform view. This prevent visible drawings - // on the background canvas when the platform view has opacity. + // Don't draw subsequent drawings on the area covered by the platform view, + // since these drawings are render on an overlay on top of the platform view. + // This prevent visible drawings on the background canvas when the platform + // view has opacity. + context.background_canvas->resetMatrix(); context.background_canvas->clipRect( context.view_embedder->GetPlatformViewRect(view_id_), SkClipOp::kDifference); + FML_CHECK(matrix_ != nullptr); + context.background_canvas->setMatrix(matrix_); } } // namespace flutter diff --git a/flow/layers/platform_view_layer.h b/flow/layers/platform_view_layer.h index 242b3734dd3b1..54baeed4d2b52 100644 --- a/flow/layers/platform_view_layer.h +++ b/flow/layers/platform_view_layer.h @@ -22,6 +22,7 @@ class PlatformViewLayer : public Layer { SkPoint offset_; SkSize size_; int64_t view_id_; + SkMatrix matrix_; FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewLayer); }; From 2b27ed65b0fc9dfa3b8c2caca26fa689abf0efed Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 3 Mar 2020 23:00:13 -0800 Subject: [PATCH 16/44] Add GetPlatformViewRect to EmbedderExternalViewEmbedder --- .../embedder/embedder_external_view_embedder.cc | 14 ++++++++++++++ .../embedder/embedder_external_view_embedder.h | 3 +++ 2 files changed, 17 insertions(+) diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index 5e77073e7ff47..f8c71bbbfbf4b 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -92,6 +92,20 @@ SkCanvas* EmbedderExternalViewEmbedder::GetRootCanvas() { return found->second->GetCanvas(); } +// |ExternalViewEmbedder| +SkRect EmbedderExternalViewEmbedder::GetPlatformViewRect(int view_id) { + auto found = pending_views_.find(view_id); + if (found == pending_views_.end()) { + FML_DLOG(WARNING) + << "No root canvas could be found. This is extremely unlikely and " + "indicates that the external view embedder did not receive the " + "notification to begin the frame."; + return nullptr; + } + auto size = found->second->GetRenderSurfaceSize(); + return SkRect::MakeXYWH(0, 0, size.width(), size.height()); +} + // |ExternalViewEmbedder| std::vector EmbedderExternalViewEmbedder::GetCurrentCanvases() { std::vector canvases; diff --git a/shell/platform/embedder/embedder_external_view_embedder.h b/shell/platform/embedder/embedder_external_view_embedder.h index 7000d2cde04cd..9ce5cce6b8ca0 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.h +++ b/shell/platform/embedder/embedder_external_view_embedder.h @@ -94,6 +94,9 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { // |ExternalViewEmbedder| SkCanvas* GetRootCanvas() override; + // |ExternalViewEmbedder| + SkRect GetPlatformViewRect(int view_id) override; + private: const CreateRenderTargetCallback create_render_target_callback_; const PresentCallback present_callback_; From b7c2bc620cf9c5a416ee6bf9fd5b50e483852f8c Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 3 Mar 2020 23:16:44 -0800 Subject: [PATCH 17/44] Remove matrix check --- flow/layers/platform_view_layer.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 178cfc4124ab2..72737f810ae06 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -49,7 +49,6 @@ void PlatformViewLayer::Paint(PaintContext& context) const { context.background_canvas->clipRect( context.view_embedder->GetPlatformViewRect(view_id_), SkClipOp::kDifference); - FML_CHECK(matrix_ != nullptr); context.background_canvas->setMatrix(matrix_); } } // namespace flutter From 71ba25c41215862f699f17fb7537ade820fa426c Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 4 Mar 2020 00:11:10 -0800 Subject: [PATCH 18/44] Return empty skrect --- shell/platform/embedder/embedder_external_view_embedder.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index f8c71bbbfbf4b..4b4127bee6952 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -100,7 +100,7 @@ SkRect EmbedderExternalViewEmbedder::GetPlatformViewRect(int view_id) { << "No root canvas could be found. This is extremely unlikely and " "indicates that the external view embedder did not receive the " "notification to begin the frame."; - return nullptr; + return SkRect::MakeEmpty(); } auto size = found->second->GetRenderSurfaceSize(); return SkRect::MakeXYWH(0, 0, size.width(), size.height()); From 70029298da3182ac6a0acd8ab6a488ba8907f5a6 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 4 Mar 2020 15:01:33 -0800 Subject: [PATCH 19/44] Test --- flow/layers/platform_view_layer.cc | 10 +++++----- shell/platform/embedder/tests/embedder_unittests.cc | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 72737f810ae06..66b550eaf0242 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -45,10 +45,10 @@ void PlatformViewLayer::Paint(PaintContext& context) const { // since these drawings are render on an overlay on top of the platform view. // This prevent visible drawings on the background canvas when the platform // view has opacity. - context.background_canvas->resetMatrix(); - context.background_canvas->clipRect( - context.view_embedder->GetPlatformViewRect(view_id_), - SkClipOp::kDifference); - context.background_canvas->setMatrix(matrix_); + // context.background_canvas->resetMatrix(); + // context.background_canvas->clipRect( + // context.view_embedder->GetPlatformViewRect(view_id_), + // SkClipOp::kDifference); + // context.background_canvas->setMatrix(matrix_); } } // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index a9c6767099aa5..8e2cd17e05170 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -3529,7 +3529,7 @@ TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) { fml::AutoResetWaitableEvent latch; context.GetCompositor().SetNextPresentCallback( [&](const FlutterLayer** layers, size_t layers_count) { - ASSERT_EQ(layers_count, 3u); + ASSERT_EQ(layers_count, 2u); { FlutterPlatformView platform_view = *layers[0]->platform_view; From 9f7f91cd925bd1596165490f693e88d40123cecb Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 4 Mar 2020 21:15:49 -0800 Subject: [PATCH 20/44] Fix tests --- flow/embedded_views.cc | 3 +- flow/embedded_views.h | 2 +- flow/layers/child_scene_layer.cc | 5 +-- flow/layers/image_filter_layer.cc | 7 ++-- flow/layers/layer.h | 3 -- flow/layers/layer_tree.cc | 4 +-- flow/layers/opacity_layer.cc | 9 ++--- flow/layers/performance_overlay_layer.cc | 16 ++------- .../performance_overlay_layer_unittests.cc | 4 +-- flow/layers/physical_shape_layer.cc | 16 ++------- flow/layers/picture_layer.cc | 21 +++--------- flow/layers/platform_view_layer_unittests.cc | 2 +- flow/layers/shader_mask_layer.cc | 10 ++---- flow/layers/texture_layer.cc | 2 +- flow/raster_cache.cc | 1 - flow/testing/layer_test.h | 3 +- flow/testing/mock_layer.cc | 2 +- lib/ui/painting/flutter_rtree.cc | 33 ++++++++++++++----- shell/common/rasterizer.cc | 7 ++-- .../framework/Source/FlutterPlatformViews.mm | 23 +++++++++++-- .../Source/FlutterPlatformViews_Internal.h | 4 ++- shell/platform/darwin/ios/ios_surface_gl.h | 2 +- shell/platform/darwin/ios/ios_surface_gl.mm | 5 +-- shell/platform/darwin/ios/ios_surface_metal.h | 2 +- .../platform/darwin/ios/ios_surface_metal.mm | 4 +-- .../darwin/ios/ios_surface_software.h | 2 +- .../darwin/ios/ios_surface_software.mm | 4 +-- .../embedder_external_view_embedder.cc | 3 +- .../embedder_external_view_embedder.h | 2 +- 29 files changed, 93 insertions(+), 108 deletions(-) diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index c660f4691318b..894abe1299dd2 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -6,7 +6,8 @@ namespace flutter { -bool ExternalViewEmbedder::SubmitFrame(GrContext* context) { +bool ExternalViewEmbedder::SubmitFrame(GrContext* context, + SkCanvas* background_canvas) { return false; }; diff --git a/flow/embedded_views.h b/flow/embedded_views.h index fc476e071c16e..14c9daba9b1f2 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -251,7 +251,7 @@ class ExternalViewEmbedder { // Must be called on the UI thread. virtual SkRect GetPlatformViewRect(int view_id) = 0; - virtual bool SubmitFrame(GrContext* context); + virtual bool SubmitFrame(GrContext* context, SkCanvas* background_canvas); FML_DISALLOW_COPY_AND_ASSIGN(ExternalViewEmbedder); diff --git a/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc index b17d10b4b819c..4a5358053928f 100644 --- a/flow/layers/child_scene_layer.cc +++ b/flow/layers/child_scene_layer.cc @@ -39,10 +39,7 @@ void ChildSceneLayer::Paint(PaintContext& context) const { SkPaint paint; paint.setColor(SK_ColorTRANSPARENT); paint.setBlendMode(SkBlendMode::kSrc); - context.background_canvas->drawRect(paint_bounds(), paint); - if (context.leaf_nodes_canvas != nullptr) { - context.leaf_nodes_canvas->drawRect(paint_bounds(), paint); - } + context.leaf_nodes_canvas->drawRect(paint_bounds(), paint); } void ChildSceneLayer::UpdateScene(SceneUpdateContext& context) { diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index a3e996b1622d0..164fb5077465c 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -36,14 +36,11 @@ void ImageFilterLayer::Paint(PaintContext& context) const { #endif if (context.raster_cache) { - const SkMatrix& ctm = context.background_canvas->getTotalMatrix(); + const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix(); RasterCacheResult layer_cache = context.raster_cache->Get((Layer*)this, ctm); if (layer_cache.is_valid()) { - layer_cache.draw(*context.background_canvas); - if (context.leaf_nodes_canvas != nullptr) { - layer_cache.draw(*context.leaf_nodes_canvas); - } + layer_cache.draw(*context.leaf_nodes_canvas); return; } } diff --git a/flow/layers/layer.h b/flow/layers/layer.h index 074eebfd22e00..63c7cdb316d7e 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -114,11 +114,8 @@ class Layer { // and applies the operations to all canvases. // The leaf_nodes_canvas is the "current" canvas and is used by leaf // layers. - // The background_canvas is the default canvas used by all leaf - // layers. SkCanvas* internal_nodes_canvas; SkCanvas* leaf_nodes_canvas; - SkCanvas* background_canvas; GrContext* gr_context; ExternalViewEmbedder* view_embedder; const Stopwatch& raster_time; diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index 5f07bb913d0fe..ca24a330a6593 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -115,7 +115,6 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame, Layer::PaintContext context = { (SkCanvas*)&internal_nodes_canvas, - nullptr, frame.canvas(), frame.gr_context(), frame.view_embedder(), @@ -170,8 +169,7 @@ sk_sp LayerTree::Flatten(const SkRect& bounds) { Layer::PaintContext paint_context = { (SkCanvas*)&internal_nodes_canvas, - nullptr, // leaf node canvas - canvas, // background canvas + canvas, // leaf node canvas nullptr, nullptr, unused_stopwatch, // frame time (dont care) diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index cfff1455f718d..e6ca90a66a42f 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -88,18 +88,15 @@ void OpacityLayer::Paint(PaintContext& context) const { #ifndef SUPPORT_FRACTIONAL_TRANSLATION context.internal_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( - context.background_canvas->getTotalMatrix())); + context.leaf_nodes_canvas->getTotalMatrix())); #endif if (context.raster_cache) { ContainerLayer* container = GetChildContainer(); - const SkMatrix& ctm = context.background_canvas->getTotalMatrix(); + const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix(); RasterCacheResult child_cache = context.raster_cache->Get(container, ctm); if (child_cache.is_valid()) { - child_cache.draw(*context.background_canvas, &paint); - if (context.leaf_nodes_canvas != nullptr) { - child_cache.draw(*context.leaf_nodes_canvas, &paint); - } + child_cache.draw(*context.leaf_nodes_canvas, &paint); return; } } diff --git a/flow/layers/performance_overlay_layer.cc b/flow/layers/performance_overlay_layer.cc index ac85e47996097..b53f4324a47ff 100644 --- a/flow/layers/performance_overlay_layer.cc +++ b/flow/layers/performance_overlay_layer.cc @@ -87,26 +87,14 @@ void PerformanceOverlayLayer::Paint(PaintContext& context) const { SkAutoCanvasRestore save(context.internal_nodes_canvas, true); VisualizeStopWatch( - *context.background_canvas, context.raster_time, x, y, width, + *context.leaf_nodes_canvas, context.raster_time, x, y, width, height - padding, options_ & kVisualizeRasterizerStatistics, options_ & kDisplayRasterizerStatistics, "GPU", font_path_); - VisualizeStopWatch(*context.background_canvas, context.ui_time, x, y + height, + VisualizeStopWatch(*context.leaf_nodes_canvas, context.ui_time, x, y + height, width, height - padding, options_ & kVisualizeEngineStatistics, options_ & kDisplayEngineStatistics, "UI", font_path_); - - if (context.leaf_nodes_canvas != nullptr) { - VisualizeStopWatch( - *context.leaf_nodes_canvas, context.raster_time, x, y, width, - height - padding, options_ & kVisualizeRasterizerStatistics, - options_ & kDisplayRasterizerStatistics, "GPU", font_path_); - - VisualizeStopWatch(*context.leaf_nodes_canvas, context.ui_time, x, - y + height, width, height - padding, - options_ & kVisualizeEngineStatistics, - options_ & kDisplayEngineStatistics, "UI", font_path_); - } } } // namespace flutter diff --git a/flow/layers/performance_overlay_layer_unittests.cc b/flow/layers/performance_overlay_layer_unittests.cc index 52989a4bd04e8..6f383fc8716c2 100644 --- a/flow/layers/performance_overlay_layer_unittests.cc +++ b/flow/layers/performance_overlay_layer_unittests.cc @@ -63,8 +63,8 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { flutter::TextureRegistry unused_texture_registry; flutter::Layer::PaintContext paintContext = { - nullptr, nullptr, surface->getCanvas(), nullptr, nullptr, - mock_stopwatch, mock_stopwatch, unused_texture_registry, nullptr, false, + nullptr, surface->getCanvas(), nullptr, nullptr, mock_stopwatch, + mock_stopwatch, unused_texture_registry, nullptr, false, }; // Specify font file to ensure the same font across different operation diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index 350c3b36573ad..c64049222a8e7 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -111,12 +111,8 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting()); if (elevation_ != 0) { - DrawShadow(context.background_canvas, path_, shadow_color_, elevation_, + DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation_, SkColorGetA(color_) != 0xff, context.frame_device_pixel_ratio); - if (context.leaf_nodes_canvas != nullptr) { - DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation_, - SkColorGetA(color_) != 0xff, context.frame_device_pixel_ratio); - } } // Call drawPath without clip if possible for better performance. @@ -124,10 +120,7 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { paint.setColor(color_); paint.setAntiAlias(true); if (clip_behavior_ != Clip::antiAliasWithSaveLayer) { - context.background_canvas->drawPath(path_, paint); - if (context.leaf_nodes_canvas != nullptr) { - context.leaf_nodes_canvas->drawPath(path_, paint); - } + context.leaf_nodes_canvas->drawPath(path_, paint); } int saveCount = context.internal_nodes_canvas->save(); @@ -151,10 +144,7 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { // (https://github.com/flutter/flutter/issues/18057#issue-328003931) // using saveLayer, we have to call drawPaint instead of drawPath as // anti-aliased drawPath will always have such artifacts. - context.background_canvas->drawPaint(paint); - if (context.leaf_nodes_canvas != nullptr) { - context.leaf_nodes_canvas->drawPaint(paint); - } + context.leaf_nodes_canvas->drawPaint(paint); } PaintChildren(context); diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index 4b94bdecb3ccb..fe78eaefc85bb 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -46,32 +46,21 @@ void PictureLayer::Paint(PaintContext& context) const { context.internal_nodes_canvas->translate(offset_.x(), offset_.y()); #ifndef SUPPORT_FRACTIONAL_TRANSLATION - context.background_canvas->setMatrix(RasterCache::GetIntegralTransCTM( - context.background_canvas->getTotalMatrix())); - if (context.leaf_nodes_canvas != nullptr) { - context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( - context.leaf_nodes_canvas->getTotalMatrix())); - } + context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); #endif if (context.raster_cache) { - const SkMatrix& ctm = context.background_canvas->getTotalMatrix(); + const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix(); RasterCacheResult result = context.raster_cache->Get(*picture(), ctm); if (result.is_valid()) { TRACE_EVENT_INSTANT0("flutter", "raster cache hit"); - result.draw(*context.background_canvas); - if (context.leaf_nodes_canvas != nullptr) { - result.draw(*context.leaf_nodes_canvas); - } + result.draw(*context.leaf_nodes_canvas); return; } } - - context.background_canvas->drawPicture(picture()); - if (context.leaf_nodes_canvas != nullptr) { - picture()->playback(context.leaf_nodes_canvas); - } + picture()->playback(context.leaf_nodes_canvas); // } diff --git a/flow/layers/platform_view_layer_unittests.cc b/flow/layers/platform_view_layer_unittests.cc index ff7f30c69eedc..123f9ab9925f6 100644 --- a/flow/layers/platform_view_layer_unittests.cc +++ b/flow/layers/platform_view_layer_unittests.cc @@ -30,7 +30,7 @@ TEST_F(PlatformViewLayerTest, NullViewEmbedderDoesntPrerollCompositeOrPaint) { EXPECT_FALSE(layer->needs_system_composite()); layer->Paint(paint_context()); - EXPECT_EQ(paint_context().background_canvas, &mock_canvas()); + EXPECT_EQ(paint_context().leaf_nodes_canvas, &mock_canvas()); EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); } diff --git a/flow/layers/shader_mask_layer.cc b/flow/layers/shader_mask_layer.cc index fe90a2fd3b170..157354f7314b4 100644 --- a/flow/layers/shader_mask_layer.cc +++ b/flow/layers/shader_mask_layer.cc @@ -28,15 +28,9 @@ void ShaderMaskLayer::Paint(PaintContext& context) const { SkPaint paint; paint.setBlendMode(blend_mode_); paint.setShader(shader_); - context.background_canvas->translate(mask_rect_.left(), mask_rect_.top()); - context.background_canvas->drawRect( + context.leaf_nodes_canvas->translate(mask_rect_.left(), mask_rect_.top()); + context.leaf_nodes_canvas->drawRect( SkRect::MakeWH(mask_rect_.width(), mask_rect_.height()), paint); - - if (context.leaf_nodes_canvas != nullptr) { - context.leaf_nodes_canvas->translate(mask_rect_.left(), mask_rect_.top()); - context.leaf_nodes_canvas->drawRect( - SkRect::MakeWH(mask_rect_.width(), mask_rect_.height()), paint); - } } } // namespace flutter diff --git a/flow/layers/texture_layer.cc b/flow/layers/texture_layer.cc index e227a3b669aaf..0e3242eaee615 100644 --- a/flow/layers/texture_layer.cc +++ b/flow/layers/texture_layer.cc @@ -30,7 +30,7 @@ void TextureLayer::Paint(PaintContext& context) const { TRACE_EVENT_INSTANT0("flutter", "null texture"); return; } - texture->Paint(*context.background_canvas, paint_bounds(), freeze_, + texture->Paint(*context.leaf_nodes_canvas, paint_bounds(), freeze_, context.gr_context); if (context.leaf_nodes_canvas != nullptr) { texture->Paint(*context.leaf_nodes_canvas, paint_bounds(), freeze_, diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index 9aa8109befe33..f179905ed8e53 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -147,7 +147,6 @@ void RasterCache::Prepare(PrerollContext* context, internal_nodes_canvas.addCanvas(canvas); Layer::PaintContext paintContext = { (SkCanvas*)&internal_nodes_canvas, - nullptr, canvas, context->gr_context, nullptr, diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h index 2c4d81c8091b9..593dec1836823 100644 --- a/flow/testing/layer_test.h +++ b/flow/testing/layer_test.h @@ -47,8 +47,7 @@ class LayerTestBase : public CanvasTestBase { }), paint_context_({ TestT::mock_canvas().internal_canvas(), /* internal_nodes_canvas */ - nullptr, /* leaf_nodes_canvas */ - &TestT::mock_canvas(), /* background_canvas */ + &TestT::mock_canvas(), /* leaf_nodes_canvas */ nullptr, /* gr_context */ nullptr, /* external_view_embedder */ raster_time_, ui_time_, texture_registry_, diff --git a/flow/testing/mock_layer.cc b/flow/testing/mock_layer.cc index 64bb963ad2d5b..5fe1b98088af1 100644 --- a/flow/testing/mock_layer.cc +++ b/flow/testing/mock_layer.cc @@ -36,7 +36,7 @@ void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { void MockLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting()); - context.background_canvas->drawPath(fake_paint_path_, fake_paint_); + context.leaf_nodes_canvas->drawPath(fake_paint_path_, fake_paint_); } } // namespace testing diff --git a/lib/ui/painting/flutter_rtree.cc b/lib/ui/painting/flutter_rtree.cc index 483072d2f0009..e743fa23cfe75 100644 --- a/lib/ui/painting/flutter_rtree.cc +++ b/lib/ui/painting/flutter_rtree.cc @@ -210,18 +210,33 @@ void FlutterRTree::searchRects(Node* node, continue; } SkRect* currentRecordRect = &node->fChildren[i].fBounds; - bool replacedExistingRect = false; - - std::vector currentResults = *results; + std::vector current_results = *results; + bool replaced_existing_rect = false; // If the current record rect intersects with any of the rects in the - // result, then join them, and update the rect in results. - for (size_t j = 0; j < results->size(); j++) { - if (SkRect::Intersects(*currentResults[j], *currentRecordRect)) { - currentResults[j]->join(*currentRecordRect); - replacedExistingRect = true; + // result vector, then join them, and update the rect in results. + size_t joining_rect_idx = current_results.size(); + size_t result_idx = 0; + while (result_idx < results->size()) { + if (SkRect::Intersects(*current_results[result_idx], + *currentRecordRect)) { + joining_rect_idx = result_idx; + replaced_existing_rect = true; + current_results[joining_rect_idx]->join(*currentRecordRect); + break; + } + result_idx++; + } + result_idx = joining_rect_idx + 1; + while (replaced_existing_rect && result_idx < results->size()) { + if (SkRect::Intersects(*current_results[result_idx], + *current_results[joining_rect_idx])) { + current_results[joining_rect_idx]->join(*current_results[result_idx]); + results->erase(results->begin() + result_idx); + } else { + result_idx++; } } - if (!replacedExistingRect) { + if (!replaced_existing_rect) { results->push_back(currentRecordRect); } } diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index acad5ada8510a..399096332eba3 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -343,11 +343,14 @@ 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); } + frame->Submit(); + FireNextFrameCallbackIfPresent(); if (surface_->GetContext()) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 119624dfa675c..3fc325a54b333 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -443,9 +443,14 @@ } bool FlutterPlatformViewsController::SubmitFrame(GrContext* gr_context, - std::shared_ptr ios_context) { + std::shared_ptr ios_context, + SkCanvas* background_canvas) { DisposeViews(); + // Clipping the background canvas before drawing the picture recorders requires to + // save and restore the clip context. + SkAutoCanvasRestore save(background_canvas, true); + bool did_submit = true; // Maps a platform view id to a vector of `FlutterPlatformViewLayer`. @@ -478,7 +483,12 @@ // Get the intersection rect between the current joined rect // and the platform view rect. joined_rect.intersect(platform_view_rect); - auto layer = GetLayer(gr_context, ios_context, picture, joined_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); + + auto layer = GetLayer(gr_context, gl_context, picture, joined_rect); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); } else if (allocation_size > 0) { @@ -487,13 +497,20 @@ // and the platform view rect. SkRect joined_rect = *rect; joined_rect.intersect(platform_view_rect); - auto layer = GetLayer(gr_context, ios_context, picture, joined_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); + + auto layer = GetLayer(gr_context, gl_context, picture, joined_rect); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); } } } + background_canvas->drawPicture(picture); } + RemoveUnusedLayers(); BringLayersIntoView(platform_view_layers); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 7997893f780c3..2adc907901512 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -137,7 +137,9 @@ class FlutterPlatformViewsController { // 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); diff --git a/shell/platform/darwin/ios/ios_surface_gl.h b/shell/platform/darwin/ios/ios_surface_gl.h index a00ac47141c56..31ff4851c4b62 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.h +++ b/shell/platform/darwin/ios/ios_surface_gl.h @@ -77,7 +77,7 @@ class IOSSurfaceGL final : public IOSSurface, public GPUSurfaceGLDelegate { SkRect GetPlatformViewRect(int view_id) override; // |ExternalViewEmbedder| - bool SubmitFrame(GrContext* context) override; + bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; private: std::unique_ptr render_target_; diff --git a/shell/platform/darwin/ios/ios_surface_gl.mm b/shell/platform/darwin/ios/ios_surface_gl.mm index 78ebdd565fa67..665b0b130a942 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/shell/platform/darwin/ios/ios_surface_gl.mm @@ -151,13 +151,14 @@ } // |ExternalViewEmbedder| -bool IOSSurfaceGL::SubmitFrame(GrContext* context) { +bool IOSSurfaceGL::SubmitFrame(GrContext* context, SkCanvas* background_canvas) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); if (platform_views_controller == nullptr) { return true; } - bool submitted = platform_views_controller->SubmitFrame(std::move(context), context_); + bool submitted = platform_views_controller->SubmitFrame(std::move(context), context_, + std::move(background_canvas)); [CATransaction commit]; return submitted; } diff --git a/shell/platform/darwin/ios/ios_surface_metal.h b/shell/platform/darwin/ios/ios_surface_metal.h index 678644c429d30..28b4d0573fea8 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.h +++ b/shell/platform/darwin/ios/ios_surface_metal.h @@ -64,7 +64,7 @@ class IOSSurfaceMetal final : public IOSSurface, public GPUSurfaceDelegate { SkRect GetPlatformViewRect(int view_id) override; // |ExternalViewEmbedder| - bool SubmitFrame(GrContext* context) override; + bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; private: fml::scoped_nsobject layer_; diff --git a/shell/platform/darwin/ios/ios_surface_metal.mm b/shell/platform/darwin/ios/ios_surface_metal.mm index faad0b68c2fab..4f980a5befc7d 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.mm +++ b/shell/platform/darwin/ios/ios_surface_metal.mm @@ -59,13 +59,13 @@ return platform_views_controller->GetPlatformViewRect(view_id); } -bool IOSSurfaceMetal::SubmitFrame(GrContext* context) { +bool IOSSurfaceMetal::SubmitFrame(GrContext* context, SkCanvas* background_canvas) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); if (platform_views_controller == nullptr) { return true; } - bool submitted = platform_views_controller->SubmitFrame(context, nullptr); + bool submitted = platform_views_controller->SubmitFrame(context, nullptr, background_canvas); [CATransaction commit]; return submitted; } diff --git a/shell/platform/darwin/ios/ios_surface_software.h b/shell/platform/darwin/ios/ios_surface_software.h index 2b8cf28add33b..924bdd0df4341 100644 --- a/shell/platform/darwin/ios/ios_surface_software.h +++ b/shell/platform/darwin/ios/ios_surface_software.h @@ -65,7 +65,7 @@ class IOSSurfaceSoftware final : public IOSSurface, public GPUSurfaceSoftwareDel SkRect GetPlatformViewRect(int view_id) override; // |ExternalViewEmbedder| - bool SubmitFrame(GrContext* context) override; + bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; private: fml::scoped_nsobject layer_; diff --git a/shell/platform/darwin/ios/ios_surface_software.mm b/shell/platform/darwin/ios/ios_surface_software.mm index 5d05f379c469a..f8b92063dcb2f 100644 --- a/shell/platform/darwin/ios/ios_surface_software.mm +++ b/shell/platform/darwin/ios/ios_surface_software.mm @@ -184,12 +184,12 @@ } // |ExternalViewEmbedder| -bool IOSSurfaceSoftware::SubmitFrame(GrContext* context) { +bool IOSSurfaceSoftware::SubmitFrame(GrContext* context, SkCanvas* background_canvas) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); if (platform_views_controller == nullptr) { return true; } - return platform_views_controller->SubmitFrame(nullptr, nullptr); + return platform_views_controller->SubmitFrame(nullptr, nullptr, background_canvas); } } // namespace flutter diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index 4b4127bee6952..fe4c7e19bd3eb 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -143,7 +143,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_); diff --git a/shell/platform/embedder/embedder_external_view_embedder.h b/shell/platform/embedder/embedder_external_view_embedder.h index 9ce5cce6b8ca0..296fb59c57b8a 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.h +++ b/shell/platform/embedder/embedder_external_view_embedder.h @@ -89,7 +89,7 @@ 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| SkCanvas* GetRootCanvas() override; From 4cb007a8bfaed5c064db2f30cf34d89f012ff2eb Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 4 Mar 2020 23:17:31 -0800 Subject: [PATCH 21/44] Update rtree unit test --- lib/ui/painting/flutter_rtree_unittests.cc | 92 ++++++++++++++++++---- 1 file changed, 75 insertions(+), 17 deletions(-) diff --git a/lib/ui/painting/flutter_rtree_unittests.cc b/lib/ui/painting/flutter_rtree_unittests.cc index 7106b6318e8f7..8a7de1116cbef 100644 --- a/lib/ui/painting/flutter_rtree_unittests.cc +++ b/lib/ui/painting/flutter_rtree_unittests.cc @@ -25,13 +25,14 @@ TEST_F(FlutterRTreeTest, NoIntersection) { rect_paint.setColor(SkColors::kCyan); rect_paint.setStyle(SkPaint::Style::kFill_Style); - recording_canvas->drawRect(SkRect::MakeXYWH(20, 20, 20, 20), rect_paint); + // If no rect is intersected with the query rect, then the result vector is empty. + recording_canvas->drawRect(SkRect::MakeLTRB(20, 20, 40, 40), rect_paint); recorder->finishRecordingAsPicture(); auto hits = std::vector(); - auto unobstructed = SkRect::MakeXYWH(40, 40, 20, 20); + auto query = SkRect::MakeLTRB(40, 40, 80, 80); - r_tree->searchRects(unobstructed, &hits); + r_tree->searchRects(query, &hits); ASSERT_TRUE(hits.empty()); } @@ -46,21 +47,21 @@ TEST_F(FlutterRTreeTest, Intersection) { rect_paint.setColor(SkColors::kCyan); rect_paint.setStyle(SkPaint::Style::kFill_Style); - recording_canvas->drawRect(SkRect::MakeXYWH(120, 120, 40, 40), rect_paint); + // Given a single rect A that intersects with the query rect, + // the result vector contains this rect. + recording_canvas->drawRect(SkRect::MakeLTRB(120, 120, 160, 160), rect_paint); recorder->finishRecordingAsPicture(); - // Hits that partially overlap with a drawn area return bboxes describing the - // intersection of the query and the drawn area. - auto one_hit = SkRect::MakeXYWH(140, 140, 40, 40); + auto query = SkRect::MakeLTRB(140, 140, 150, 150); auto hits = std::vector(); - r_tree->searchRects(one_hit, &hits); + r_tree->searchRects(query, &hits); ASSERT_EQ(1UL, hits.size()); - ASSERT_EQ(*hits[0], SkRect::MakeXYWH(120, 120, 40, 40)); + ASSERT_EQ(*hits[0], SkRect::MakeLTRB(120, 120, 160, 160)); } -TEST_F(FlutterRTreeTest, JoinRectsWhenIntersected) { +TEST_F(FlutterRTreeTest, JoinRectsWhenIntersectedCase1) { auto r_tree = sk_make_sp(); auto rtree_factory = FlutterRTreeFactory(r_tree); auto recorder = std::make_unique(); @@ -71,19 +72,76 @@ TEST_F(FlutterRTreeTest, JoinRectsWhenIntersected) { rect_paint.setColor(SkColors::kCyan); rect_paint.setStyle(SkPaint::Style::kFill_Style); - recording_canvas->drawRect(SkRect::MakeXYWH(120, 120, 40, 40), rect_paint); - recording_canvas->drawRect(SkRect::MakeXYWH(140, 140, 40, 40), rect_paint); + + // Given the A, and B rects, which intersect with the query rect, + // the result vector contains the rect resulting from the union of A and B. + // + // +-----+ + // | A | + // | +-----+ + // | | C | + // | +-----+ + // | | + // +-----+ + + // A + recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 150, 150), rect_paint); + // B + recording_canvas->drawRect(SkRect::MakeLTRB(125, 125, 175, 175), rect_paint); recorder->finishRecordingAsPicture(); - // Hits that partially overlap with a drawn area return bboxes describing the - // intersection of the query and the drawn area. - auto one_hit = SkRect::MakeXYWH(142, 142, 10, 10); + auto query = SkRect::MakeXYWH(120, 120, 126, 126); + auto hits = std::vector(); + + r_tree->searchRects(query, &hits); + ASSERT_EQ(1UL, hits.size()); + ASSERT_EQ(*hits[0], SkRect::MakeLTRB(100, 100, 175, 175)); +} + +TEST_F(FlutterRTreeTest, JoinRectsWhenIntersectedCase2) { + auto r_tree = sk_make_sp(); + auto rtree_factory = FlutterRTreeFactory(r_tree); + auto recorder = std::make_unique(); + auto recording_canvas = + recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + // Given the A, B, and C rects that intersect with the query rect, + // there should be only C in the result vector, + // since A and B are contained in C. + // + // +---------------------+ + // | C | + // | +-----+ +-----+ | + // | | A | | B | | + // | +-----+ +-----+ | + // +---------------------+ + // +-----+ + // | D | + // +-----+ + + // A + recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 200, 200), rect_paint); + // B + recording_canvas->drawRect(SkRect::MakeLTRB(300, 100, 400, 200), rect_paint); + // C + recording_canvas->drawRect(SkRect::MakeLTRB(50, 50, 500, 250), rect_paint); + // D + recording_canvas->drawRect(SkRect::MakeLTRB(280, 100, 280, 320), rect_paint); + + recorder->finishRecordingAsPicture(); + + + auto query = SkRect::MakeLTRB(30, 30, 550, 270); auto hits = std::vector(); - r_tree->searchRects(one_hit, &hits); + r_tree->searchRects(query, &hits); ASSERT_EQ(1UL, hits.size()); - ASSERT_EQ(*hits[0], SkRect::MakeXYWH(120, 120, 60, 60)); + ASSERT_EQ(*hits[0], SkRect::MakeLTRB(50, 50, 500, 250)); } } // namespace testing From 326f339ec1ec0cdf04149419106370525bd0cd01 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 5 Mar 2020 19:57:56 -0800 Subject: [PATCH 22/44] Minor fixes --- flow/layers/performance_overlay_layer.cc | 2 +- .../layers/performance_overlay_layer_unittests.cc | 3 +-- lib/ui/painting/flutter_rtree_unittests.cc | 15 +++++++-------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/flow/layers/performance_overlay_layer.cc b/flow/layers/performance_overlay_layer.cc index b53f4324a47ff..ef7b6f2c6194c 100644 --- a/flow/layers/performance_overlay_layer.cc +++ b/flow/layers/performance_overlay_layer.cc @@ -84,7 +84,7 @@ void PerformanceOverlayLayer::Paint(PaintContext& context) const { SkScalar y = paint_bounds().y() + padding; SkScalar width = paint_bounds().width() - (padding * 2); SkScalar height = paint_bounds().height() / 2; - SkAutoCanvasRestore save(context.internal_nodes_canvas, true); + SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); VisualizeStopWatch( *context.leaf_nodes_canvas, context.raster_time, x, y, width, diff --git a/flow/layers/performance_overlay_layer_unittests.cc b/flow/layers/performance_overlay_layer_unittests.cc index 6f383fc8716c2..769f80803a8fa 100644 --- a/flow/layers/performance_overlay_layer_unittests.cc +++ b/flow/layers/performance_overlay_layer_unittests.cc @@ -64,8 +64,7 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { flutter::TextureRegistry unused_texture_registry; flutter::Layer::PaintContext paintContext = { nullptr, surface->getCanvas(), nullptr, nullptr, mock_stopwatch, - mock_stopwatch, unused_texture_registry, nullptr, false, - }; + mock_stopwatch, unused_texture_registry, nullptr, false}; // Specify font file to ensure the same font across different operation // systems. diff --git a/lib/ui/painting/flutter_rtree_unittests.cc b/lib/ui/painting/flutter_rtree_unittests.cc index 8a7de1116cbef..7e231dfefcf47 100644 --- a/lib/ui/painting/flutter_rtree_unittests.cc +++ b/lib/ui/painting/flutter_rtree_unittests.cc @@ -25,7 +25,8 @@ TEST_F(FlutterRTreeTest, NoIntersection) { rect_paint.setColor(SkColors::kCyan); rect_paint.setStyle(SkPaint::Style::kFill_Style); - // If no rect is intersected with the query rect, then the result vector is empty. + // If no rect is intersected with the query rect, then the result vector is + // empty. recording_canvas->drawRect(SkRect::MakeLTRB(20, 20, 40, 40), rect_paint); recorder->finishRecordingAsPicture(); @@ -72,10 +73,9 @@ TEST_F(FlutterRTreeTest, JoinRectsWhenIntersectedCase1) { rect_paint.setColor(SkColors::kCyan); rect_paint.setStyle(SkPaint::Style::kFill_Style); - - // Given the A, and B rects, which intersect with the query rect, + // Given the A, and B rects, which intersect with the query rect, // the result vector contains the rect resulting from the union of A and B. - // + // // +-----+ // | A | // | +-----+ @@ -111,9 +111,9 @@ TEST_F(FlutterRTreeTest, JoinRectsWhenIntersectedCase2) { rect_paint.setStyle(SkPaint::Style::kFill_Style); // Given the A, B, and C rects that intersect with the query rect, - // there should be only C in the result vector, + // there should be only C in the result vector, // since A and B are contained in C. - // + // // +---------------------+ // | C | // | +-----+ +-----+ | @@ -129,13 +129,12 @@ TEST_F(FlutterRTreeTest, JoinRectsWhenIntersectedCase2) { // B recording_canvas->drawRect(SkRect::MakeLTRB(300, 100, 400, 200), rect_paint); // C - recording_canvas->drawRect(SkRect::MakeLTRB(50, 50, 500, 250), rect_paint); + recording_canvas->drawRect(SkRect::MakeLTRB(50, 50, 500, 250), rect_paint); // D recording_canvas->drawRect(SkRect::MakeLTRB(280, 100, 280, 320), rect_paint); recorder->finishRecordingAsPicture(); - auto query = SkRect::MakeLTRB(30, 30, 550, 270); auto hits = std::vector(); From 6d283a997a09accb0526092a32ffca1ea3dda976 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 5 Mar 2020 22:31:41 -0800 Subject: [PATCH 23/44] Revert unintended change --- flow/layers/picture_layer.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index fe78eaefc85bb..7214501156cf6 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -42,8 +42,8 @@ void PictureLayer::Paint(PaintContext& context) const { FML_DCHECK(picture_.get()); FML_DCHECK(needs_painting()); - SkAutoCanvasRestore save(context.internal_nodes_canvas, true); - context.internal_nodes_canvas->translate(offset_.x(), offset_.y()); + SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); + context.leaf_nodes_canvas->translate(offset_.x(), offset_.y()); #ifndef SUPPORT_FRACTIONAL_TRANSLATION context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( From 781fb29c2999fd54a775eb3cebad113a549a29ea Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 5 Mar 2020 22:32:25 -0800 Subject: [PATCH 24/44] Remove stale comment --- flow/layers/picture_layer.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index 7214501156cf6..6e776ee756588 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -61,7 +61,6 @@ void PictureLayer::Paint(PaintContext& context) const { } } picture()->playback(context.leaf_nodes_canvas); - // } } // namespace flutter From 858849bed0b441bfe066d45ef314394fed47ae45 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 5 Mar 2020 22:33:29 -0800 Subject: [PATCH 25/44] Clean up --- flow/layers/platform_view_layer.cc | 10 ---------- flow/layers/platform_view_layer.h | 1 - 2 files changed, 11 deletions(-) diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 66b550eaf0242..81541b7a0cde8 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -21,7 +21,6 @@ void PlatformViewLayer::Preroll(PrerollContext* context, "does not support embedding"; return; } - matrix_ = matrix; context->has_platform_view = true; std::unique_ptr params = std::make_unique(); @@ -41,14 +40,5 @@ void PlatformViewLayer::Paint(PaintContext& context) const { } SkCanvas* canvas = context.view_embedder->CompositeEmbeddedView(view_id_); context.leaf_nodes_canvas = canvas; - // Don't draw subsequent drawings on the area covered by the platform view, - // since these drawings are render on an overlay on top of the platform view. - // This prevent visible drawings on the background canvas when the platform - // view has opacity. - // context.background_canvas->resetMatrix(); - // context.background_canvas->clipRect( - // context.view_embedder->GetPlatformViewRect(view_id_), - // SkClipOp::kDifference); - // context.background_canvas->setMatrix(matrix_); } } // namespace flutter diff --git a/flow/layers/platform_view_layer.h b/flow/layers/platform_view_layer.h index 54baeed4d2b52..242b3734dd3b1 100644 --- a/flow/layers/platform_view_layer.h +++ b/flow/layers/platform_view_layer.h @@ -22,7 +22,6 @@ class PlatformViewLayer : public Layer { SkPoint offset_; SkSize size_; int64_t view_id_; - SkMatrix matrix_; FML_DISALLOW_COPY_AND_ASSIGN(PlatformViewLayer); }; From d482d2dc3bed4c0f5443b73152a391a5f7bb149b Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 6 Mar 2020 16:07:01 -0800 Subject: [PATCH 26/44] Fix unittest --- flow/layers/picture_layer_unittests.cc | 3 --- flow/layers/texture_layer.cc | 4 ---- flow/scene_update_context.cc | 1 - 3 files changed, 8 deletions(-) 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/flow/layers/texture_layer.cc b/flow/layers/texture_layer.cc index 0e3242eaee615..e5df2b795dd34 100644 --- a/flow/layers/texture_layer.cc +++ b/flow/layers/texture_layer.cc @@ -32,10 +32,6 @@ void TextureLayer::Paint(PaintContext& context) const { } texture->Paint(*context.leaf_nodes_canvas, paint_bounds(), freeze_, context.gr_context); - if (context.leaf_nodes_canvas != nullptr) { - texture->Paint(*context.leaf_nodes_canvas, paint_bounds(), freeze_, - context.gr_context); - } } } // namespace flutter diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc index 20eadfee3f16a..59524bc9c6e56 100644 --- a/flow/scene_update_context.cc +++ b/flow/scene_update_context.cc @@ -201,7 +201,6 @@ SceneUpdateContext::ExecutePaintTasks(CompositorContext::ScopedFrame& frame) { FML_DCHECK(task.surface); SkCanvas* canvas = task.surface->GetSkiaSurface()->getCanvas(); Layer::PaintContext context = {canvas, - nullptr, canvas, frame.gr_context(), nullptr, From 00f86a56021a0ea579aaa9529267fbbfae2235ae Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Sun, 8 Mar 2020 21:11:16 -0700 Subject: [PATCH 27/44] Add tests for unobstructed platform views --- .../framework/Source/FlutterPlatformViews.mm | 28 +- .../Source/FlutterPlatformViews_Internal.h | 4 +- .../ios/Flutter/Generated.xcconfig | 9 + .../ios/Flutter/flutter_export_environment.sh | 10 + .../Scenarios.xcodeproj/project.pbxproj | 4 + .../ios/Scenarios/Scenarios/AppDelegate.m | 7 + .../UnobstructedPlatformViewTests.m | 254 ++++++++++++++++++ testing/scenario_app/lib/main.dart | 6 + .../scenario_app/lib/src/platform_view.dart | 229 +++++++++++++++- 9 files changed, 545 insertions(+), 6 deletions(-) create mode 100644 testing/scenario_app/ios/Flutter/Generated.xcconfig create mode 100755 testing/scenario_app/ios/Flutter/flutter_export_environment.sh create mode 100644 testing/scenario_app/ios/Scenarios/ScenariosUITests/UnobstructedPlatformViewTests.m diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 3fc325a54b333..4b1c3f8d5098e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -142,6 +142,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] @@ -473,6 +476,7 @@ bbh->searchRects(platform_view_rect, &intersection_rects); size_t allocation_size = intersection_rects.size(); + int64_t overlay_count = platform_view_layers[current_platform_view_id].size(); if (allocation_size > kMaxLayerAllocations) { // If the max number of allocations per platform view is exceeded, // then join all the rects into a single one. @@ -488,7 +492,13 @@ // on the overlay layer. background_canvas->clipRect(joined_rect, SkClipOp::kDifference); - auto layer = GetLayer(gr_context, gl_context, picture, joined_rect); + auto layer = GetLayer(gr_context, // + gl_context, // + picture, // + joined_rect, // + current_platform_view_id, // + overlay_count // + ); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); } else if (allocation_size > 0) { @@ -502,9 +512,16 @@ // on the overlay layer. background_canvas->clipRect(joined_rect, SkClipOp::kDifference); - auto layer = GetLayer(gr_context, gl_context, picture, joined_rect); + auto layer = GetLayer(gr_context, // + gl_context, // + picture, // + joined_rect, // + current_platform_view_id, // + overlay_count // + ); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); + overlay_count++; } } } @@ -548,7 +565,9 @@ GrContext* gr_context, std::shared_ptr ios_context, sk_sp picture, - SkRect rect) { + SkRect rect, + int64_t view_id, + int64_t overlay_id) { std::shared_ptr layer = layer_pool_->GetLayer(gr_context, ios_context); CGFloat screenScale = [UIScreen mainScreen].scale; // Set the size of the overlay UIView. @@ -557,6 +576,9 @@ 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]; SkISize rect_size = SkISize::Make(rect.width(), rect.height()); std::unique_ptr frame = layer->surface->AcquireFrame(rect_size); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 2adc907901512..dc328b1b5747d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -250,7 +250,9 @@ class FlutterPlatformViewsController { std::shared_ptr GetLayer(GrContext* gr_context, std::shared_ptr ios_context, sk_sp picture, - SkRect rect); + 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 diff --git a/testing/scenario_app/ios/Flutter/Generated.xcconfig b/testing/scenario_app/ios/Flutter/Generated.xcconfig new file mode 100644 index 0000000000000..f85df1d4a283d --- /dev/null +++ b/testing/scenario_app/ios/Flutter/Generated.xcconfig @@ -0,0 +1,9 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=/Users/egarciad/p/flutter +FLUTTER_APPLICATION_PATH=/Users/egarciad/p/engine/src/flutter/testing/scenario_app +FLUTTER_TARGET=lib/main.dart +FLUTTER_BUILD_DIR=build +SYMROOT=${SOURCE_ROOT}/../build/ios +FLUTTER_FRAMEWORK_DIR=/Users/egarciad/p/flutter/bin/cache/artifacts/engine/ios +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 diff --git a/testing/scenario_app/ios/Flutter/flutter_export_environment.sh b/testing/scenario_app/ios/Flutter/flutter_export_environment.sh new file mode 100755 index 0000000000000..cae81ba3f898c --- /dev/null +++ b/testing/scenario_app/ios/Flutter/flutter_export_environment.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=/Users/egarciad/p/flutter" +export "FLUTTER_APPLICATION_PATH=/Users/egarciad/p/engine/src/flutter/testing/scenario_app" +export "FLUTTER_TARGET=lib/main.dart" +export "FLUTTER_BUILD_DIR=build" +export "SYMROOT=${SOURCE_ROOT}/../build/ios" +export "FLUTTER_FRAMEWORK_DIR=/Users/egarciad/p/flutter/bin/cache/artifacts/engine/ios" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" 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/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/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..ef811dce8b146 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,19 @@ 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, + }) { + if (overlayOffset == null) { + 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 b35dde16b4219a55aee1d76df200fcedcf14d14b Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 9 Mar 2020 09:17:30 -0700 Subject: [PATCH 28/44] Format dart code --- testing/scenario_app/lib/src/platform_view.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/testing/scenario_app/lib/src/platform_view.dart b/testing/scenario_app/lib/src/platform_view.dart index ef811dce8b146..fcea41a65f7d5 100644 --- a/testing/scenario_app/lib/src/platform_view.dart +++ b/testing/scenario_app/lib/src/platform_view.dart @@ -647,9 +647,7 @@ mixin _BasePlatformViewScenarioMixin on Scenario { int viewId, { Offset overlayOffset, }) { - if (overlayOffset == null) { - overlayOffset = const Offset(50, 50); - } + overlayOffset ??= const Offset(50, 50); _addPlatformViewtoScene(sceneBuilder, viewId, 500, 500); final PictureRecorder recorder = PictureRecorder(); final Canvas canvas = Canvas(recorder); From 423036d5dafbcbc4c5dca0599401d11c84a55e25 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 9 Mar 2020 09:44:50 -0700 Subject: [PATCH 29/44] Clean up --- flow/embedded_views.h | 3 --- .../Source/FlutterPlatformViews_Internal.h | 4 ++-- .../Source/FlutterPlatformViews_Internal.mm | 4 ++++ shell/platform/darwin/ios/ios_surface_gl.h | 3 --- shell/platform/darwin/ios/ios_surface_gl.mm | 7 ------- shell/platform/darwin/ios/ios_surface_metal.h | 3 --- shell/platform/darwin/ios/ios_surface_metal.mm | 6 ------ shell/platform/darwin/ios/ios_surface_software.h | 3 --- shell/platform/darwin/ios/ios_surface_software.mm | 7 ------- .../embedder/embedder_external_view_embedder.cc | 14 -------------- .../embedder/embedder_external_view_embedder.h | 3 --- 11 files changed, 6 insertions(+), 51 deletions(-) diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 14c9daba9b1f2..71f9bdaf5c98e 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -248,9 +248,6 @@ class ExternalViewEmbedder { // Must be called on the UI thread. virtual SkCanvas* CompositeEmbeddedView(int view_id) = 0; - // Must be called on the UI thread. - virtual SkRect GetPlatformViewRect(int view_id) = 0; - virtual bool SubmitFrame(GrContext* context, SkCanvas* background_canvas); FML_DISALLOW_COPY_AND_ASSIGN(ExternalViewEmbedder); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index dc328b1b5747d..f5d583899da57 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -63,7 +63,7 @@ struct FlutterPlatformViewLayer { std::unique_ptr ios_surface, std::unique_ptr surface); - ~FlutterPlatformViewLayer() = default; + ~FlutterPlatformViewLayer(); fml::scoped_nsobject overlay_view; std::unique_ptr ios_surface; @@ -101,7 +101,7 @@ class FlutterPlatformViewsController { public: FlutterPlatformViewsController(); - ~FlutterPlatformViewsController() = default; + ~FlutterPlatformViewsController(); void SetFlutterView(UIView* flutter_view); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm index 8450568bc7358..21db9530fe0bb 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.mm @@ -18,9 +18,13 @@ ios_surface(std::move(ios_surface)), surface(std::move(surface)){}; +FlutterPlatformViewLayer::~FlutterPlatformViewLayer() = default; + FlutterPlatformViewsController::FlutterPlatformViewsController() : layer_pool_(std::make_unique()){}; +FlutterPlatformViewsController::~FlutterPlatformViewsController() = default; + CATransform3D GetCATransform3DFromSkMatrix(const SkMatrix& matrix) { // Skia only supports 2D transform so we don't map z. CATransform3D transform = CATransform3DIdentity; diff --git a/shell/platform/darwin/ios/ios_surface_gl.h b/shell/platform/darwin/ios/ios_surface_gl.h index 31ff4851c4b62..d20e085d6e15e 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.h +++ b/shell/platform/darwin/ios/ios_surface_gl.h @@ -73,9 +73,6 @@ class IOSSurfaceGL final : public IOSSurface, public GPUSurfaceGLDelegate { // |ExternalViewEmbedder| SkCanvas* CompositeEmbeddedView(int view_id) override; - // |ExternalViewEmbedder| - SkRect GetPlatformViewRect(int view_id) override; - // |ExternalViewEmbedder| bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; diff --git a/shell/platform/darwin/ios/ios_surface_gl.mm b/shell/platform/darwin/ios/ios_surface_gl.mm index 665b0b130a942..52ae09b8538d6 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/shell/platform/darwin/ios/ios_surface_gl.mm @@ -121,13 +121,6 @@ platform_views_controller->PrerollCompositeEmbeddedView(view_id, std::move(params)); } -// |ExternalViewEmbedder| -SkRect IOSSurfaceGL::GetPlatformViewRect(int view_id) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - return platform_views_controller->GetPlatformViewRect(view_id); -} - // |ExternalViewEmbedder| PostPrerollResult IOSSurfaceGL::PostPrerollAction( fml::RefPtr gpu_thread_merger) { diff --git a/shell/platform/darwin/ios/ios_surface_metal.h b/shell/platform/darwin/ios/ios_surface_metal.h index 28b4d0573fea8..2a35325b9307b 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.h +++ b/shell/platform/darwin/ios/ios_surface_metal.h @@ -60,9 +60,6 @@ class IOSSurfaceMetal final : public IOSSurface, public GPUSurfaceDelegate { // |ExternalViewEmbedder| SkCanvas* CompositeEmbeddedView(int view_id) override; - // |ExternalViewEmbedder| - SkRect GetPlatformViewRect(int view_id) override; - // |ExternalViewEmbedder| bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; diff --git a/shell/platform/darwin/ios/ios_surface_metal.mm b/shell/platform/darwin/ios/ios_surface_metal.mm index 4f980a5befc7d..9255e74e2d6fa 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.mm +++ b/shell/platform/darwin/ios/ios_surface_metal.mm @@ -53,12 +53,6 @@ ); } -SkRect IOSSurfaceMetal::GetPlatformViewRect(int view_id) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - return platform_views_controller->GetPlatformViewRect(view_id); -} - bool IOSSurfaceMetal::SubmitFrame(GrContext* context, SkCanvas* background_canvas) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); if (platform_views_controller == nullptr) { diff --git a/shell/platform/darwin/ios/ios_surface_software.h b/shell/platform/darwin/ios/ios_surface_software.h index 924bdd0df4341..78b2f8e4825dc 100644 --- a/shell/platform/darwin/ios/ios_surface_software.h +++ b/shell/platform/darwin/ios/ios_surface_software.h @@ -61,9 +61,6 @@ class IOSSurfaceSoftware final : public IOSSurface, public GPUSurfaceSoftwareDel // |ExternalViewEmbedder| SkCanvas* CompositeEmbeddedView(int view_id) override; - // |ExternalViewEmbedder| - SkRect GetPlatformViewRect(int view_id) override; - // |ExternalViewEmbedder| bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; diff --git a/shell/platform/darwin/ios/ios_surface_software.mm b/shell/platform/darwin/ios/ios_surface_software.mm index f8b92063dcb2f..fd43533e0b3d0 100644 --- a/shell/platform/darwin/ios/ios_surface_software.mm +++ b/shell/platform/darwin/ios/ios_surface_software.mm @@ -176,13 +176,6 @@ return platform_views_controller->CompositeEmbeddedView(view_id); } -// |ExternalViewEmbedder| -SkRect IOSSurfaceSoftware::GetPlatformViewRect(int view_id) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - return platform_views_controller->GetPlatformViewRect(view_id); -} - // |ExternalViewEmbedder| bool IOSSurfaceSoftware::SubmitFrame(GrContext* context, SkCanvas* background_canvas) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index fe4c7e19bd3eb..6bdb27677858f 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -92,20 +92,6 @@ SkCanvas* EmbedderExternalViewEmbedder::GetRootCanvas() { return found->second->GetCanvas(); } -// |ExternalViewEmbedder| -SkRect EmbedderExternalViewEmbedder::GetPlatformViewRect(int view_id) { - auto found = pending_views_.find(view_id); - if (found == pending_views_.end()) { - FML_DLOG(WARNING) - << "No root canvas could be found. This is extremely unlikely and " - "indicates that the external view embedder did not receive the " - "notification to begin the frame."; - return SkRect::MakeEmpty(); - } - auto size = found->second->GetRenderSurfaceSize(); - return SkRect::MakeXYWH(0, 0, size.width(), size.height()); -} - // |ExternalViewEmbedder| std::vector EmbedderExternalViewEmbedder::GetCurrentCanvases() { std::vector canvases; diff --git a/shell/platform/embedder/embedder_external_view_embedder.h b/shell/platform/embedder/embedder_external_view_embedder.h index 296fb59c57b8a..b03969c8363aa 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.h +++ b/shell/platform/embedder/embedder_external_view_embedder.h @@ -94,9 +94,6 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { // |ExternalViewEmbedder| SkCanvas* GetRootCanvas() override; - // |ExternalViewEmbedder| - SkRect GetPlatformViewRect(int view_id) override; - private: const CreateRenderTargetCallback create_render_target_callback_; const PresentCallback present_callback_; From ed891cb7eeeae5b3e9de42108f29a178394ef1b0 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 9 Mar 2020 19:02:00 -0700 Subject: [PATCH 30/44] Remove old rtree --- ci/licenses_golden/licenses_flutter | 3 - lib/ui/BUILD.gn | 2 - lib/ui/painting/flutter_rtree.cc | 258 ------------------ lib/ui/painting/flutter_rtree.h | 85 ------ lib/ui/painting/flutter_rtree_unittests.cc | 147 ---------- .../framework/Source/FlutterPlatformViews.mm | 20 +- .../Source/FlutterPlatformViews_Internal.h | 4 +- 7 files changed, 12 insertions(+), 507 deletions(-) delete mode 100644 lib/ui/painting/flutter_rtree.cc delete mode 100644 lib/ui/painting/flutter_rtree.h delete mode 100644 lib/ui/painting/flutter_rtree_unittests.cc diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index b3342763fc373..2f6c5c31fccef 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -301,9 +301,6 @@ FILE: ../../../flutter/lib/ui/painting/color_filter.cc FILE: ../../../flutter/lib/ui/painting/color_filter.h FILE: ../../../flutter/lib/ui/painting/engine_layer.cc FILE: ../../../flutter/lib/ui/painting/engine_layer.h -FILE: ../../../flutter/lib/ui/painting/flutter_rtree.cc -FILE: ../../../flutter/lib/ui/painting/flutter_rtree.h -FILE: ../../../flutter/lib/ui/painting/flutter_rtree_unittests.cc FILE: ../../../flutter/lib/ui/painting/frame_info.cc FILE: ../../../flutter/lib/ui/painting/frame_info.h FILE: ../../../flutter/lib/ui/painting/gradient.cc diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 0c08356bd9e12..25c20218a428d 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -30,8 +30,6 @@ source_set("ui") { "painting/color_filter.h", "painting/engine_layer.cc", "painting/engine_layer.h", - "painting/flutter_rtree.cc", - "painting/flutter_rtree.h", "painting/frame_info.cc", "painting/frame_info.h", "painting/gradient.cc", diff --git a/lib/ui/painting/flutter_rtree.cc b/lib/ui/painting/flutter_rtree.cc deleted file mode 100644 index e743fa23cfe75..0000000000000 --- a/lib/ui/painting/flutter_rtree.cc +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "flutter/lib/ui/painting/flutter_rtree.h" -#include "flutter/fml/logging.h" - -namespace flutter { - -FlutterRTree::FlutterRTree() : fCount(0) {} - -void FlutterRTree::insert(const SkRect boundsArray[], - const SkBBoxHierarchy::Metadata metadata[], - int N) { - SkASSERT(0 == fCount); - - std::vector branches; - branches.reserve(N); - - for (int i = 0; i < N; i++) { - const SkRect& bounds = boundsArray[i]; - if (bounds.isEmpty()) { - continue; - } - Branch b; - b.fBounds = bounds; - b.fOpIndex = i; - b.isDraw = (metadata == nullptr) ? false : metadata[i].isDraw; - branches.push_back(b); - } - - fCount = (int)branches.size(); - if (fCount) { - if (1 == fCount) { - fNodes.reserve(1); - Node* n = this->allocateNodeAtLevel(0); - n->fNumChildren = 1; - n->fChildren[0] = branches[0]; - fRoot.fSubtree = n; - fRoot.fBounds = branches[0].fBounds; - } else { - fNodes.reserve(CountNodes(fCount)); - fRoot = this->bulkLoad(&branches); - } - } -} - -void FlutterRTree::insert(const SkRect boundsArray[], int N) { - // TODO(egarciad): Ask Skia about removing this method signature. - insert(boundsArray, nullptr, N); -} - -FlutterRTree::Node* FlutterRTree::allocateNodeAtLevel(uint16_t level) { - SkDEBUGCODE(Node* p = fNodes.data()); - fNodes.push_back(Node{}); - Node& out = fNodes.back(); - SkASSERT(fNodes.data() == p); // If this fails, we didn't reserve() enough. - out.fNumChildren = 0; - out.fLevel = level; - return &out; -} - -// This function parallels bulkLoad, but just counts how many nodes bulkLoad -// would allocate. -int FlutterRTree::CountNodes(int branches) { - if (branches == 1) { - return 1; - } - int numBranches = branches / kMaxChildren; - int remainder = branches % kMaxChildren; - if (remainder > 0) { - numBranches++; - if (remainder >= kMinChildren) { - remainder = 0; - } else { - remainder = kMinChildren - remainder; - } - } - int currentBranch = 0; - int nodes = 0; - while (currentBranch < branches) { - int incrementBy = kMaxChildren; - if (remainder != 0) { - if (remainder <= kMaxChildren - kMinChildren) { - incrementBy -= remainder; - remainder = 0; - } else { - incrementBy = kMinChildren; - remainder -= kMaxChildren - kMinChildren; - } - } - nodes++; - currentBranch++; - for (int k = 1; k < incrementBy && currentBranch < branches; ++k) { - currentBranch++; - } - } - return nodes + CountNodes(nodes); -} - -FlutterRTree::Branch FlutterRTree::bulkLoad(std::vector* branches, - int level) { - if (branches->size() == 1) { // Only one branch. It will be the root. - return (*branches)[0]; - } - - // We might sort our branches here, but we expect Blink gives us a reasonable - // x,y order. Skipping a call to sort (in Y) here resulted in a 17% win for - // recording with negligible difference in playback speed. - int numBranches = (int)branches->size() / kMaxChildren; - int remainder = (int)branches->size() % kMaxChildren; - int newBranches = 0; - - if (remainder > 0) { - ++numBranches; - // If the remainder isn't enough to fill a node, we'll add fewer nodes to - // other branches. - if (remainder >= kMinChildren) { - remainder = 0; - } else { - remainder = kMinChildren - remainder; - } - } - - int currentBranch = 0; - while (currentBranch < (int)branches->size()) { - int incrementBy = kMaxChildren; - if (remainder != 0) { - // if need be, omit some nodes to make up for remainder - if (remainder <= kMaxChildren - kMinChildren) { - incrementBy -= remainder; - remainder = 0; - } else { - incrementBy = kMinChildren; - remainder -= kMaxChildren - kMinChildren; - } - } - Node* n = allocateNodeAtLevel(level); - n->fNumChildren = 1; - n->fChildren[0] = (*branches)[currentBranch]; - Branch b; - b.fBounds = (*branches)[currentBranch].fBounds; - b.fSubtree = n; - ++currentBranch; - for (int k = 1; k < incrementBy && currentBranch < (int)branches->size(); - ++k) { - b.fBounds.join((*branches)[currentBranch].fBounds); - n->fChildren[k] = (*branches)[currentBranch]; - ++n->fNumChildren; - ++currentBranch; - } - (*branches)[newBranches] = b; - ++newBranches; - } - branches->resize(newBranches); - return this->bulkLoad(branches, level + 1); -} - -void FlutterRTree::search(const SkRect& query, - std::vector* results) const { - if (fCount > 0 && SkRect::Intersects(fRoot.fBounds, query)) { - this->search(fRoot.fSubtree, query, results); - } -} - -void FlutterRTree::search(Node* node, - const SkRect& query, - std::vector* results) const { - for (int i = 0; i < node->fNumChildren; ++i) { - if (!SkRect::Intersects(node->fChildren[i].fBounds, query)) { - continue; - } - if (0 != node->fLevel) { - this->search(node->fChildren[i].fSubtree, query, results); - continue; - } - results->push_back(node->fChildren[i].fOpIndex); - } -} - -void FlutterRTree::searchRects(const SkRect& query, - std::vector* results) const { - if (fCount > 0 && SkRect::Intersects(fRoot.fBounds, query)) { - this->searchRects(fRoot.fSubtree, query, results); - } -} - -void FlutterRTree::getAll(std::vector* results) const { - searchRects(fRoot.fBounds, results); -} - -void FlutterRTree::searchRects(Node* node, - const SkRect& query, - std::vector* results) const { - if (!SkRect::Intersects(fRoot.fBounds, query)) { - return; - } - for (int i = 0; i < node->fNumChildren; ++i) { - if (!SkRect::Intersects(node->fChildren[i].fBounds, query)) { - continue; - } - // Non-leaf node - if (0 != node->fLevel) { - this->searchRects(node->fChildren[i].fSubtree, query, results); - continue; - } - // Ignore records that don't draw anything. - // TODO(egarciad): Figure out if this can be moved to insert(). - if (!node->fChildren[i].isDraw) { - continue; - } - SkRect* currentRecordRect = &node->fChildren[i].fBounds; - std::vector current_results = *results; - bool replaced_existing_rect = false; - // If the current record rect intersects with any of the rects in the - // result vector, then join them, and update the rect in results. - size_t joining_rect_idx = current_results.size(); - size_t result_idx = 0; - while (result_idx < results->size()) { - if (SkRect::Intersects(*current_results[result_idx], - *currentRecordRect)) { - joining_rect_idx = result_idx; - replaced_existing_rect = true; - current_results[joining_rect_idx]->join(*currentRecordRect); - break; - } - result_idx++; - } - result_idx = joining_rect_idx + 1; - while (replaced_existing_rect && result_idx < results->size()) { - if (SkRect::Intersects(*current_results[result_idx], - *current_results[joining_rect_idx])) { - current_results[joining_rect_idx]->join(*current_results[result_idx]); - results->erase(results->begin() + result_idx); - } else { - result_idx++; - } - } - if (!replaced_existing_rect) { - results->push_back(currentRecordRect); - } - } -} - -size_t FlutterRTree::bytesUsed() const { - size_t byteCount = sizeof(FlutterRTree); - byteCount += fNodes.capacity() * sizeof(Node); - return byteCount; -} - -FlutterRTreeFactory::FlutterRTreeFactory(sk_sp& r_tree) - : r_tree_(r_tree) {} - -sk_sp FlutterRTreeFactory::operator()() const { - return r_tree_; -} - -} // namespace flutter diff --git a/lib/ui/painting/flutter_rtree.h b/lib/ui/painting/flutter_rtree.h deleted file mode 100644 index 0b4203261e377..0000000000000 --- a/lib/ui/painting/flutter_rtree.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_LIB_UI_PAINTING_FLUTTER_RTREE_H_ -#define FLUTTER_LIB_UI_PAINTING_FLUTTER_RTREE_H_ - -#include "third_party/skia/include/core/SkBBHFactory.h" -#include "third_party/skia/include/core/SkTypes.h" - -namespace flutter { - -class FlutterRTree : public SkBBoxHierarchy { - public: - FlutterRTree(); - - void insert(const SkRect[], - const SkBBoxHierarchy::Metadata[], - int N) override; - void insert(const SkRect[], int N) override; - void search(const SkRect& query, std::vector* results) const override; - void searchRects(const SkRect& query, std::vector* results) const; - void getAll(std::vector* results) const; - size_t bytesUsed() const override; - - // Methods and constants below here are only public for tests. - - // Return the depth of the tree structure. - int getDepth() const { return fCount ? fRoot.fSubtree->fLevel + 1 : 0; } - // Insertion count (not overall node count, which may be greater). - int getCount() const { return fCount; } - - // These values were empirically determined to produce reasonable performance - // in most cases. - static const int kMinChildren = 6, kMaxChildren = 11; - - private: - struct Node; - - struct Branch { - union { - Node* fSubtree; - int fOpIndex; - }; - bool isDraw; - SkRect fBounds; - }; - - struct Node { - uint16_t fNumChildren; - uint16_t fLevel; - Branch fChildren[kMaxChildren]; - }; - - void search(Node* root, const SkRect& query, std::vector* results) const; - void searchRects(Node* root, - const SkRect& query, - std::vector* results) const; - - // Consumes the input array. - Branch bulkLoad(std::vector* branches, int level = 0); - - // How many times will bulkLoad() call allocateNodeAtLevel()? - static int CountNodes(int branches); - - Node* allocateNodeAtLevel(uint16_t level); - - // This is the count of data elements (rather than total nodes in the tree) - int fCount; - Branch fRoot; - std::vector fNodes; -}; - -class FlutterRTreeFactory : public SkBBHFactory { - public: - FlutterRTreeFactory(sk_sp& r_tree); - sk_sp operator()() const override; - - private: - sk_sp r_tree_; -}; - -} // namespace flutter - -#endif // FLUTTER_LIB_UI_PAINTING_PATCHED_R_TREE_H_ diff --git a/lib/ui/painting/flutter_rtree_unittests.cc b/lib/ui/painting/flutter_rtree_unittests.cc deleted file mode 100644 index 7e231dfefcf47..0000000000000 --- a/lib/ui/painting/flutter_rtree_unittests.cc +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "flutter/lib/ui/painting/flutter_rtree.h" -#include "flutter/testing/testing.h" -#include "flutter/testing/thread_test.h" - -#include "third_party/skia/include/core/SkCanvas.h" -#include "third_party/skia/include/core/SkPictureRecorder.h" - -namespace flutter { -namespace testing { - -using FlutterRTreeTest = ThreadTest; - -TEST_F(FlutterRTreeTest, NoIntersection) { - auto r_tree = sk_make_sp(); - auto rtree_factory = FlutterRTreeFactory(r_tree); - auto recorder = std::make_unique(); - auto recording_canvas = - recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); - - auto rect_paint = SkPaint(); - rect_paint.setColor(SkColors::kCyan); - rect_paint.setStyle(SkPaint::Style::kFill_Style); - - // If no rect is intersected with the query rect, then the result vector is - // empty. - recording_canvas->drawRect(SkRect::MakeLTRB(20, 20, 40, 40), rect_paint); - recorder->finishRecordingAsPicture(); - - auto hits = std::vector(); - auto query = SkRect::MakeLTRB(40, 40, 80, 80); - - r_tree->searchRects(query, &hits); - ASSERT_TRUE(hits.empty()); -} - -TEST_F(FlutterRTreeTest, Intersection) { - auto r_tree = sk_make_sp(); - auto rtree_factory = FlutterRTreeFactory(r_tree); - auto recorder = std::make_unique(); - auto recording_canvas = - recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); - - auto rect_paint = SkPaint(); - rect_paint.setColor(SkColors::kCyan); - rect_paint.setStyle(SkPaint::Style::kFill_Style); - - // Given a single rect A that intersects with the query rect, - // the result vector contains this rect. - recording_canvas->drawRect(SkRect::MakeLTRB(120, 120, 160, 160), rect_paint); - - recorder->finishRecordingAsPicture(); - - auto query = SkRect::MakeLTRB(140, 140, 150, 150); - auto hits = std::vector(); - - r_tree->searchRects(query, &hits); - ASSERT_EQ(1UL, hits.size()); - ASSERT_EQ(*hits[0], SkRect::MakeLTRB(120, 120, 160, 160)); -} - -TEST_F(FlutterRTreeTest, JoinRectsWhenIntersectedCase1) { - auto r_tree = sk_make_sp(); - auto rtree_factory = FlutterRTreeFactory(r_tree); - auto recorder = std::make_unique(); - auto recording_canvas = - recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); - - auto rect_paint = SkPaint(); - rect_paint.setColor(SkColors::kCyan); - rect_paint.setStyle(SkPaint::Style::kFill_Style); - - // Given the A, and B rects, which intersect with the query rect, - // the result vector contains the rect resulting from the union of A and B. - // - // +-----+ - // | A | - // | +-----+ - // | | C | - // | +-----+ - // | | - // +-----+ - - // A - recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 150, 150), rect_paint); - // B - recording_canvas->drawRect(SkRect::MakeLTRB(125, 125, 175, 175), rect_paint); - - recorder->finishRecordingAsPicture(); - - auto query = SkRect::MakeXYWH(120, 120, 126, 126); - auto hits = std::vector(); - - r_tree->searchRects(query, &hits); - ASSERT_EQ(1UL, hits.size()); - ASSERT_EQ(*hits[0], SkRect::MakeLTRB(100, 100, 175, 175)); -} - -TEST_F(FlutterRTreeTest, JoinRectsWhenIntersectedCase2) { - auto r_tree = sk_make_sp(); - auto rtree_factory = FlutterRTreeFactory(r_tree); - auto recorder = std::make_unique(); - auto recording_canvas = - recorder->beginRecording(SkRect::MakeIWH(1000, 1000), &rtree_factory); - - auto rect_paint = SkPaint(); - rect_paint.setColor(SkColors::kCyan); - rect_paint.setStyle(SkPaint::Style::kFill_Style); - - // Given the A, B, and C rects that intersect with the query rect, - // there should be only C in the result vector, - // since A and B are contained in C. - // - // +---------------------+ - // | C | - // | +-----+ +-----+ | - // | | A | | B | | - // | +-----+ +-----+ | - // +---------------------+ - // +-----+ - // | D | - // +-----+ - - // A - recording_canvas->drawRect(SkRect::MakeLTRB(100, 100, 200, 200), rect_paint); - // B - recording_canvas->drawRect(SkRect::MakeLTRB(300, 100, 400, 200), rect_paint); - // C - recording_canvas->drawRect(SkRect::MakeLTRB(50, 50, 500, 250), rect_paint); - // D - recording_canvas->drawRect(SkRect::MakeLTRB(280, 100, 280, 320), rect_paint); - - recorder->finishRecordingAsPicture(); - - auto query = SkRect::MakeLTRB(30, 30, 550, 270); - auto hits = std::vector(); - - r_tree->searchRects(query, &hits); - ASSERT_EQ(1UL, hits.size()); - ASSERT_EQ(*hits[0], SkRect::MakeLTRB(50, 50, 500, 250)); -} - -} // namespace testing -} // namespace flutter diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 4b1c3f8d5098e..00dc7609f31df 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -8,13 +8,14 @@ #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/lib/ui/painting/flutter_rtree.h" #include "flutter/shell/common/persistent_cache.h" #include "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" @@ -258,9 +259,9 @@ int view_id, std::unique_ptr params) { platform_views_recorder_[view_id] = std::make_unique(); - platform_views_bbh_[view_id] = sk_make_sp(); - auto bbh_factory = FlutterRTreeFactory(platform_views_bbh_[view_id]); + auto bbh_factory = RTreeFactory(); + platform_views_bbh_[view_id] = bbh_factory.getInstance(); platform_views_recorder_[view_id]->beginRecording(SkRect::Make(frame_size_), &bbh_factory); composition_order_.push_back(view_id); @@ -463,7 +464,7 @@ for (size_t i = 0; i < num_platform_views; i++) { int platform_view_id = composition_order_[i]; - FlutterRTree* bbh = platform_views_bbh_[platform_view_id].get(); + auto bbh = platform_views_bbh_[platform_view_id].get(); sk_sp picture = platform_views_recorder_[platform_view_id]->finishRecordingAsPicture(); @@ -472,8 +473,8 @@ SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id); - std::vector intersection_rects; - bbh->searchRects(platform_view_rect, &intersection_rects); + std::list intersection_rects; + bbh->searchNonOverlappingDrawnRects(platform_view_rect, &intersection_rects); size_t allocation_size = intersection_rects.size(); int64_t overlay_count = platform_view_layers[current_platform_view_id].size(); @@ -481,8 +482,8 @@ // If the max number of allocations per platform view is exceeded, // then join all the rects into a single one. SkRect joined_rect; - for (SkRect* rect : intersection_rects) { - joined_rect.join(*rect); + for (SkRect rect : intersection_rects) { + joined_rect.join(rect); } // Get the intersection rect between the current joined rect // and the platform view rect. @@ -502,10 +503,9 @@ did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); } else if (allocation_size > 0) { - for (SkRect* rect : intersection_rects) { + for (SkRect joined_rect : intersection_rects) { // Get the intersection rect between the current rect // and the platform view rect. - SkRect joined_rect = *rect; joined_rect.intersect(platform_view_rect); // Clip the background canvas, so it doesn't contain any of the pixels drawn diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index f5d583899da57..2fc93e3310b8f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -6,8 +6,8 @@ #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/lib/ui/painting/flutter_rtree.h" #include "flutter/shell/common/shell.h" #include "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #include "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" @@ -150,7 +150,7 @@ class FlutterPlatformViewsController { // The pool of reusable view layers. std::unique_ptr layer_pool_; - std::map> platform_views_bbh_; + std::map> platform_views_bbh_; std::map platform_views_params_; std::map> platform_views_recorder_; From 207beb4efe46f2513d9640c28bdc56eed31e3a0a Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 9 Mar 2020 21:21:23 -0700 Subject: [PATCH 31/44] Clean up --- flow/layers/image_filter_layer.cc | 6 +- flow/layers/layer_tree.cc | 2 +- .../framework/Source/FlutterPlatformViews.mm | 56 +++++++++---------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index 164fb5077465c..119fddc181fb8 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -30,9 +30,9 @@ void ImageFilterLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting()); #ifndef SUPPORT_FRACTIONAL_TRANSLATION - SkAutoCanvasRestore save(context.internal_nodes_canvas, true); - context.internal_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( - context.internal_nodes_canvas->getTotalMatrix())); + SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); + context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); #endif if (context.raster_cache) { diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index ca24a330a6593..37c28e861bf8a 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -169,7 +169,7 @@ sk_sp LayerTree::Flatten(const SkRect& bounds) { Layer::PaintContext paint_context = { (SkCanvas*)&internal_nodes_canvas, - canvas, // leaf node canvas + canvas, // canvas nullptr, nullptr, unused_stopwatch, // frame time (dont care) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 00dc7609f31df..cff75142ab068 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -462,25 +462,23 @@ size_t num_platform_views = composition_order_.size(); for (size_t i = 0; i < num_platform_views; i++) { - int platform_view_id = composition_order_[i]; + auto platform_view_id = composition_order_[i]; auto bbh = platform_views_bbh_[platform_view_id].get(); sk_sp picture = platform_views_recorder_[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--) { - int current_platform_view_id = composition_order_[j - 1]; - - SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id); - - std::list intersection_rects; - bbh->searchNonOverlappingDrawnRects(platform_view_rect, &intersection_rects); - - size_t allocation_size = intersection_rects.size(); - int64_t overlay_count = platform_view_layers[current_platform_view_id].size(); + auto current_platform_view_id = composition_order_[j - 1]; + auto platform_view_rect = GetPlatformViewRect(current_platform_view_id); + auto intersection_rects = bbh->searchNonOverlappingDrawnRects(platform_view_rect); + auto allocation_size = intersection_rects.size(); + auto overlay_count = 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. if (allocation_size > kMaxLayerAllocations) { - // If the max number of allocations per platform view is exceeded, - // then join all the rects into a single one. SkRect joined_rect; for (SkRect rect : intersection_rects) { joined_rect.join(rect); @@ -488,11 +486,10 @@ // Get the intersection rect between the current joined 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, // gl_context, // picture, // @@ -507,11 +504,10 @@ // 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, // gl_context, // picture, // @@ -527,23 +523,26 @@ } 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); - - composition_order_.clear(); + // 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(); - int zIndex = 0; + auto flutter_view = flutter_view_.get(); + auto zIndex = 0; for (size_t i = 0; i < composition_order_.size(); i++) { - int platform_view_id = composition_order_[i]; + auto platform_view_id = composition_order_[i]; auto layers = layer_map[platform_view_id]; - UIView* platform_view_root = root_views_[platform_view_id].get(); + auto platform_view_root = root_views_[platform_view_id].get(); if (platform_view_root.superview != flutter_view) { [flutter_view addSubview:platform_view_root]; @@ -568,8 +567,8 @@ SkRect rect, int64_t view_id, int64_t overlay_id) { - std::shared_ptr layer = layer_pool_->GetLayer(gr_context, ios_context); - CGFloat screenScale = [UIScreen mainScreen].scale; + 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, // @@ -580,14 +579,13 @@ layer->overlay_view.get().accessibilityIdentifier = [NSString stringWithFormat:@"platform_view[%lld].overlay[%lld]", view_id, overlay_id]; - SkISize rect_size = SkISize::Make(rect.width(), rect.height()); - std::unique_ptr frame = layer->surface->AcquireFrame(rect_size); - + 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 == nullptr) { return layer; } - SkCanvas* overlay_canvas = frame->SkiaCanvas(); + auto overlay_canvas = frame->SkiaCanvas(); overlay_canvas->clear(SK_ColorTRANSPARENT); overlay_canvas->translate(-rect.x(), -rect.y()); overlay_canvas->drawPicture(picture); From f6d1269db75d782f7340b325cfd38ed579b07818 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 10 Mar 2020 17:11:19 -0700 Subject: [PATCH 32/44] Remove duplicated entry after rebase --- lib/ui/BUILD.gn | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 25c20218a428d..2077c113b32fe 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -169,7 +169,6 @@ if (current_toolchain == host_toolchain) { "painting/image_decoder_test.cc", "painting/image_decoder_test.h", "painting/image_decoder_unittests.cc", - "painting/image_decoder_unittests.cc", "window/pointer_data_packet_converter_unittests.cc", ] From af7b91624a739700081c448d32e90ba8c9e3dd60 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 10 Mar 2020 17:16:47 -0700 Subject: [PATCH 33/44] Remove dependency --- lib/ui/BUILD.gn | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 2077c113b32fe..4d3210d3bef37 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -165,7 +165,6 @@ if (current_toolchain == host_toolchain) { configs += [ "//flutter:export_dynamic_symbols" ] sources = [ - "painting/flutter_rtree_unittests.cc", "painting/image_decoder_test.cc", "painting/image_decoder_test.h", "painting/image_decoder_unittests.cc", From 4100603d89bfa63ea393f6bdf213a29ff98b26ef Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 10 Mar 2020 17:42:41 -0700 Subject: [PATCH 34/44] Revert changes from rebase --- shell/platform/darwin/ios/ios_surface_gl.h | 25 ------ shell/platform/darwin/ios/ios_surface_gl.mm | 79 +------------------ shell/platform/darwin/ios/ios_surface_metal.h | 30 +------ .../darwin/ios/ios_surface_software.h | 22 ------ .../darwin/ios/ios_surface_software.mm | 60 +------------- 5 files changed, 5 insertions(+), 211 deletions(-) diff --git a/shell/platform/darwin/ios/ios_surface_gl.h b/shell/platform/darwin/ios/ios_surface_gl.h index d20e085d6e15e..3cbc6ebd047ad 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.h +++ b/shell/platform/darwin/ios/ios_surface_gl.h @@ -51,31 +51,6 @@ class IOSSurfaceGL final : public IOSSurface, public GPUSurfaceGLDelegate { // |GPUSurfaceGLDelegate| ExternalViewEmbedder* GetExternalViewEmbedder() override; - // |ExternalViewEmbedder| - SkCanvas* GetRootCanvas() override; - - // |ExternalViewEmbedder| - void CancelFrame() override; - - // |ExternalViewEmbedder| - void BeginFrame(SkISize frame_size, GrContext* context, double device_pixel_ratio) override; - - // |ExternalViewEmbedder| - void PrerollCompositeEmbeddedView(int view_id, - std::unique_ptr params) override; - - // |ExternalViewEmbedder| - PostPrerollResult PostPrerollAction(fml::RefPtr gpu_thread_merger) override; - - // |ExternalViewEmbedder| - std::vector GetCurrentCanvases() override; - - // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; - - // |ExternalViewEmbedder| - bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; - private: std::unique_ptr render_target_; diff --git a/shell/platform/darwin/ios/ios_surface_gl.mm b/shell/platform/darwin/ios/ios_surface_gl.mm index 52ae09b8538d6..b3aa25a4e66cd 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/shell/platform/darwin/ios/ios_surface_gl.mm @@ -78,82 +78,9 @@ return IsValid() && render_target_->PresentRenderBuffer(); } -// |ExternalViewEmbedder| -SkCanvas* IOSSurfaceGL::GetRootCanvas() { - // On iOS, the root surface is created from the on-screen render target. Only the surfaces for the - // various overlays are controlled by this class. - return nullptr; -} - -// |ExternalViewEmbedder| -flutter::ExternalViewEmbedder* IOSSurfaceGL::GetExternalViewEmbedder() { - if (IsIosEmbeddedViewsPreviewEnabled()) { - return this; - } else { - return nullptr; - } -} - -// |ExternalViewEmbedder| -void IOSSurfaceGL::CancelFrame() { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - platform_views_controller->CancelFrame(); - // Committing the current transaction as |BeginFrame| will create a nested - // CATransaction otherwise. - [CATransaction commit]; -} - -// |ExternalViewEmbedder| -void IOSSurfaceGL::BeginFrame(SkISize frame_size, GrContext* context, double device_pixel_ratio) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - platform_views_controller->SetFrameSize(frame_size); - [CATransaction begin]; -} - -// |ExternalViewEmbedder| -void IOSSurfaceGL::PrerollCompositeEmbeddedView( - int view_id, - std::unique_ptr params) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - platform_views_controller->PrerollCompositeEmbeddedView(view_id, std::move(params)); -} - -// |ExternalViewEmbedder| -PostPrerollResult IOSSurfaceGL::PostPrerollAction( - fml::RefPtr gpu_thread_merger) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - return platform_views_controller->PostPrerollAction(gpu_thread_merger); -} - -// |ExternalViewEmbedder| -std::vector IOSSurfaceGL::GetCurrentCanvases() { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - return platform_views_controller->GetCurrentCanvases(); -} - -// |ExternalViewEmbedder| -SkCanvas* IOSSurfaceGL::CompositeEmbeddedView(int view_id) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - return platform_views_controller->CompositeEmbeddedView(view_id); -} - -// |ExternalViewEmbedder| -bool IOSSurfaceGL::SubmitFrame(GrContext* context, SkCanvas* background_canvas) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - if (platform_views_controller == nullptr) { - return true; - } - - bool submitted = platform_views_controller->SubmitFrame(std::move(context), context_, - std::move(background_canvas)); - [CATransaction commit]; - return submitted; +// |GPUSurfaceGLDelegate| +ExternalViewEmbedder* IOSSurfaceGL::GetExternalViewEmbedder() { + return GetExternalViewEmbedderIfEnabled(); } } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface_metal.h b/shell/platform/darwin/ios/ios_surface_metal.h index 2a35325b9307b..e700bc02a3eb2 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.h +++ b/shell/platform/darwin/ios/ios_surface_metal.h @@ -36,35 +36,7 @@ class IOSSurfaceMetal final : public IOSSurface, public GPUSurfaceDelegate { std::unique_ptr CreateGPUSurface(GrContext* gr_context) override; // |GPUSurfaceDelegate| - flutter::ExternalViewEmbedder* GetExternalViewEmbedder() override; - - // |ExternalViewEmbedder| - SkCanvas* GetRootCanvas() override; - - // |ExternalViewEmbedder| - void CancelFrame() override; - - // |ExternalViewEmbedder| - void BeginFrame(SkISize frame_size, GrContext* context, double device_pixel_ratio) override; - - // |ExternalViewEmbedder| - void PrerollCompositeEmbeddedView(int view_id, - std::unique_ptr params) override; - - // |ExternalViewEmbedder| - PostPrerollResult PostPrerollAction(fml::RefPtr gpu_thread_merger) override; - - // |ExternalViewEmbedder| - std::vector GetCurrentCanvases() override; - - // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; - - // |ExternalViewEmbedder| - bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; - - private: - fml::scoped_nsobject layer_; + ExternalViewEmbedder* GetExternalViewEmbedder() override; FML_DISALLOW_COPY_AND_ASSIGN(IOSSurfaceMetal); }; diff --git a/shell/platform/darwin/ios/ios_surface_software.h b/shell/platform/darwin/ios/ios_surface_software.h index 78b2f8e4825dc..86e6b6b209785 100644 --- a/shell/platform/darwin/ios/ios_surface_software.h +++ b/shell/platform/darwin/ios/ios_surface_software.h @@ -42,28 +42,6 @@ class IOSSurfaceSoftware final : public IOSSurface, public GPUSurfaceSoftwareDel // |GPUSurfaceSoftwareDelegate| ExternalViewEmbedder* GetExternalViewEmbedder() override; - // |ExternalViewEmbedder| - SkCanvas* GetRootCanvas() override; - - // |ExternalViewEmbedder| - void CancelFrame() override; - - // |ExternalViewEmbedder| - void BeginFrame(SkISize frame_size, GrContext* context, double device_pixel_ratio) override; - - // |ExternalViewEmbedder| - void PrerollCompositeEmbeddedView(int view_id, - std::unique_ptr params) override; - - // |ExternalViewEmbedder| - std::vector GetCurrentCanvases() override; - - // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; - - // |ExternalViewEmbedder| - bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; - private: fml::scoped_nsobject layer_; sk_sp sk_surface_; diff --git a/shell/platform/darwin/ios/ios_surface_software.mm b/shell/platform/darwin/ios/ios_surface_software.mm index fd43533e0b3d0..06abe29c661e8 100644 --- a/shell/platform/darwin/ios/ios_surface_software.mm +++ b/shell/platform/darwin/ios/ios_surface_software.mm @@ -124,65 +124,7 @@ // |GPUSurfaceSoftwareDelegate| ExternalViewEmbedder* IOSSurfaceSoftware::GetExternalViewEmbedder() { - if (IsIosEmbeddedViewsPreviewEnabled()) { - return this; - } else { - return nullptr; - } -} - -// |ExternalViewEmbedder| -SkCanvas* IOSSurfaceSoftware::GetRootCanvas() { - // On iOS, the root surface is created using a managed allocation that is submitted to the - // platform. Only the surfaces for the various overlays are controlled by this class. - return nullptr; -} - -// |ExternalViewEmbedder| -void IOSSurfaceSoftware::CancelFrame() { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - platform_views_controller->CancelFrame(); -} - -// |ExternalViewEmbedder| -void IOSSurfaceSoftware::BeginFrame(SkISize frame_size, - GrContext* context, - double device_pixel_ratio) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - platform_views_controller->SetFrameSize(frame_size); -} - -// |ExternalViewEmbedder| -void IOSSurfaceSoftware::PrerollCompositeEmbeddedView(int view_id, - std::unique_ptr params) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - platform_views_controller->PrerollCompositeEmbeddedView(view_id, std::move(params)); -} - -// |ExternalViewEmbedder| -std::vector IOSSurfaceSoftware::GetCurrentCanvases() { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - return platform_views_controller->GetCurrentCanvases(); -} - -// |ExternalViewEmbedder| -SkCanvas* IOSSurfaceSoftware::CompositeEmbeddedView(int view_id) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - FML_CHECK(platform_views_controller != nullptr); - return platform_views_controller->CompositeEmbeddedView(view_id); -} - -// |ExternalViewEmbedder| -bool IOSSurfaceSoftware::SubmitFrame(GrContext* context, SkCanvas* background_canvas) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - if (platform_views_controller == nullptr) { - return true; - } - return platform_views_controller->SubmitFrame(nullptr, nullptr, background_canvas); + return GetExternalViewEmbedderIfEnabled(); } } // namespace flutter From 0c06c48a4733389117abfbc07099db3516ab7dc1 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 10 Mar 2020 17:42:51 -0700 Subject: [PATCH 35/44] Delete transient files --- testing/scenario_app/ios/Flutter/Generated.xcconfig | 9 --------- .../ios/Flutter/flutter_export_environment.sh | 10 ---------- 2 files changed, 19 deletions(-) delete mode 100644 testing/scenario_app/ios/Flutter/Generated.xcconfig delete mode 100755 testing/scenario_app/ios/Flutter/flutter_export_environment.sh diff --git a/testing/scenario_app/ios/Flutter/Generated.xcconfig b/testing/scenario_app/ios/Flutter/Generated.xcconfig deleted file mode 100644 index f85df1d4a283d..0000000000000 --- a/testing/scenario_app/ios/Flutter/Generated.xcconfig +++ /dev/null @@ -1,9 +0,0 @@ -// This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=/Users/egarciad/p/flutter -FLUTTER_APPLICATION_PATH=/Users/egarciad/p/engine/src/flutter/testing/scenario_app -FLUTTER_TARGET=lib/main.dart -FLUTTER_BUILD_DIR=build -SYMROOT=${SOURCE_ROOT}/../build/ios -FLUTTER_FRAMEWORK_DIR=/Users/egarciad/p/flutter/bin/cache/artifacts/engine/ios -FLUTTER_BUILD_NAME=1.0.0 -FLUTTER_BUILD_NUMBER=1 diff --git a/testing/scenario_app/ios/Flutter/flutter_export_environment.sh b/testing/scenario_app/ios/Flutter/flutter_export_environment.sh deleted file mode 100755 index cae81ba3f898c..0000000000000 --- a/testing/scenario_app/ios/Flutter/flutter_export_environment.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -# This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/Users/egarciad/p/flutter" -export "FLUTTER_APPLICATION_PATH=/Users/egarciad/p/engine/src/flutter/testing/scenario_app" -export "FLUTTER_TARGET=lib/main.dart" -export "FLUTTER_BUILD_DIR=build" -export "SYMROOT=${SOURCE_ROOT}/../build/ios" -export "FLUTTER_FRAMEWORK_DIR=/Users/egarciad/p/flutter/bin/cache/artifacts/engine/ios" -export "FLUTTER_BUILD_NAME=1.0.0" -export "FLUTTER_BUILD_NUMBER=1" From f7054622a16a55afded7efcf08bf7dcd24b1ee9c Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 10 Mar 2020 18:21:36 -0700 Subject: [PATCH 36/44] nits --- flow/layers/picture_layer.cc | 1 - shell/common/rasterizer.cc | 6 ++++-- .../ios/framework/Source/FlutterPlatformViews.mm | 10 +++++----- .../Source/FlutterPlatformViews_Internal.h | 14 +++++++++++--- shell/platform/darwin/ios/ios_surface.h | 2 +- shell/platform/darwin/ios/ios_surface.mm | 5 +++-- 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index 6e776ee756588..08c09cc9e833b 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -44,7 +44,6 @@ void PictureLayer::Paint(PaintContext& context) const { SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); context.leaf_nodes_canvas->translate(offset_.x(), offset_.y()); - #ifndef SUPPORT_FRACTIONAL_TRANSLATION context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( context.leaf_nodes_canvas->getTotalMatrix())); diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 399096332eba3..0fe40eb896584 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -343,12 +343,14 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { if (raster_status == RasterStatus::kFailed) { return raster_status; } - if (external_view_embedder != nullptr) { 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(); FireNextFrameCallbackIfPresent(); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index cff75142ab068..2da2a785de5e1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -261,7 +261,7 @@ platform_views_recorder_[view_id] = std::make_unique(); auto bbh_factory = RTreeFactory(); - platform_views_bbh_[view_id] = bbh_factory.getInstance(); + platform_views_bbh_[view_id] = bbh_factory.getInstance().get(); platform_views_recorder_[view_id]->beginRecording(SkRect::Make(frame_size_), &bbh_factory); composition_order_.push_back(view_id); @@ -374,7 +374,7 @@ // // The UIKit frame is set based on the logical resolution instead of physical. // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html). - // However, flow is based on the physical resol ution. For eaxmple, 1000 pixels in flow equals + // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals // 500 points in UIKit. And until this point, we did all the calculation based on the flow // resolution. So we need to scale down to match UIKit's logical resolution. CGFloat screenScale = [UIScreen mainScreen].scale; @@ -464,7 +464,7 @@ for (size_t i = 0; i < num_platform_views; i++) { auto platform_view_id = composition_order_[i]; - auto bbh = platform_views_bbh_[platform_view_id].get(); + auto bbh = platform_views_bbh_[platform_view_id]; sk_sp picture = platform_views_recorder_[platform_view_id]->finishRecordingAsPicture(); @@ -491,7 +491,7 @@ background_canvas->clipRect(joined_rect, SkClipOp::kDifference); // Get a new host layer. auto layer = GetLayer(gr_context, // - gl_context, // + ios_context, // picture, // joined_rect, // current_platform_view_id, // @@ -509,7 +509,7 @@ background_canvas->clipRect(joined_rect, SkClipOp::kDifference); // Get a new host layer. auto layer = GetLayer(gr_context, // - gl_context, // + ios_context, // picture, // joined_rect, // current_platform_view_id, // diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 2fc93e3310b8f..b1087cd1fcd3d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -148,12 +148,20 @@ class FlutterPlatformViewsController { using LayersMap = std::map>>; - // The pool of reusable view layers. + // The pool of reusable view layers. The pool allows to recycle layer in each frame. std::unique_ptr layer_pool_; - std::map> platform_views_bbh_; - std::map platform_views_params_; + + // The platform view's BBoxHierarchy 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. + std::map platform_views_bbh_; + + // 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> platform_views_recorder_; + // The platform view's parameters used to compose the scene keyed off the view id. + std::map platform_views_params_; + fml::scoped_nsobject channel_; fml::scoped_nsobject flutter_view_; fml::scoped_nsobject flutter_view_controller_; diff --git a/shell/platform/darwin/ios/ios_surface.h b/shell/platform/darwin/ios/ios_surface.h index 43049d6c367b8..a54ad64f8fac1 100644 --- a/shell/platform/darwin/ios/ios_surface.h +++ b/shell/platform/darwin/ios/ios_surface.h @@ -79,7 +79,7 @@ class IOSSurface : public ExternalViewEmbedder { SkCanvas* CompositeEmbeddedView(int view_id) override; // |ExternalViewEmbedder| - bool SubmitFrame(GrContext* context) override; + bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) 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 d0a931f7b1f46..cc7ff686c3be1 100644 --- a/shell/platform/darwin/ios/ios_surface.mm +++ b/shell/platform/darwin/ios/ios_surface.mm @@ -136,10 +136,11 @@ 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_); + bool submitted = + platform_views_controller_->SubmitFrame(std::move(context), ios_context_, background_canvas); [CATransaction commit]; return submitted; } From 71e5bebf5e4cef355917988a6dc0a5525952b46d Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 10 Mar 2020 18:34:46 -0700 Subject: [PATCH 37/44] Pass ios_context --- .../darwin/ios/framework/Source/FlutterPlatformViews.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 2da2a785de5e1..25ccf75793456 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -31,7 +31,8 @@ fml::scoped_nsobject overlay_view([[FlutterOverlayView alloc] init]); overlay_view.get().autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); - std::unique_ptr ios_surface = [overlay_view.get() createSurface:nil]; + std::unique_ptr ios_surface = + [overlay_view.get() createSurface:std::move(ios_context)]; std::unique_ptr surface = ios_surface->CreateGPUSurface(); layer = std::make_shared( From d4f4386202ff380731a32f737028571a36adf1a7 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 12 Mar 2020 16:07:13 -0700 Subject: [PATCH 38/44] comments --- .../framework/Source/FlutterPlatformViews.mm | 62 ++++++++----------- .../Source/FlutterPlatformViews_Internal.h | 20 ++++-- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 25ccf75793456..0c642bf1b3ab7 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -259,20 +259,20 @@ void FlutterPlatformViewsController::PrerollCompositeEmbeddedView( int view_id, std::unique_ptr params) { - platform_views_recorder_[view_id] = std::make_unique(); + picture_recorders_[view_id] = std::make_unique(); auto bbh_factory = RTreeFactory(); platform_views_bbh_[view_id] = bbh_factory.getInstance().get(); - platform_views_recorder_[view_id]->beginRecording(SkRect::Make(frame_size_), &bbh_factory); + picture_recorders_[view_id]->beginRecording(SkRect::Make(frame_size_), &bbh_factory); composition_order_.push_back(view_id); - if (platform_views_params_.count(view_id) == 1 && - platform_views_params_[view_id] == *params.get()) { + if (current_composition_params_.count(view_id) == 1 && + current_composition_params_[view_id] == *params.get()) { // Do nothing if the params didn't change. return; } - platform_views_params_[view_id] = EmbeddedViewParams(*params.get()); + current_composition_params_[view_id] = EmbeddedViewParams(*params.get()); views_to_recomposite_.insert(view_id); } @@ -287,7 +287,7 @@ std::vector canvases; for (size_t i = 0; i < composition_order_.size(); i++) { int64_t view_id = composition_order_[i]; - canvases.push_back(platform_views_recorder_[view_id]->getRecordingCanvas()); + canvases.push_back(picture_recorders_[view_id]->getRecordingCanvas()); } return canvases; } @@ -411,11 +411,11 @@ // Do nothing if the view doesn't need to be composited. if (views_to_recomposite_.count(view_id) == 0) { - return platform_views_recorder_[view_id]->getRecordingCanvas(); + return picture_recorders_[view_id]->getRecordingCanvas(); } - CompositeWithParams(view_id, platform_views_params_[view_id]); + CompositeWithParams(view_id, current_composition_params_[view_id]); views_to_recomposite_.erase(view_id); - return platform_views_recorder_[view_id]->getRecordingCanvas(); + return picture_recorders_[view_id]->getRecordingCanvas(); } void FlutterPlatformViewsController::Reset() { @@ -427,9 +427,9 @@ overlays_.clear(); composition_order_.clear(); active_composition_order_.clear(); - platform_views_recorder_.clear(); + picture_recorders_.clear(); platform_views_bbh_.clear(); - platform_views_params_.clear(); + current_composition_params_.clear(); clip_count_.clear(); views_to_recomposite_.clear(); layer_pool_->RecycleLayers(); @@ -439,7 +439,7 @@ UIView* platform_view = [views_[view_id].get() view]; UIScreen* screen = [UIScreen mainScreen]; CGRect platform_view_cgrect = [platform_view convertRect:platform_view.bounds - toCoordinateSpace:screen.coordinateSpace]; + 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, // @@ -464,10 +464,8 @@ size_t num_platform_views = composition_order_.size(); for (size_t i = 0; i < num_platform_views; i++) { auto platform_view_id = composition_order_[i]; - auto bbh = platform_views_bbh_[platform_view_id]; - sk_sp picture = - platform_views_recorder_[platform_view_id]->finishRecordingAsPicture(); + 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. @@ -477,6 +475,7 @@ auto intersection_rects = bbh->searchNonOverlappingDrawnRects(platform_view_rect); auto allocation_size = intersection_rects.size(); auto overlay_count = 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. if (allocation_size > kMaxLayerAllocations) { @@ -484,7 +483,13 @@ for (SkRect rect : intersection_rects) { joined_rect.join(rect); } - // Get the intersection rect between the current joined 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 @@ -500,26 +505,7 @@ ); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); - } else if (allocation_size > 0) { - 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_count // - ); - did_submit &= layer->did_submit_last_frame; - platform_view_layers[current_platform_view_id].push_back(layer); - overlay_count++; - } + overlay_count++; } } background_canvas->drawPicture(picture); @@ -588,6 +574,8 @@ } 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); @@ -630,7 +618,7 @@ [overlays_[viewId]->overlay_view.get() removeFromSuperview]; } overlays_.erase(viewId); - platform_views_params_.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 b1087cd1fcd3d..87245e6b6e05a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -76,6 +76,7 @@ struct FlutterPlatformViewLayer { GrContext* gr_context; }; +// This class isn't thread safe. class FlutterPlatformViewLayerPool { public: FlutterPlatformViewLayerPool() = default; @@ -93,8 +94,22 @@ class FlutterPlatformViewLayerPool { void RecycleLayers(); private: + // The index of the entry in the layer_ 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> layer_; + + FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewLayerPool); }; class FlutterPlatformViewsController { @@ -157,10 +172,7 @@ class FlutterPlatformViewsController { // 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> platform_views_recorder_; - - // The platform view's parameters used to compose the scene keyed off the view id. - std::map platform_views_params_; + std::map> picture_recorders_; fml::scoped_nsobject channel_; fml::scoped_nsobject flutter_view_; From 7f2d64ebd68fb9d247fd8e741202abaffd55a3d2 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Thu, 12 Mar 2020 16:23:03 -0700 Subject: [PATCH 39/44] Add todo --- .../darwin/ios/framework/Source/FlutterPlatformViews.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 0c642bf1b3ab7..159a14730563b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -478,6 +478,9 @@ // 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 (SkRect rect : intersection_rects) { From f48860fdaad848c4be0e477b702afbb57d0cb715 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 13 Mar 2020 18:25:16 -0700 Subject: [PATCH 40/44] Add FinishFrame hook to embedded_views --- flow/embedded_views.h | 3 +++ shell/common/rasterizer.cc | 13 ++++++++----- shell/platform/darwin/ios/ios_surface.h | 3 +++ shell/platform/darwin/ios/ios_surface.mm | 7 ++++++- shell/platform/darwin/ios/ios_surface_metal.mm | 6 ++++-- .../embedder/embedder_external_view_embedder.cc | 3 +++ .../embedder/embedder_external_view_embedder.h | 3 +++ 7 files changed, 30 insertions(+), 8 deletions(-) diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 71f9bdaf5c98e..7a491a8a152ef 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -250,6 +250,9 @@ class ExternalViewEmbedder { 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); }; // ExternalViewEmbedder diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 0fe40eb896584..87de4128d5c50 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -346,12 +346,15 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { if (external_view_embedder != nullptr) { 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(); } - // 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(); FireNextFrameCallbackIfPresent(); diff --git a/shell/platform/darwin/ios/ios_surface.h b/shell/platform/darwin/ios/ios_surface.h index a54ad64f8fac1..ca6cf9584897a 100644 --- a/shell/platform/darwin/ios/ios_surface.h +++ b/shell/platform/darwin/ios/ios_surface.h @@ -81,6 +81,9 @@ class IOSSurface : public ExternalViewEmbedder { // |ExternalViewEmbedder| 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 cc7ff686c3be1..0395e17978ce9 100644 --- a/shell/platform/darwin/ios/ios_surface.mm +++ b/shell/platform/darwin/ios/ios_surface.mm @@ -141,8 +141,13 @@ bool IsIosEmbeddedViewsPreviewEnabled() { FML_CHECK(platform_views_controller_ != nullptr); bool submitted = platform_views_controller_->SubmitFrame(std::move(context), ios_context_, background_canvas); - [CATransaction commit]; return submitted; } +// |ExternalViewEmbedder| +void IOSSurface::FinishFrame() { + TRACE_EVENT0("flutter", "IOSSurface::DidSubmitFrame"); + [CATransaction commit]; +} + } // namespace flutter diff --git a/shell/platform/darwin/ios/ios_surface_metal.mm b/shell/platform/darwin/ios/ios_surface_metal.mm index 9255e74e2d6fa..aea1f9dba6ed5 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.mm +++ b/shell/platform/darwin/ios/ios_surface_metal.mm @@ -59,9 +59,11 @@ return true; } - bool submitted = platform_views_controller->SubmitFrame(context, nullptr, background_canvas); + return platform_views_controller->SubmitFrame(context, nullptr, background_canvas); +} + +void IOSSurfaceMetal::FinishFrame() { [CATransaction commit]; - return submitted; } } // namespace flutter diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index 6bdb27677858f..23658888f7caa 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -266,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 b03969c8363aa..63c944a88d7ef 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.h +++ b/shell/platform/embedder/embedder_external_view_embedder.h @@ -91,6 +91,9 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { // |ExternalViewEmbedder| bool SubmitFrame(GrContext* context, SkCanvas* background_canvas) override; + // |ExternalViewEmbedder| + void FinishFrame() override; + // |ExternalViewEmbedder| SkCanvas* GetRootCanvas() override; From 32fbc8c3ca4f204a1f4e9ce8f6854e288dbcc32c Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 13 Mar 2020 18:28:11 -0700 Subject: [PATCH 41/44] empty line --- shell/platform/darwin/ios/ios_surface_metal.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/platform/darwin/ios/ios_surface_metal.mm b/shell/platform/darwin/ios/ios_surface_metal.mm index aea1f9dba6ed5..505791dea139e 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.mm +++ b/shell/platform/darwin/ios/ios_surface_metal.mm @@ -58,7 +58,6 @@ if (platform_views_controller == nullptr) { return true; } - return platform_views_controller->SubmitFrame(context, nullptr, background_canvas); } From 165d9ea93be3d6264cb542ebcba17e6ba26e2788 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 13 Mar 2020 18:45:24 -0700 Subject: [PATCH 42/44] Missing method --- flow/embedded_views.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index 894abe1299dd2..5234bf1e50c8c 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -11,6 +11,8 @@ bool ExternalViewEmbedder::SubmitFrame(GrContext* context, return false; }; +void ExternalViewEmbedder::FinishFrame(){}; + void MutatorsStack::PushClipRect(const SkRect& rect) { std::shared_ptr element = std::make_shared(rect); vector_.push_back(element); From 41b8ac4f1f47ff1419a20f6b00a418c4b0a1e6a0 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 18 Mar 2020 16:52:49 -0700 Subject: [PATCH 43/44] Feedback --- .../framework/Source/FlutterPlatformViews.mm | 62 ++++++++++--------- .../Source/FlutterPlatformViews_Internal.h | 19 ++++-- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 159a14730563b..5e09fc9768c5e 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -24,7 +24,7 @@ std::shared_ptr FlutterPlatformViewLayerPool::GetLayer( GrContext* gr_context, std::shared_ptr ios_context) { - if (available_layer_index_ >= layer_.size()) { + if (available_layer_index_ >= layers_.size()) { std::shared_ptr layer; if (!gr_context) { @@ -51,9 +51,9 @@ std::move(overlay_view), std::move(ios_surface), std::move(surface)); layer->gr_context = gr_context; } - layer_.push_back(layer); + layers_.push_back(layer); } - auto layer = layer_[available_layer_index_]; + 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 @@ -73,8 +73,8 @@ std::vector> FlutterPlatformViewLayerPool::GetUnusedLayers() { std::vector> results; - for (size_t i = available_layer_index_; i < layer_.size(); i++) { - results.push_back(layer_[i]); + for (size_t i = available_layer_index_; i < layers_.size(); i++) { + results.push_back(layers_[i]); } return results; } @@ -261,9 +261,9 @@ std::unique_ptr params) { picture_recorders_[view_id] = std::make_unique(); - auto bbh_factory = RTreeFactory(); - platform_views_bbh_[view_id] = bbh_factory.getInstance().get(); - picture_recorders_[view_id]->beginRecording(SkRect::Make(frame_size_), &bbh_factory); + 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); @@ -428,7 +428,7 @@ composition_order_.clear(); active_composition_order_.clear(); picture_recorders_.clear(); - platform_views_bbh_.clear(); + platform_view_rtrees_.clear(); current_composition_params_.clear(); clip_count_.clear(); views_to_recomposite_.clear(); @@ -451,30 +451,32 @@ std::shared_ptr ios_context, SkCanvas* background_canvas) { DisposeViews(); - // Clipping the background canvas before drawing the picture recorders requires to // save and restore the clip context. - SkAutoCanvasRestore save(background_canvas, true); - - bool did_submit = true; - + SkAutoCanvasRestore save(background_canvas, /*doSave=*/true); // Maps a platform view id to a vector of `FlutterPlatformViewLayer`. LayersMap platform_view_layers; - size_t num_platform_views = composition_order_.size(); + auto did_submit = true; + auto num_platform_views = composition_order_.size(); + for (size_t i = 0; i < num_platform_views; i++) { - auto platform_view_id = composition_order_[i]; - auto bbh = platform_views_bbh_[platform_view_id]; + 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--) { - auto current_platform_view_id = composition_order_[j - 1]; - auto platform_view_rect = GetPlatformViewRect(current_platform_view_id); - auto intersection_rects = bbh->searchNonOverlappingDrawnRects(platform_view_rect); + 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(); - auto overlay_count = platform_view_layers[current_platform_view_id].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. @@ -483,7 +485,7 @@ // https://github.com/flutter/flutter/issues/52510 if (allocation_size > kMaxLayerAllocations) { SkRect joined_rect; - for (SkRect rect : intersection_rects) { + for (const SkRect& rect : intersection_rects) { joined_rect.join(rect); } // Replace the rects in the intersection rects list for a single rect that is @@ -491,7 +493,7 @@ intersection_rects.clear(); intersection_rects.push_back(joined_rect); } - for (SkRect joined_rect : intersection_rects) { + 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); @@ -504,11 +506,11 @@ picture, // joined_rect, // current_platform_view_id, // - overlay_count // + overlay_id // ); did_submit &= layer->did_submit_last_frame; platform_view_layers[current_platform_view_id].push_back(layer); - overlay_count++; + overlay_id++; } } background_canvas->drawPicture(picture); @@ -527,19 +529,19 @@ } void FlutterPlatformViewsController::BringLayersIntoView(LayersMap layer_map) { - auto flutter_view = flutter_view_.get(); + UIView* flutter_view = flutter_view_.get(); auto zIndex = 0; for (size_t i = 0; i < composition_order_.size(); i++) { - auto platform_view_id = composition_order_[i]; - auto layers = layer_map[platform_view_id]; - auto platform_view_root = root_views_[platform_view_id].get(); + 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]; } else { platform_view_root.layer.zPosition = zIndex++; } - for (auto layer : layers) { + for (std::shared_ptr layer : layers) { if ([layer->overlay_view superview] != flutter_view) { [flutter_view addSubview:layer->overlay_view]; } else { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 87245e6b6e05a..36676cc6ea76c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -68,6 +68,8 @@ struct FlutterPlatformViewLayer { fml::scoped_nsobject overlay_view; 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. @@ -83,7 +85,7 @@ class FlutterPlatformViewLayerPool { ~FlutterPlatformViewLayerPool() = default; // Gets a layer from the pool if available, or allocates a new one. - // Finally, it marks the layer as used. + // 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); @@ -94,8 +96,8 @@ class FlutterPlatformViewLayerPool { void RecycleLayers(); private: - // The index of the entry in the layer_ vector that determines the beginning of the unused layers. - // For example, consider the following vector: + // The index of the entry in the layers_ vector that determines the beginning of the unused + // layers. For example, consider the following vector: // _____ // | 0 | /// |---| @@ -107,7 +109,7 @@ class FlutterPlatformViewLayerPool { /// 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> layer_; + std::vector> layers_; FML_DISALLOW_COPY_AND_ASSIGN(FlutterPlatformViewLayerPool); }; @@ -147,6 +149,9 @@ 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. That is, after applying any transformation matrix and scaling each + // of the rect's components based on the device's screen scale. SkRect GetPlatformViewRect(int view_id); // Discards all platform views instances and auxiliary resources. @@ -166,9 +171,11 @@ class FlutterPlatformViewsController { // The pool of reusable view layers. The pool allows to recycle layer in each frame. std::unique_ptr layer_pool_; - // The platform view's BBoxHierarchy keyed off the view id, which contains any subsequent + // 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. - std::map platform_views_bbh_; + // + // 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. From 32f433abd6296bf0b3b4191d465181a8b1b8d614 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 20 Mar 2020 11:26:16 -0700 Subject: [PATCH 44/44] Feedback --- .../ios/framework/Source/FlutterPlatformViews.mm | 6 +++--- .../Source/FlutterPlatformViews_Internal.h | 4 ++-- shell/platform/darwin/ios/ios_surface_metal.mm | 13 +++---------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index 5e09fc9768c5e..b96fcbbbaa76a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -541,7 +541,7 @@ } else { platform_view_root.layer.zPosition = zIndex++; } - for (std::shared_ptr layer : layers) { + for (const std::shared_ptr& layer : layers) { if ([layer->overlay_view superview] != flutter_view) { [flutter_view addSubview:layer->overlay_view]; } else { @@ -574,7 +574,7 @@ 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 == nullptr) { + if (!frame) { return layer; } auto overlay_canvas = frame->SkiaCanvas(); @@ -590,7 +590,7 @@ void FlutterPlatformViewsController::RemoveUnusedLayers() { auto layers = layer_pool_->GetUnusedLayers(); - for (auto layer : layers) { + for (const std::shared_ptr& layer : layers) { [layer->overlay_view removeFromSuperview]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index 36676cc6ea76c..59dc940c4d126 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -90,6 +90,7 @@ class FlutterPlatformViewLayerPool { 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. @@ -150,8 +151,7 @@ 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. That is, after applying any transformation matrix and scaling each - // of the rect's components based on the device's screen scale. + // host view coordinate system. Units are device screen pixels. SkRect GetPlatformViewRect(int view_id); // Discards all platform views instances and auxiliary resources. diff --git a/shell/platform/darwin/ios/ios_surface_metal.mm b/shell/platform/darwin/ios/ios_surface_metal.mm index 505791dea139e..db345b383ef7b 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.mm +++ b/shell/platform/darwin/ios/ios_surface_metal.mm @@ -53,16 +53,9 @@ ); } -bool IOSSurfaceMetal::SubmitFrame(GrContext* context, SkCanvas* background_canvas) { - FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); - if (platform_views_controller == nullptr) { - return true; - } - return platform_views_controller->SubmitFrame(context, nullptr, background_canvas); -} - -void IOSSurfaceMetal::FinishFrame() { - [CATransaction commit]; +// |GPUSurfaceDelegate| +ExternalViewEmbedder* IOSSurfaceMetal::GetExternalViewEmbedder() { + return GetExternalViewEmbedderIfEnabled(); } } // namespace flutter