Flutter Engine
 
Loading...
Searching...
No Matches
FlutterPlatformViewsController.mm
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6
17
20using flutter::DlRect;
22
23static constexpr NSUInteger kFlutterClippingMaskViewPoolCapacity = 5;
24
25struct LayerData {
26 DlRect rect;
27 int64_t view_id;
28 int64_t overlay_id;
29 std::shared_ptr<flutter::OverlayLayer> layer;
30};
31using LayersMap = std::unordered_map<int64_t, LayerData>;
32
33/// Each of the following structs stores part of the platform view hierarchy according to its
34/// ID.
35///
36/// This data must only be accessed on the platform thread.
38 NSObject<FlutterPlatformView>* view;
40 UIView* root_view;
41};
42
43// Converts a DlMatrix to CATransform3D.
44static CATransform3D GetCATransform3DFromDlMatrix(const DlMatrix& matrix) {
45 CATransform3D transform = CATransform3DIdentity;
46 transform.m11 = matrix.m[0];
47 transform.m12 = matrix.m[1];
48 transform.m13 = matrix.m[2];
49 transform.m14 = matrix.m[3];
50
51 transform.m21 = matrix.m[4];
52 transform.m22 = matrix.m[5];
53 transform.m23 = matrix.m[6];
54 transform.m24 = matrix.m[7];
55
56 transform.m31 = matrix.m[8];
57 transform.m32 = matrix.m[9];
58 transform.m33 = matrix.m[10];
59 transform.m34 = matrix.m[11];
60
61 transform.m41 = matrix.m[12];
62 transform.m42 = matrix.m[13];
63 transform.m43 = matrix.m[14];
64 transform.m44 = matrix.m[15];
65 return transform;
66}
67
68// Reset the anchor of `layer` to match the transform operation from flow.
69//
70// The position of the `layer` should be unchanged after resetting the anchor.
71static void ResetAnchor(CALayer* layer) {
72 // Flow uses (0, 0) to apply transform matrix so we need to match that in Quartz.
73 layer.anchorPoint = CGPointZero;
74 layer.position = CGPointZero;
75}
76
77static CGRect GetCGRectFromDlRect(const DlRect& clipDlRect) {
78 return CGRectMake(clipDlRect.GetLeft(), //
79 clipDlRect.GetTop(), //
80 clipDlRect.GetWidth(), //
81 clipDlRect.GetHeight());
82}
83
85
86// The pool of reusable view layers. The pool allows to recycle layer in each frame.
87@property(nonatomic, readonly) flutter::OverlayLayerPool* layerPool;
88
89// The platform view's |EmbedderViewSlice| keyed off the view id, which contains any subsequent
90// operation until the next platform view or the end of the last leaf node in the layer tree.
91//
92// The Slices are deleted by the PlatformViewsController.reset().
93@property(nonatomic, readonly)
94 std::unordered_map<int64_t, std::unique_ptr<flutter::EmbedderViewSlice>>& slices;
95
96@property(nonatomic, readonly) FlutterClippingMaskViewPool* maskViewPool;
97
98@property(nonatomic, readonly)
99 std::unordered_map<std::string, NSObject<FlutterPlatformViewFactory>*>& factories;
100
101// The FlutterPlatformViewGestureRecognizersBlockingPolicy for each type of platform view.
102@property(nonatomic, readonly)
103 std::unordered_map<std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy>&
104 gestureRecognizersBlockingPolicies;
105
106/// The size of the current onscreen surface in physical pixels.
107@property(nonatomic, assign) DlISize frameSize;
108
109/// The task runner for posting tasks to the platform thread.
110@property(nonatomic, readonly) const fml::RefPtr<fml::TaskRunner>& platformTaskRunner;
111
112/// This data must only be accessed on the platform thread.
113@property(nonatomic, readonly) std::unordered_map<int64_t, PlatformViewData>& platformViews;
114
115/// The composition parameters for each platform view.
116///
117/// This state is only modified on the raster thread.
118@property(nonatomic, readonly)
119 std::unordered_map<int64_t, flutter::EmbeddedViewParams>& currentCompositionParams;
120
121/// Method channel `OnDispose` calls adds the views to be disposed to this set to be disposed on
122/// the next frame.
123///
124/// This state is modified on both the platform and raster thread.
125@property(nonatomic, readonly) std::unordered_set<int64_t>& viewsToDispose;
126
127/// view IDs in composition order.
128///
129/// This state is only modified on the raster thread.
130@property(nonatomic, readonly) std::vector<int64_t>& compositionOrder;
131
132/// platform view IDs visited during layer tree composition.
133///
134/// This state is only modified on the raster thread.
135@property(nonatomic, readonly) std::vector<int64_t>& visitedPlatformViews;
136
137/// Only composite platform views in this set.
138///
139/// This state is only modified on the raster thread.
140@property(nonatomic, readonly) std::unordered_set<int64_t>& viewsToRecomposite;
141
142/// @brief The composition order from the previous thread.
143///
144/// Only accessed from the platform thread.
145@property(nonatomic, readonly) std::vector<int64_t>& previousCompositionOrder;
146
147/// Whether the previous frame had any platform views in active composition order.
148///
149/// This state is tracked so that the first frame after removing the last platform view
150/// runs through the platform view rendering code path, giving us a chance to remove the
151/// platform view from the UIView hierarchy.
152///
153/// Only accessed from the raster thread.
154@property(nonatomic, assign) BOOL hadPlatformViews;
155
156/// Whether blurred backdrop filters can be applied.
157///
158/// Defaults to YES, but becomes NO if blurred backdrop filters cannot be applied.
159@property(nonatomic, assign) BOOL canApplyBlurBackdrop;
160
161/// Populate any missing overlay layers.
162///
163/// This requires posting a task to the platform thread and blocking on its completion.
164- (void)createMissingOverlays:(size_t)requiredOverlayLayers
165 withIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext;
166
167/// Update the buffers and mutate the platform views in CATransaction on the platform thread.
168- (void)performSubmit:(const LayersMap&)platformViewLayers
169 currentCompositionParams:
170 (std::unordered_map<int64_t, flutter::EmbeddedViewParams>&)currentCompositionParams
171 viewsToRecomposite:(const std::unordered_set<int64_t>&)viewsToRecomposite
172 compositionOrder:(const std::vector<int64_t>&)compositionOrder
173 unusedLayers:
174 (const std::vector<std::shared_ptr<flutter::OverlayLayer>>&)unusedLayers
175 surfaceFrames:
176 (const std::vector<std::unique_ptr<flutter::SurfaceFrame>>&)surfaceFrames;
177
178- (void)onCreate:(FlutterMethodCall*)call result:(FlutterResult)result;
179- (void)onDispose:(FlutterMethodCall*)call result:(FlutterResult)result;
180- (void)onAcceptGesture:(FlutterMethodCall*)call result:(FlutterResult)result;
181- (void)onRejectGesture:(FlutterMethodCall*)call result:(FlutterResult)result;
182
183- (void)clipViewSetMaskView:(UIView*)clipView;
184
185// Applies the mutators in the mutatorsStack to the UIView chain that was constructed by
186// `ReconstructClipViewsChain`
187//
188// Clips are applied to the `embeddedView`'s super view(|ChildClippingView|) using a
189// |FlutterClippingMaskView|. Transforms are applied to `embeddedView`
190//
191// The `boundingRect` is the final bounding rect of the PlatformView
192// (EmbeddedViewParams::finalBoundingRect). If a clip mutator's rect contains the final bounding
193// rect of the PlatformView, the clip mutator is not applied for performance optimization.
194//
195// This method is only called when the `embeddedView` needs to be re-composited at the current
196// frame. See: `compositeView:withParams:` for details.
197- (void)applyMutators:(const flutter::MutatorsStack&)mutatorsStack
198 embeddedView:(UIView*)embeddedView
199 boundingRect:(const DlRect&)boundingRect;
200
201// Appends the overlay views and platform view and sets their z index based on the composition
202// order.
203- (void)bringLayersIntoView:(const LayersMap&)layerMap
204 withCompositionOrder:(const std::vector<int64_t>&)compositionOrder;
205
206- (std::shared_ptr<flutter::OverlayLayer>)nextLayerInPool;
207
208/// Runs on the platform thread.
209- (void)createLayerWithIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext
210 pixelFormat:(MTLPixelFormat)pixelFormat;
211
212/// Removes overlay views and platform views that aren't needed in the current frame.
213/// Must run on the platform thread.
214- (void)removeUnusedLayers:(const std::vector<std::shared_ptr<flutter::OverlayLayer>>&)unusedLayers
215 withCompositionOrder:(const std::vector<int64_t>&)compositionOrder;
216
217/// Computes and returns all views to be disposed on the platform thread, removes them from
218/// self.platformViews, self.viewsToRecomposite, and self.currentCompositionParams. Any views that
219/// still require compositing are not returned, but instead added to `viewsToDelayDispose` for
220/// disposal on the next call.
221- (std::vector<UIView*>)computeViewsToDispose;
222
223/// Resets the state of the frame.
224- (void)resetFrameState;
225@end
226
227@implementation FlutterPlatformViewsController {
228 // TODO(cbracken): Replace with Obj-C types and use @property declarations to automatically
229 // synthesize the ivars.
230 //
231 // These ivars are required because we're transitioning the previous C++ implementation to Obj-C.
232 // We require ivars to declare the concrete types and then wrap with @property declarations that
233 // return a reference to the ivar, allowing for use like `self.layerPool` and
234 // `self.slices[viewId] = x`.
235 std::unique_ptr<flutter::OverlayLayerPool> _layerPool;
236 std::unordered_map<int64_t, std::unique_ptr<flutter::EmbedderViewSlice>> _slices;
237 std::unordered_map<std::string, NSObject<FlutterPlatformViewFactory>*> _factories;
238 std::unordered_map<std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy>
241 std::unordered_map<int64_t, PlatformViewData> _platformViews;
242 std::unordered_map<int64_t, flutter::EmbeddedViewParams> _currentCompositionParams;
243 std::unordered_set<int64_t> _viewsToDispose;
244 std::vector<int64_t> _compositionOrder;
245 std::vector<int64_t> _visitedPlatformViews;
246 std::unordered_set<int64_t> _viewsToRecomposite;
247 std::vector<int64_t> _previousCompositionOrder;
248}
249
250- (id)init {
251 if (self = [super init]) {
252 _layerPool = std::make_unique<flutter::OverlayLayerPool>();
253 _maskViewPool =
254 [[FlutterClippingMaskViewPool alloc] initWithCapacity:kFlutterClippingMaskViewPoolCapacity];
255 _hadPlatformViews = NO;
256 _canApplyBlurBackdrop = YES;
257 }
258 return self;
259}
260
261- (const fml::RefPtr<fml::TaskRunner>&)taskRunner {
262 return _platformTaskRunner;
263}
264
265- (void)setTaskRunner:(const fml::RefPtr<fml::TaskRunner>&)platformTaskRunner {
266 _platformTaskRunner = platformTaskRunner;
267}
268
269- (void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
270 if ([[call method] isEqualToString:@"create"]) {
271 [self onCreate:call result:result];
272 } else if ([[call method] isEqualToString:@"dispose"]) {
273 [self onDispose:call result:result];
274 } else if ([[call method] isEqualToString:@"acceptGesture"]) {
275 [self onAcceptGesture:call result:result];
276 } else if ([[call method] isEqualToString:@"rejectGesture"]) {
277 [self onRejectGesture:call result:result];
278 } else {
280 }
281}
282
283- (void)onCreate:(FlutterMethodCall*)call result:(FlutterResult)result {
284 NSDictionary<NSString*, id>* args = [call arguments];
285
286 int64_t viewId = [args[@"id"] longLongValue];
287 NSString* viewTypeString = args[@"viewType"];
288 std::string viewType(viewTypeString.UTF8String);
289
290 if (self.platformViews.count(viewId) != 0) {
291 result([FlutterError errorWithCode:@"recreating_view"
292 message:@"trying to create an already created view"
293 details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
294 return;
295 }
296
297 NSObject<FlutterPlatformViewFactory>* factory = self.factories[viewType];
298 if (factory == nil) {
299 result([FlutterError
300 errorWithCode:@"unregistered_view_type"
301 message:[NSString stringWithFormat:@"A UIKitView widget is trying to create a "
302 @"PlatformView with an unregistered type: < %@ >",
303 viewTypeString]
304 details:@"If you are the author of the PlatformView, make sure `registerViewFactory` "
305 @"is invoked.\n"
306 @"See: "
307 @"https://docs.flutter.dev/development/platform-integration/"
308 @"platform-views#on-the-platform-side-1 for more details.\n"
309 @"If you are not the author of the PlatformView, make sure to call "
310 @"`GeneratedPluginRegistrant.register`."]);
311 return;
312 }
313
314 id params = nil;
315 if ([factory respondsToSelector:@selector(createArgsCodec)]) {
316 NSObject<FlutterMessageCodec>* codec = [factory createArgsCodec];
317 if (codec != nil && args[@"params"] != nil) {
318 FlutterStandardTypedData* paramsData = args[@"params"];
319 params = [codec decode:paramsData.data];
320 }
321 }
322
323 NSObject<FlutterPlatformView>* embeddedView = [factory createWithFrame:CGRectZero
324 viewIdentifier:viewId
325 arguments:params];
326 UIView* platformView = [embeddedView view];
327 // Set a unique view identifier, so the platform view can be identified in unit tests.
328 platformView.accessibilityIdentifier = [NSString stringWithFormat:@"platform_view[%lld]", viewId];
329
331 initWithEmbeddedView:platformView
332 platformViewsController:self
333 gestureRecognizersBlockingPolicy:self.gestureRecognizersBlockingPolicies[viewType]];
334
335 ChildClippingView* clippingView = [[ChildClippingView alloc] initWithFrame:CGRectZero];
336 [clippingView addSubview:touchInterceptor];
337
338 self.platformViews.emplace(viewId, PlatformViewData{
339 .view = embeddedView, //
340 .touch_interceptor = touchInterceptor, //
341 .root_view = clippingView //
342 });
343
344 result(nil);
345}
346
347- (void)onDispose:(FlutterMethodCall*)call result:(FlutterResult)result {
348 NSNumber* arg = [call arguments];
349 int64_t viewId = [arg longLongValue];
350
351 if (self.platformViews.count(viewId) == 0) {
352 result([FlutterError errorWithCode:@"unknown_view"
353 message:@"trying to dispose an unknown"
354 details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
355 return;
356 }
357 // We wait for next submitFrame to dispose views.
358 self.viewsToDispose.insert(viewId);
359 result(nil);
360}
361
362- (void)onAcceptGesture:(FlutterMethodCall*)call result:(FlutterResult)result {
363 NSDictionary<NSString*, id>* args = [call arguments];
364 int64_t viewId = [args[@"id"] longLongValue];
365
366 if (self.platformViews.count(viewId) == 0) {
367 result([FlutterError errorWithCode:@"unknown_view"
368 message:@"trying to set gesture state for an unknown view"
369 details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
370 return;
371 }
372
373 FlutterTouchInterceptingView* view = self.platformViews[viewId].touch_interceptor;
374 [view releaseGesture];
375
376 result(nil);
377}
378
379- (void)onRejectGesture:(FlutterMethodCall*)call result:(FlutterResult)result {
380 NSDictionary<NSString*, id>* args = [call arguments];
381 int64_t viewId = [args[@"id"] longLongValue];
382
383 if (self.platformViews.count(viewId) == 0) {
384 result([FlutterError errorWithCode:@"unknown_view"
385 message:@"trying to set gesture state for an unknown view"
386 details:[NSString stringWithFormat:@"view id: '%lld'", viewId]]);
387 return;
388 }
389
390 FlutterTouchInterceptingView* view = self.platformViews[viewId].touch_interceptor;
391 [view blockGesture];
392
393 result(nil);
394}
395
396- (void)registerViewFactory:(NSObject<FlutterPlatformViewFactory>*)factory
397 withId:(NSString*)factoryId
398 gestureRecognizersBlockingPolicy:
399 (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizerBlockingPolicy {
400 std::string idString([factoryId UTF8String]);
401 FML_CHECK(self.factories.count(idString) == 0);
402 self.factories[idString] = factory;
403 self.gestureRecognizersBlockingPolicies[idString] = gestureRecognizerBlockingPolicy;
404}
405
406- (void)beginFrameWithSize:(DlISize)frameSize {
407 [self resetFrameState];
408 self.frameSize = frameSize;
409}
410
411- (void)cancelFrame {
412 [self resetFrameState];
413}
414
415- (flutter::PostPrerollResult)postPrerollActionWithThreadMerger:
416 (const fml::RefPtr<fml::RasterThreadMerger>&)rasterThreadMerger {
418}
419
420- (void)endFrameWithResubmit:(BOOL)shouldResubmitFrame
421 threadMerger:(const fml::RefPtr<fml::RasterThreadMerger>&)rasterThreadMerger {
422}
423
424- (void)pushFilterToVisitedPlatformViews:(const std::shared_ptr<flutter::DlImageFilter>&)filter
425 withRect:(const flutter::DlRect&)filterRect {
426 for (int64_t id : self.visitedPlatformViews) {
427 flutter::EmbeddedViewParams params = self.currentCompositionParams[id];
428 params.PushImageFilter(filter, filterRect);
429 self.currentCompositionParams[id] = params;
430 }
431}
432
433- (void)prerollCompositeEmbeddedView:(int64_t)viewId
434 withParams:(std::unique_ptr<flutter::EmbeddedViewParams>)params {
435 DlRect viewBounds = DlRect::MakeSize(self.frameSize);
436 std::unique_ptr<flutter::EmbedderViewSlice> view;
437 view = std::make_unique<flutter::DisplayListEmbedderViewSlice>(viewBounds);
438 self.slices.insert_or_assign(viewId, std::move(view));
439
440 self.compositionOrder.push_back(viewId);
441
442 if (self.currentCompositionParams.count(viewId) == 1 &&
443 self.currentCompositionParams[viewId] == *params.get()) {
444 // Do nothing if the params didn't change.
445 return;
446 }
447 self.currentCompositionParams[viewId] = flutter::EmbeddedViewParams(*params.get());
448 self.viewsToRecomposite.insert(viewId);
449}
450
451- (size_t)embeddedViewCount {
452 return self.compositionOrder.size();
453}
454
455- (UIView*)platformViewForId:(int64_t)viewId {
456 return [self flutterTouchInterceptingViewForId:viewId].embeddedView;
457}
458
459- (FlutterTouchInterceptingView*)flutterTouchInterceptingViewForId:(int64_t)viewId {
460 if (self.platformViews.empty()) {
461 return nil;
462 }
463 return self.platformViews[viewId].touch_interceptor;
464}
465
466- (long)firstResponderPlatformViewId {
467 for (auto const& [id, platformViewData] : self.platformViews) {
468 UIView* rootView = platformViewData.root_view;
469 if (rootView.flt_hasFirstResponderInViewHierarchySubtree) {
470 return id;
471 }
472 }
473 return -1;
474}
475
476- (void)clipViewSetMaskView:(UIView*)clipView {
477 FML_DCHECK([[NSThread currentThread] isMainThread]);
478 if (clipView.maskView) {
479 return;
480 }
481 CGRect frame =
482 CGRectMake(-clipView.frame.origin.x, -clipView.frame.origin.y,
483 CGRectGetWidth(self.flutterView.bounds), CGRectGetHeight(self.flutterView.bounds));
484 clipView.maskView = [self.maskViewPool getMaskViewWithFrame:frame];
485}
486
487- (void)applyMutators:(const flutter::MutatorsStack&)mutatorsStack
488 embeddedView:(UIView*)embeddedView
489 boundingRect:(const DlRect&)boundingRect {
490 if (self.flutterView == nil) {
491 return;
492 }
493
494 ResetAnchor(embeddedView.layer);
495 ChildClippingView* clipView = (ChildClippingView*)embeddedView.superview;
496
497 DlMatrix transformMatrix;
498 NSMutableArray* blurFilters = [[NSMutableArray alloc] init];
499 FML_DCHECK(!clipView.maskView ||
500 [clipView.maskView isKindOfClass:[FlutterClippingMaskView class]]);
501 if (clipView.maskView) {
502 [self.maskViewPool insertViewToPoolIfNeeded:(FlutterClippingMaskView*)(clipView.maskView)];
503 clipView.maskView = nil;
504 }
505 CGFloat screenScale = [UIScreen mainScreen].scale;
506 auto iter = mutatorsStack.Begin();
507 while (iter != mutatorsStack.End()) {
508 switch ((*iter)->GetType()) {
510 transformMatrix = transformMatrix * (*iter)->GetMatrix();
511 break;
512 }
515 (*iter)->GetRect(), transformMatrix, boundingRect)) {
516 break;
517 }
518 [self clipViewSetMaskView:clipView];
519 [(FlutterClippingMaskView*)clipView.maskView clipRect:(*iter)->GetRect()
520 matrix:transformMatrix];
521 break;
522 }
525 (*iter)->GetRRect(), transformMatrix, boundingRect)) {
526 break;
527 }
528 [self clipViewSetMaskView:clipView];
529 [(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRRect()
530 matrix:transformMatrix];
531 break;
532 }
535 (*iter)->GetRSE(), transformMatrix, boundingRect)) {
536 break;
537 }
538 [self clipViewSetMaskView:clipView];
539 [(FlutterClippingMaskView*)clipView.maskView clipRRect:(*iter)->GetRSEApproximation()
540 matrix:transformMatrix];
541 break;
542 }
544 // TODO(cyanglaz): Find a way to pre-determine if path contains the PlatformView boudning
545 // rect. See `ClipRRectContainsPlatformViewBoundingRect`.
546 // https://github.com/flutter/flutter/issues/118650
547 [self clipViewSetMaskView:clipView];
548 [(FlutterClippingMaskView*)clipView.maskView clipPath:(*iter)->GetPath()
549 matrix:transformMatrix];
550 break;
551 }
553 embeddedView.alpha = (*iter)->GetAlphaFloat() * embeddedView.alpha;
554 break;
556 // Only support DlBlurImageFilter for BackdropFilter.
557 if (!self.canApplyBlurBackdrop || !(*iter)->GetFilterMutation().GetFilter().asBlur()) {
558 break;
559 }
560 CGRect filterRect = GetCGRectFromDlRect((*iter)->GetFilterMutation().GetFilterRect());
561 // `filterRect` is in global coordinates. We need to convert to local space.
562 filterRect = CGRectApplyAffineTransform(
563 filterRect, CGAffineTransformMakeScale(1 / screenScale, 1 / screenScale));
564 // `filterRect` reprents the rect that should be filtered inside the `_flutterView`.
565 // The `PlatformViewFilter` needs the frame inside the `clipView` that needs to be
566 // filtered.
567 if (CGRectIsNull(CGRectIntersection(filterRect, clipView.frame))) {
568 break;
569 }
570 CGRect intersection = CGRectIntersection(filterRect, clipView.frame);
571 CGRect frameInClipView = [self.flutterView convertRect:intersection toView:clipView];
572 // sigma_x is arbitrarily chosen as the radius value because Quartz sets
573 // sigma_x and sigma_y equal to each other. DlBlurImageFilter's Tile Mode
574 // is not supported in Quartz's gaussianBlur CAFilter, so it is not used
575 // to blur the PlatformView.
576 CGFloat blurRadius = (*iter)->GetFilterMutation().GetFilter().asBlur()->sigma_x();
577 UIVisualEffectView* visualEffectView = [[UIVisualEffectView alloc]
578 initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
579 PlatformViewFilter* filter = [[PlatformViewFilter alloc] initWithFrame:frameInClipView
580 blurRadius:blurRadius
581 visualEffectView:visualEffectView];
582 if (!filter) {
583 self.canApplyBlurBackdrop = NO;
584 } else {
585 [blurFilters addObject:filter];
586 }
587 break;
588 }
589 }
590 ++iter;
591 }
592
593 if (self.canApplyBlurBackdrop) {
594 [clipView applyBlurBackdropFilters:blurFilters];
595 }
596
597 // The UIKit frame is set based on the logical resolution (points) instead of physical.
598 // (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
599 // However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
600 // 500 points in UIKit for devices that has screenScale of 2. We need to scale the transformMatrix
601 // down to the logical resoltion before applying it to the layer of PlatformView.
602 flutter::DlScalar pointScale = 1.0 / screenScale;
603 transformMatrix = DlMatrix::MakeScale({pointScale, pointScale, 1}) * transformMatrix;
604
605 // Reverse the offset of the clipView.
606 // The clipView's frame includes the final translate of the final transform matrix.
607 // Thus, this translate needs to be reversed so the platform view can layout at the correct
608 // offset.
609 //
610 // Note that the transforms are not applied to the clipping paths because clipping paths happen on
611 // the mask view, whose origin is always (0,0) to the _flutterView.
612 impeller::Vector3 origin = impeller::Vector3(clipView.frame.origin.x, clipView.frame.origin.y);
613 transformMatrix = DlMatrix::MakeTranslation(-origin) * transformMatrix;
614
615 embeddedView.layer.transform = GetCATransform3DFromDlMatrix(transformMatrix);
616}
617
618- (void)compositeView:(int64_t)viewId withParams:(const flutter::EmbeddedViewParams&)params {
619 // TODO(https://github.com/flutter/flutter/issues/109700)
620 CGRect frame = CGRectMake(0, 0, params.sizePoints().width, params.sizePoints().height);
621 FlutterTouchInterceptingView* touchInterceptor = self.platformViews[viewId].touch_interceptor;
622 touchInterceptor.layer.transform = CATransform3DIdentity;
623 touchInterceptor.frame = frame;
624 touchInterceptor.alpha = 1;
625
626 const flutter::MutatorsStack& mutatorStack = params.mutatorsStack();
627 UIView* clippingView = self.platformViews[viewId].root_view;
628 // The frame of the clipping view should be the final bounding rect.
629 // Because the translate matrix in the Mutator Stack also includes the offset,
630 // when we apply the transforms matrix in |applyMutators:embeddedView:boundingRect|, we need
631 // to remember to do a reverse translate.
632 const DlRect& rect = params.finalBoundingRect();
633 CGFloat screenScale = [UIScreen mainScreen].scale;
634 clippingView.frame = CGRectMake(rect.GetX() / screenScale, rect.GetY() / screenScale,
635 rect.GetWidth() / screenScale, rect.GetHeight() / screenScale);
636 [self applyMutators:mutatorStack embeddedView:touchInterceptor boundingRect:rect];
637}
638
639- (flutter::DlCanvas*)compositeEmbeddedViewWithId:(int64_t)viewId {
640 FML_DCHECK(self.slices.find(viewId) != self.slices.end());
641 return self.slices[viewId]->canvas();
642}
643
644- (void)reset {
645 // Reset will only be called from the raster thread or a merged raster/platform thread.
646 // _platformViews must only be modified on the platform thread, and any operations that
647 // read or modify platform views should occur there.
648 fml::TaskRunner::RunNowOrPostTask(self.platformTaskRunner, [self]() {
649 for (int64_t viewId : self.compositionOrder) {
650 [self.platformViews[viewId].root_view removeFromSuperview];
651 }
652 self.platformViews.clear();
653 self.previousCompositionOrder.clear();
654 });
655
656 self.compositionOrder.clear();
657 self.slices.clear();
658 self.currentCompositionParams.clear();
659 self.viewsToRecomposite.clear();
660 self.layerPool->RecycleLayers();
661 self.visitedPlatformViews.clear();
662}
663
664- (BOOL)submitFrame:(std::unique_ptr<flutter::SurfaceFrame>)background_frame
665 withIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext {
666 TRACE_EVENT0("flutter", "PlatformViewsController::SubmitFrame");
667
668 // No platform views to render; we're done.
669 if (self.flutterView == nil || (self.compositionOrder.empty() && !self.hadPlatformViews)) {
670 self.hadPlatformViews = NO;
671 return background_frame->Submit();
672 }
673 self.hadPlatformViews = !self.compositionOrder.empty();
674
675 bool didEncode = true;
676 LayersMap platformViewLayers;
677 std::vector<std::unique_ptr<flutter::SurfaceFrame>> surfaceFrames;
678 surfaceFrames.reserve(self.compositionOrder.size());
679 std::unordered_map<int64_t, DlRect> viewRects;
680
681 for (int64_t viewId : self.compositionOrder) {
682 viewRects[viewId] = self.currentCompositionParams[viewId].finalBoundingRect();
683 }
684
685 std::unordered_map<int64_t, DlRect> overlayLayers =
686 SliceViews(background_frame->Canvas(), self.compositionOrder, self.slices, viewRects);
687
688 size_t requiredOverlayLayers = 0;
689 for (int64_t viewId : self.compositionOrder) {
690 std::unordered_map<int64_t, DlRect>::const_iterator overlay = overlayLayers.find(viewId);
691 if (overlay == overlayLayers.end()) {
692 continue;
693 }
694 requiredOverlayLayers++;
695 }
696
697 // If there are not sufficient overlay layers, we must construct them on the platform
698 // thread, at least until we've refactored iOS surface creation to use IOSurfaces
699 // instead of CALayers.
700 [self createMissingOverlays:requiredOverlayLayers withIosContext:iosContext];
701
702 int64_t overlayId = 0;
703 for (int64_t viewId : self.compositionOrder) {
704 std::unordered_map<int64_t, DlRect>::const_iterator overlay = overlayLayers.find(viewId);
705 if (overlay == overlayLayers.end()) {
706 continue;
707 }
708 std::shared_ptr<flutter::OverlayLayer> layer = self.nextLayerInPool;
709 if (!layer) {
710 continue;
711 }
712
713 std::unique_ptr<flutter::SurfaceFrame> frame = layer->surface->AcquireFrame(self.frameSize);
714 // If frame is null, AcquireFrame already printed out an error message.
715 if (!frame) {
716 continue;
717 }
718 flutter::DlCanvas* overlayCanvas = frame->Canvas();
719 int restoreCount = overlayCanvas->GetSaveCount();
720 overlayCanvas->Save();
721 overlayCanvas->ClipRect(overlay->second);
722 overlayCanvas->Clear(flutter::DlColor::kTransparent());
723 self.slices[viewId]->render_into(overlayCanvas);
724 overlayCanvas->RestoreToCount(restoreCount);
725
726 // This flutter view is never the last in a frame, since we always submit the
727 // underlay view last.
728 frame->set_submit_info({.frame_boundary = false, .present_with_transaction = true});
729 layer->did_submit_last_frame = frame->Encode();
730
731 didEncode &= layer->did_submit_last_frame;
732 platformViewLayers[viewId] = LayerData{
733 .rect = overlay->second, //
734 .view_id = viewId, //
735 .overlay_id = overlayId, //
736 .layer = layer //
737 };
738 surfaceFrames.push_back(std::move(frame));
739 overlayId++;
740 }
741
742 auto previousSubmitInfo = background_frame->submit_info();
743 background_frame->set_submit_info({
744 .frame_damage = previousSubmitInfo.frame_damage,
745 .buffer_damage = previousSubmitInfo.buffer_damage,
746 .present_with_transaction = true,
747 });
748 background_frame->Encode();
749 surfaceFrames.push_back(std::move(background_frame));
750
751 // Mark all layers as available, so they can be used in the next frame.
752 std::vector<std::shared_ptr<flutter::OverlayLayer>> unusedLayers =
753 self.layerPool->RemoveUnusedLayers();
754 self.layerPool->RecycleLayers();
755
756 auto task = [self, //
757 platformViewLayers = std::move(platformViewLayers), //
758 currentCompositionParams = self.currentCompositionParams, //
759 viewsToRecomposite = self.viewsToRecomposite, //
760 compositionOrder = self.compositionOrder, //
761 unusedLayers = std::move(unusedLayers), //
762 surfaceFrames = std::move(surfaceFrames) //
763 ]() mutable {
764 [self performSubmit:platformViewLayers
765 currentCompositionParams:currentCompositionParams
766 viewsToRecomposite:viewsToRecomposite
767 compositionOrder:compositionOrder
768 unusedLayers:unusedLayers
769 surfaceFrames:surfaceFrames];
770 };
771
772 fml::TaskRunner::RunNowOrPostTask(self.platformTaskRunner, fml::MakeCopyable(std::move(task)));
773
774 return didEncode;
775}
776
777- (void)createMissingOverlays:(size_t)requiredOverlayLayers
778 withIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext {
779 TRACE_EVENT0("flutter", "PlatformViewsController::CreateMissingLayers");
780
781 if (requiredOverlayLayers <= self.layerPool->size()) {
782 return;
783 }
784 auto missingLayerCount = requiredOverlayLayers - self.layerPool->size();
785
786 // If the raster thread isn't merged, create layers on the platform thread and block until
787 // complete.
788 auto latch = std::make_shared<fml::CountDownLatch>(1u);
790 self.platformTaskRunner, [self, missingLayerCount, iosContext, latch]() {
791 for (auto i = 0u; i < missingLayerCount; i++) {
792 [self createLayerWithIosContext:iosContext
793 pixelFormat:((FlutterView*)self.flutterView).pixelFormat];
794 }
795 latch->CountDown();
796 });
797 if (![[NSThread currentThread] isMainThread]) {
798 latch->Wait();
799 }
800}
801
802- (void)performSubmit:(const LayersMap&)platformViewLayers
803 currentCompositionParams:
804 (std::unordered_map<int64_t, flutter::EmbeddedViewParams>&)currentCompositionParams
805 viewsToRecomposite:(const std::unordered_set<int64_t>&)viewsToRecomposite
806 compositionOrder:(const std::vector<int64_t>&)compositionOrder
807 unusedLayers:
808 (const std::vector<std::shared_ptr<flutter::OverlayLayer>>&)unusedLayers
809 surfaceFrames:
810 (const std::vector<std::unique_ptr<flutter::SurfaceFrame>>&)surfaceFrames {
811 TRACE_EVENT0("flutter", "PlatformViewsController::PerformSubmit");
812 FML_DCHECK([[NSThread currentThread] isMainThread]);
813
814 [CATransaction begin];
815
816 // Configure Flutter overlay views.
817 for (const auto& [viewId, layerData] : platformViewLayers) {
818 layerData.layer->UpdateViewState(self.flutterView, //
819 layerData.rect, //
820 layerData.view_id, //
821 layerData.overlay_id //
822 );
823 }
824
825 // Dispose unused Flutter Views.
826 for (auto& view : [self computeViewsToDispose]) {
827 [view removeFromSuperview];
828 }
829
830 // Composite Platform Views.
831 for (int64_t viewId : viewsToRecomposite) {
832 [self compositeView:viewId withParams:currentCompositionParams[viewId]];
833 }
834
835 // Present callbacks.
836 for (const auto& frame : surfaceFrames) {
837 frame->Submit();
838 }
839
840 // If a layer was allocated in the previous frame, but it's not used in the current frame,
841 // then it can be removed from the scene.
842 [self removeUnusedLayers:unusedLayers withCompositionOrder:compositionOrder];
843
844 // Organize the layers by their z indexes.
845 [self bringLayersIntoView:platformViewLayers withCompositionOrder:compositionOrder];
846
847 [CATransaction commit];
848}
849
850- (void)bringLayersIntoView:(const LayersMap&)layerMap
851 withCompositionOrder:(const std::vector<int64_t>&)compositionOrder {
852 FML_DCHECK(self.flutterView);
853 UIView* flutterView = self.flutterView;
854
855 self.previousCompositionOrder.clear();
856 NSMutableArray* desiredPlatformSubviews = [NSMutableArray array];
857 for (int64_t platformViewId : compositionOrder) {
858 self.previousCompositionOrder.push_back(platformViewId);
859 UIView* platformViewRoot = self.platformViews[platformViewId].root_view;
860 if (platformViewRoot != nil) {
861 [desiredPlatformSubviews addObject:platformViewRoot];
862 }
863
864 auto maybeLayerData = layerMap.find(platformViewId);
865 if (maybeLayerData != layerMap.end()) {
866 auto view = maybeLayerData->second.layer->overlay_view_wrapper;
867 if (view != nil) {
868 [desiredPlatformSubviews addObject:view];
869 }
870 }
871 }
872
873 NSSet* desiredPlatformSubviewsSet = [NSSet setWithArray:desiredPlatformSubviews];
874 NSArray* existingPlatformSubviews = [flutterView.subviews
875 filteredArrayUsingPredicate:[NSPredicate
876 predicateWithBlock:^BOOL(id object, NSDictionary* bindings) {
877 return [desiredPlatformSubviewsSet containsObject:object];
878 }]];
879
880 // Manipulate view hierarchy only if needed, to address a performance issue where
881 // this method is called even when view hierarchy stays the same.
882 // See: https://github.com/flutter/flutter/issues/121833
883 // TODO(hellohuanlin): investigate if it is possible to skip unnecessary bringLayersIntoView.
884 if (![desiredPlatformSubviews isEqualToArray:existingPlatformSubviews]) {
885 for (UIView* subview in desiredPlatformSubviews) {
886 // `addSubview` will automatically reorder subview if it is already added.
887 [flutterView addSubview:subview];
888 }
889 }
890}
891
892- (std::shared_ptr<flutter::OverlayLayer>)nextLayerInPool {
893 return self.layerPool->GetNextLayer();
894}
895
896- (void)createLayerWithIosContext:(const std::shared_ptr<flutter::IOSContext>&)iosContext
897 pixelFormat:(MTLPixelFormat)pixelFormat {
898 self.layerPool->CreateLayer(iosContext, pixelFormat);
899}
900
901- (void)removeUnusedLayers:(const std::vector<std::shared_ptr<flutter::OverlayLayer>>&)unusedLayers
902 withCompositionOrder:(const std::vector<int64_t>&)compositionOrder {
903 for (const std::shared_ptr<flutter::OverlayLayer>& layer : unusedLayers) {
904 [layer->overlay_view_wrapper removeFromSuperview];
905 }
906
907 std::unordered_set<int64_t> compositionOrderSet;
908 for (int64_t viewId : compositionOrder) {
909 compositionOrderSet.insert(viewId);
910 }
911 // Remove unused platform views.
912 for (int64_t viewId : self.previousCompositionOrder) {
913 if (compositionOrderSet.find(viewId) == compositionOrderSet.end()) {
914 UIView* platformViewRoot = self.platformViews[viewId].root_view;
915 [platformViewRoot removeFromSuperview];
916 }
917 }
918}
919
920- (std::vector<UIView*>)computeViewsToDispose {
921 std::vector<UIView*> views;
922 if (self.viewsToDispose.empty()) {
923 return views;
924 }
925
926 std::unordered_set<int64_t> viewsToComposite(self.compositionOrder.begin(),
927 self.compositionOrder.end());
928 std::unordered_set<int64_t> viewsToDelayDispose;
929 for (int64_t viewId : self.viewsToDispose) {
930 if (viewsToComposite.count(viewId)) {
931 viewsToDelayDispose.insert(viewId);
932 continue;
933 }
934 UIView* rootView = self.platformViews[viewId].root_view;
935 views.push_back(rootView);
936 self.currentCompositionParams.erase(viewId);
937 self.viewsToRecomposite.erase(viewId);
938 self.platformViews.erase(viewId);
939 }
940 self.viewsToDispose = std::move(viewsToDelayDispose);
941 return views;
942}
943
944- (void)resetFrameState {
945 self.slices.clear();
946 self.compositionOrder.clear();
947 self.visitedPlatformViews.clear();
948}
949
950- (void)pushVisitedPlatformViewId:(int64_t)viewId {
951 self.visitedPlatformViews.push_back(viewId);
952}
953
954- (const flutter::EmbeddedViewParams&)compositionParamsForView:(int64_t)viewId {
955 return self.currentCompositionParams.find(viewId)->second;
956}
957
958#pragma mark - Properties
959
960- (flutter::OverlayLayerPool*)layerPool {
961 return _layerPool.get();
962}
963
964- (std::unordered_map<int64_t, std::unique_ptr<flutter::EmbedderViewSlice>>&)slices {
965 return _slices;
966}
967
968- (std::unordered_map<std::string, NSObject<FlutterPlatformViewFactory>*>&)factories {
969 return _factories;
970}
971- (std::unordered_map<std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy>&)
972 gestureRecognizersBlockingPolicies {
974}
975
976- (std::unordered_map<int64_t, PlatformViewData>&)platformViews {
977 return _platformViews;
978}
979
980- (std::unordered_map<int64_t, flutter::EmbeddedViewParams>&)currentCompositionParams {
982}
983
984- (std::unordered_set<int64_t>&)viewsToDispose {
985 return _viewsToDispose;
986}
987
988- (std::vector<int64_t>&)compositionOrder {
989 return _compositionOrder;
990}
991
992- (std::vector<int64_t>&)visitedPlatformViews {
994}
995
996- (std::unordered_set<int64_t>&)viewsToRecomposite {
997 return _viewsToRecomposite;
998}
999
1000- (std::vector<int64_t>&)previousCompositionOrder {
1002}
1003
1004@end
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
std::unordered_map< std::string, FlutterPlatformViewGestureRecognizersBlockingPolicy > _gestureRecognizersBlockingPolicies
std::unordered_map< int64_t, LayerData > LayersMap
std::vector< int64_t > _compositionOrder
std::unordered_set< int64_t > _viewsToRecomposite
std::unordered_map< std::string, NSObject< FlutterPlatformViewFactory > * > _factories
std::vector< int64_t > _previousCompositionOrder
static constexpr NSUInteger kFlutterClippingMaskViewPoolCapacity
std::unordered_map< int64_t, PlatformViewData > _platformViews
std::unordered_map< int64_t, std::unique_ptr< flutter::EmbedderViewSlice > > _slices
std::vector< int64_t > _visitedPlatformViews
fml::RefPtr< fml::TaskRunner > _platformTaskRunner
std::unordered_map< int64_t, flutter::EmbeddedViewParams > _currentCompositionParams
static void ResetAnchor(CALayer *layer)
static CATransform3D GetCATransform3DFromDlMatrix(const DlMatrix &matrix)
static CGRect GetCGRectFromDlRect(const DlRect &clipDlRect)
std::unordered_set< int64_t > _viewsToDispose
FlutterPlatformViewGestureRecognizersBlockingPolicy
static bool TransformedRectCoversBounds(const DlRect &local_rect, const DlMatrix &matrix, const DlRect &cull_bounds)
Checks if the local rect, when transformed by the matrix, completely covers the indicated culling bou...
static bool TransformedRoundSuperellipseCoversBounds(const DlRoundSuperellipse &local_rse, const DlMatrix &matrix, const DlRect &cull_bounds)
Checks if the local round superellipse, when transformed by the matrix, completely covers the indicat...
static bool TransformedRRectCoversBounds(const DlRoundRect &local_rrect, const DlMatrix &matrix, const DlRect &cull_bounds)
Checks if the local round rect, when transformed by the matrix, completely covers the indicated culli...
Developer-facing API for rendering anything within the engine.
Definition dl_canvas.h:32
virtual void ClipRect(const DlRect &rect, DlClipOp clip_op=DlClipOp::kIntersect, bool is_aa=false)=0
virtual int GetSaveCount() const =0
virtual void RestoreToCount(int restore_count)=0
void Clear(DlColor color)
Definition dl_canvas.h:104
virtual void Save()=0
void PushImageFilter(const std::shared_ptr< DlImageFilter > &filter, const DlRect &filter_rect)
Storage for Overlay layers across frames.
static void RunNowOrPostTask(const fml::RefPtr< fml::TaskRunner > &runner, const fml::closure &task)
const EmbeddedViewParams * params
FlView * view
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_BEGIN_DECLS GBytes * message
HWND(* FlutterPlatformViewFactory)(const FlutterPlatformViewCreationParameters *)
#define FML_CHECK(condition)
Definition logging.h:104
#define FML_DCHECK(condition)
Definition logging.h:122
impeller::Scalar DlScalar
std::unordered_map< int64_t, DlRect > SliceViews(DlCanvas *background_canvas, const std::vector< int64_t > &composition_order, const std::unordered_map< int64_t, std::unique_ptr< EmbedderViewSlice > > &slices, const std::unordered_map< int64_t, DlRect > &view_rects)
Compute the required overlay layers and clip the view slices according to the size and position of th...
impeller::Matrix DlMatrix
impeller::Rect DlRect
impeller::ISize32 DlISize
it will be possible to load the file into Perfetto s trace viewer use test Running tests that layout and measure text will not yield consistent results across various platforms Enabling this option will make font resolution default to the Ahem test font on all disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
internal::CopyableLambda< T > MakeCopyable(T lambda)
Definition ref_ptr.h:261
std::shared_ptr< flutter::OverlayLayer > layer
FlutterTouchInterceptingView * touch_interceptor
NSObject< FlutterPlatformView > * view
static constexpr DlColor kTransparent()
Definition dl_color.h:68
A 4x4 matrix using column-major storage.
Definition matrix.h:37
Scalar m[16]
Definition matrix.h:39
const uintptr_t id
#define TRACE_EVENT0(category_group, name)
int BOOL