Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[iOS] Supported rendering platform views without merging the raster thread. #53826

Merged
merged 55 commits into from
Aug 1, 2024

Conversation

jonahwilliams
Copy link
Member

@jonahwilliams jonahwilliams commented Jul 11, 2024

Remove the need to merge raster and platform thread in the presence of platform views by defering UIView mutation and presentation of flutter views into separate platform thread task. Fixes priority inversion problem cause by platform thread blocking on drawable aquisition.

Open questions:

  • Fixed What is a better interface for handling the partial submit with impeller. (TBD)
  • Fixed Update: We Don't | How do we fix this for Skia Fixed
  • Fixed Update: Done, we post a task to the platform thread. Is there a shorter term solution for creating overlay layers on the raster thread. Fixed
  • Fixed Update: seems to. Does this perform well enough (independent of platform/ui thread merge and w/ thread merge). Fixed

Fixes flutter/flutter#142841
part of flutter/flutter#150525

return nullptr;
}

void FlutterPlatformViewLayerPool::CreateLayer(GrDirectContext* gr_context,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably the biggest PITA. Because I can only construct these on the platform thread, right now frame 0 with platform views will skip some number of overlay layers. Its possible that the FlutterMetalLayer solves this partially

Copy link
Member

@knopp knopp Jul 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we move away from CAMetalLayer (or even FlutterMetalLayer) and adopt custom composition, we can solve this easily because you can allocate IOSurfaces on any thread, when they are not tied to particular CALayer. We can also have much cheaper overlays. Right now we use up to 2 CAMetaLayers for each FlutterView overlay, which is 6 full screen surfaces. With custom composition it is possible to set a single iOSurface as content of as many CALayers as needed each showing different part, so you can many unobstructed platform view overlays with single layer.

On macOS surfaces are allocated on raster thread and the overlay sublayers are crated here.

Here are relevant files:

https://github.com/flutter/engine/blob/main/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm

https://github.com/flutter/engine/blob/main/shell/platform/darwin/macos/framework/Source/FlutterCompositor.mm

if (canvas() && clip_rect) {
canvas()->Translate(-clip_rect->x(), -clip_rect->y());
}
// if (canvas() && clip_rect) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to figure out why this was firing. Possibly we're turning off partial repaint based on thread merging when it should be based on platform view state.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need better way to disable partial repaint. I think instead of

 bool force_full_repaint =
          external_view_embedder_ &&
          (!raster_thread_merger_ || raster_thread_merger_->IsMerged());

we should reset damage and clipRect inside CompositorContext::ScopedFrame::Raster after Preroll when we know that there are platform view present.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be after we preroll with damage based cull rect though?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, fair point. Though I'm not 100% sure where the cullrect is used during preroll, the actual culling is done through quickReject during Paint. I think long time ago it was done in preroll setting a flag on layer.

Maybe better to mark entire frame dirty in PlatformViewLayer::Diff. Just need to make sure it behaves correctly when diff is skipped for retained layers. I'll look into this as a separate PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -685,8 +638,11 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
auto did_submit = true;
auto num_platform_views = composition_order_.size();

// TODO(hellohuanlin) this double for-loop is expensive with wasted computations.
// See: https://github.com/flutter/flutter/issues/145802
size_t missing_layer_count = 0;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this design, if we're missing an overlay layer then it won't render until the next frame. I think we can handle that by forcing resubmission of the frame but I'll have to follow up on it.

Copy link
Member

@cbracken cbracken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sending out the super tiny nitpicks while I review the changes to FlutterPlatformViews*.

@@ -286,7 +286,7 @@ - (void)flutterPrepareForPresent:(nonnull id<MTLCommandBuffer>)commandBuffer;
// If the threads have been merged, or there is a pending frame capture,
// then block on cmd buffer scheduling to ensure that the
// transaction/capture work correctly.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above comment isn't adding much and rather than making it even longer to cover the new case, we could probably just yank out a local and the code would do the exact same amount of documenting as the comment currently does:

BOOL threads_merged = [[NSThread currentThread] isMainThread];

Or maybe extract the whole blobby conditional to a bool named transactions_enabled or something.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I can move this to the surface_frame doc comment?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

Copy link
Member

@cbracken cbracken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second round of comments. Reviewing the tests, then I'll be done.


// 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(unused_layers, composition_order, active_composition_order);

// If the frame is submitted with embedded platform views,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep -- I'd be tempted to just delete the whole comment.

Copy link
Member

@cbracken cbracken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few more small comments. Other than that:

LGTM stamp from a Japanese personal seal

// is a non-Flutter UIView active, such as in add2app or a
// presentViewController page transition, then this will cause CoreAnimation
// assertion errors and exit the app.
bool present_with_transaction = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is necessary for things to correctly work with FlutterMetalLayer disabled?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FlutterMetalLayer is always presenting with a CATransaction, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it always makes the jump to platform thread (can't set CALayer content on background thread).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. I ported this over from the existing logic, but once we remove the old metal layer usage we could remove this as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@hellohuanlin hellohuanlin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with this part of the code, so more for me to ask and learn.

My suggestion: It'd be very helpful to read if we have a comment on each function about which thread it runs on. Otherwise I would have to jump back and forth in the code to figure it out.

// to run on the platform thread after no platform view is rendered.
//
// Note: this is an arbitrary number.
static const int kDefaultMergedLeaseDuration = 10;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ask to learn - are we still merging the thread (given this variable name?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are merging threads on the iOS simulator (technically we only need to when using the software backend but that is a harder condition to test). Once we remove skia and the software backend we can remove this condition.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May I ask what's the difference between the simulator and device that we have to merge the thread on simulator?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its not the simulator, its the software backend. The software backend surfaces cannot be used from multiple threads, which this change requires.

ChildClippingView* clipping_view = [[ChildClippingView alloc] initWithFrame:CGRectZero];
[clipping_view addSubview:touch_interceptor];
root_views_[viewId] = fml::scoped_nsobject<UIView>(clipping_view);

platform_views_.emplace(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice clean up with the struct

slices_.clear();
current_composition_params_.clear();
clip_count_.clear();
views_to_recomposite_.clear();
layer_pool_->RecycleLayers();
visited_platform_views_.clear();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these variables ever mutated on main thread as well? Or only on raster thread?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only on the raster thread

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Are all of the embedder callback APIs (e.g. SubmitFrame etc) now run on raster thread?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I think it's indeed a simpler (and better) design

size_t required_overlay_layers) {
TRACE_EVENT0("flutter", "FlutterPlatformViewsController::CreateMissingLayers");

if (required_overlay_layers <= layer_pool_->size()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like the layer_pool is accessed here on raster thread, but mutated below in CreateLayer on main thread (if im reading it right). I'm a bit concerned about race condition here

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we block the raster thread on completion via a latch, there is no race

@jonahwilliams
Copy link
Member Author

Added some more comments describing the intended thread and removed some more dead code.

@jonahwilliams jonahwilliams added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 1, 2024
@auto-submit auto-submit bot merged commit 1cbe88e into flutter:main Aug 1, 2024
31 checks passed
void Reset();

// Encode rendering for the Flutter overlay views and queue up perform platform view mutations.
//
// Runs on the raster thread.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding these!

engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Aug 1, 2024
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Aug 1, 2024
…152707)

flutter/engine@17e3c7d...1cbe88e

2024-08-01 jonahwilliams@google.com [iOS] Supported rendering platform views without merging the raster thread. (flutter/engine#53826)
2024-08-01 jhy03261997@gmail.com Set deep linking flag to true by default (flutter/engine#52350)
2024-08-01 leroux_bruno@yahoo.fr [Android] Revert "Reset IME state on clear text input client" (flutter/engine#54277)
2024-08-01 skia-flutter-autoroll@skia.org Roll Skia from 03732b9f885e to ddb6901e6141 (5 revisions) (flutter/engine#54287)
2024-08-01 skia-flutter-autoroll@skia.org Roll Dart SDK from 5acd806b6dad to acbbbe73b5eb (1 revision) (flutter/engine#54286)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC jacksongardner@google.com,rmistry@google.com,zra@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
TytaniumDev pushed a commit to TytaniumDev/flutter that referenced this pull request Aug 7, 2024
…lutter#152707)

flutter/engine@17e3c7d...1cbe88e

2024-08-01 jonahwilliams@google.com [iOS] Supported rendering platform views without merging the raster thread. (flutter/engine#53826)
2024-08-01 jhy03261997@gmail.com Set deep linking flag to true by default (flutter/engine#52350)
2024-08-01 leroux_bruno@yahoo.fr [Android] Revert "Reset IME state on clear text input client" (flutter/engine#54277)
2024-08-01 skia-flutter-autoroll@skia.org Roll Skia from 03732b9f885e to ddb6901e6141 (5 revisions) (flutter/engine#54287)
2024-08-01 skia-flutter-autoroll@skia.org Roll Dart SDK from 5acd806b6dad to acbbbe73b5eb (1 revision) (flutter/engine#54286)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC jacksongardner@google.com,rmistry@google.com,zra@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Buchimi pushed a commit to Buchimi/flutter that referenced this pull request Sep 2, 2024
…lutter#152707)

flutter/engine@17e3c7d...1cbe88e

2024-08-01 jonahwilliams@google.com [iOS] Supported rendering platform views without merging the raster thread. (flutter/engine#53826)
2024-08-01 jhy03261997@gmail.com Set deep linking flag to true by default (flutter/engine#52350)
2024-08-01 leroux_bruno@yahoo.fr [Android] Revert "Reset IME state on clear text input client" (flutter/engine#54277)
2024-08-01 skia-flutter-autoroll@skia.org Roll Skia from 03732b9f885e to ddb6901e6141 (5 revisions) (flutter/engine#54287)
2024-08-01 skia-flutter-autoroll@skia.org Roll Dart SDK from 5acd806b6dad to acbbbe73b5eb (1 revision) (flutter/engine#54286)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-engine-flutter-autoroll
Please CC jacksongardner@google.com,rmistry@google.com,zra@google.com on the revert to ensure that a human
is aware of the problem.

To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
auto-submit bot pushed a commit that referenced this pull request Sep 3, 2024
…4820)

It turns out that when there are multiple paths to clip, they are unioned together, rather than intersected. But clipping paths need to be intersected. 

There isn't any good way to intersect arbitrary paths. However, it is easy to intersect rect paths, which is the most common use case. Then we simply fallback to software rendering if we have to intersect non-rect paths. That is: 

**Case 1** Only 1 clipping path (either rect path or arbitrary path):
Hardware rendering. This should be the most common use case

**Case 2** Multiple rect clipping path: 
Hardware rendering. This is also common, and it's the linked issue that we are fixing. 

**Case 3** Other complex case (multiple non-rect clipping path)
Fallback to software rendering. This should be rare. 

After #53826, we don't have a working benchmark that measures the main thread anymore. However, this PR shouldn't impact our ad benchmark, since it only has 1 clipping path. I will verify manually by checking Instruments and make sure no software rendering is happening. But we really should make the benchmark working again, not just for performance improvement, but also for monitoring regression. 

*List which issues are fixed by this PR. You must list at least one issue.*

Fixes flutter/flutter#153904

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
jmagman pushed a commit to jmagman/engine that referenced this pull request Sep 6, 2024
…utter#54820)

It turns out that when there are multiple paths to clip, they are unioned together, rather than intersected. But clipping paths need to be intersected.

There isn't any good way to intersect arbitrary paths. However, it is easy to intersect rect paths, which is the most common use case. Then we simply fallback to software rendering if we have to intersect non-rect paths. That is:

**Case 1** Only 1 clipping path (either rect path or arbitrary path):
Hardware rendering. This should be the most common use case

**Case 2** Multiple rect clipping path:
Hardware rendering. This is also common, and it's the linked issue that we are fixing.

**Case 3** Other complex case (multiple non-rect clipping path)
Fallback to software rendering. This should be rare.

After flutter#53826, we don't have a working benchmark that measures the main thread anymore. However, this PR shouldn't impact our ad benchmark, since it only has 1 clipping path. I will verify manually by checking Instruments and make sure no software rendering is happening. But we really should make the benchmark working again, not just for performance improvement, but also for monitoring regression.

*List which issues are fixed by this PR. You must list at least one issue.*

Fixes flutter/flutter#153904

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
autosubmit Merge PR when tree becomes green via auto submit App e: impeller platform-ios
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Thread Performance Checker warns on iOS: Thread running at QOS_CLASS_USER_INTERACTIVE waiting on a thread without a QoS class specified.
6 participants