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

Commit 971a639

Browse files
authored
Allow for dynamic thread merging on IOS for embedded view mutations (#9819)
After pre-roll we know if there have been any mutations made to the IOS embedded UIViews. If there are any mutations and the thread configuration is such chat the mutations will be committed on an illegal thread (GPU thread), we merge the threads and keep them merged until the lease expires. The lease is currently set to expire after 10 frames of no mutations. If there are any mutations in the interim we extend the lease. TaskRunnerMerger will ultimately be responsible for enforcing the correct thread configurations. This configuration will be inactive even after this change since still use the same thread when we create the iOS engine. That is slated to change in the coming PRs.
1 parent c92a0d9 commit 971a639

21 files changed

+529
-52
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ FILE: ../../../flutter/fml/eintr_wrapper.h
102102
FILE: ../../../flutter/fml/file.cc
103103
FILE: ../../../flutter/fml/file.h
104104
FILE: ../../../flutter/fml/file_unittest.cc
105+
FILE: ../../../flutter/fml/gpu_thread_merger.cc
106+
FILE: ../../../flutter/fml/gpu_thread_merger.h
107+
FILE: ../../../flutter/fml/gpu_thread_merger_unittests.cc
105108
FILE: ../../../flutter/fml/icu_util.cc
106109
FILE: ../../../flutter/fml/icu_util.h
107110
FILE: ../../../flutter/fml/log_level.h

flow/compositor_context.cc

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ std::unique_ptr<CompositorContext::ScopedFrame> CompositorContext::AcquireFrame(
3434
SkCanvas* canvas,
3535
ExternalViewEmbedder* view_embedder,
3636
const SkMatrix& root_surface_transformation,
37-
bool instrumentation_enabled) {
38-
return std::make_unique<ScopedFrame>(*this, gr_context, canvas, view_embedder,
39-
root_surface_transformation,
40-
instrumentation_enabled);
37+
bool instrumentation_enabled,
38+
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger) {
39+
return std::make_unique<ScopedFrame>(
40+
*this, gr_context, canvas, view_embedder, root_surface_transformation,
41+
instrumentation_enabled, gpu_thread_merger);
4142
}
4243

4344
CompositorContext::ScopedFrame::ScopedFrame(
@@ -46,13 +47,15 @@ CompositorContext::ScopedFrame::ScopedFrame(
4647
SkCanvas* canvas,
4748
ExternalViewEmbedder* view_embedder,
4849
const SkMatrix& root_surface_transformation,
49-
bool instrumentation_enabled)
50+
bool instrumentation_enabled,
51+
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger)
5052
: context_(context),
5153
gr_context_(gr_context),
5254
canvas_(canvas),
5355
view_embedder_(view_embedder),
5456
root_surface_transformation_(root_surface_transformation),
55-
instrumentation_enabled_(instrumentation_enabled) {
57+
instrumentation_enabled_(instrumentation_enabled),
58+
gpu_thread_merger_(gpu_thread_merger) {
5659
context_.BeginFrame(*this, instrumentation_enabled_);
5760
}
5861

@@ -64,6 +67,14 @@ RasterStatus CompositorContext::ScopedFrame::Raster(
6467
flutter::LayerTree& layer_tree,
6568
bool ignore_raster_cache) {
6669
layer_tree.Preroll(*this, ignore_raster_cache);
70+
PostPrerollResult post_preroll_result = PostPrerollResult::kSuccess;
71+
if (view_embedder_ && gpu_thread_merger_) {
72+
post_preroll_result = view_embedder_->PostPrerollAction(gpu_thread_merger_);
73+
}
74+
75+
if (post_preroll_result == PostPrerollResult::kResubmitFrame) {
76+
return RasterStatus::kResubmit;
77+
}
6778
// Clearing canvas after preroll reduces one render target switch when preroll
6879
// paints some raster cache.
6980
if (canvas()) {

flow/compositor_context.h

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "flutter/flow/instrumentation.h"
1313
#include "flutter/flow/raster_cache.h"
1414
#include "flutter/flow/texture.h"
15+
#include "flutter/fml/gpu_thread_merger.h"
1516
#include "flutter/fml/macros.h"
1617
#include "third_party/skia/include/core/SkCanvas.h"
1718
#include "third_party/skia/include/core/SkPictureRecorder.h"
@@ -20,7 +21,19 @@ namespace flutter {
2021

2122
class LayerTree;
2223

23-
enum class RasterStatus { kSuccess, kFailed };
24+
enum class RasterStatus {
25+
// Frame has successfully rasterized.
26+
kSuccess,
27+
// Frame needs to be resubmitted for rasterization. This is
28+
// currently only called when thread configuration change occurs.
29+
kResubmit,
30+
// Frame has been successfully rasterized, but "there are additional items in
31+
// the pipeline waiting to be consumed. This is currently
32+
// only called when thread configuration change occurs.
33+
kEnqueuePipeline,
34+
// Failed to rasterize the frame.
35+
kFailed
36+
};
2437

2538
class CompositorContext {
2639
public:
@@ -31,7 +44,8 @@ class CompositorContext {
3144
SkCanvas* canvas,
3245
ExternalViewEmbedder* view_embedder,
3346
const SkMatrix& root_surface_transformation,
34-
bool instrumentation_enabled);
47+
bool instrumentation_enabled,
48+
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger);
3549

3650
virtual ~ScopedFrame();
3751

@@ -57,6 +71,7 @@ class CompositorContext {
5771
ExternalViewEmbedder* view_embedder_;
5872
const SkMatrix& root_surface_transformation_;
5973
const bool instrumentation_enabled_;
74+
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger_;
6075

6176
FML_DISALLOW_COPY_AND_ASSIGN(ScopedFrame);
6277
};
@@ -70,7 +85,8 @@ class CompositorContext {
7085
SkCanvas* canvas,
7186
ExternalViewEmbedder* view_embedder,
7287
const SkMatrix& root_surface_transformation,
73-
bool instrumentation_enabled);
88+
bool instrumentation_enabled,
89+
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger);
7490

7591
void OnGrContextCreated();
7692

flow/embedded_views.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <vector>
99

10+
#include "flutter/fml/gpu_thread_merger.h"
1011
#include "flutter/fml/memory/ref_counted.h"
1112
#include "third_party/skia/include/core/SkCanvas.h"
1213
#include "third_party/skia/include/core/SkPath.h"
@@ -182,6 +183,8 @@ class EmbeddedViewParams {
182183
}
183184
};
184185

186+
enum class PostPrerollResult { kResubmitFrame, kSuccess };
187+
185188
// This is only used on iOS when running in a non headless mode,
186189
// in this case ExternalViewEmbedder is a reference to the
187190
// FlutterPlatformViewsController which is owned by FlutterViewController.
@@ -191,10 +194,6 @@ class ExternalViewEmbedder {
191194
public:
192195
ExternalViewEmbedder() = default;
193196

194-
// This will return true after pre-roll if any of the embedded views
195-
// have mutated for last layer tree.
196-
virtual bool HasPendingViewOperations() = 0;
197-
198197
// Call this in-lieu of |SubmitFrame| to clear pre-roll state and
199198
// sets the stage for the next pre-roll.
200199
virtual void CancelFrame() = 0;
@@ -205,6 +204,15 @@ class ExternalViewEmbedder {
205204
int view_id,
206205
std::unique_ptr<EmbeddedViewParams> params) = 0;
207206

207+
// This needs to get called after |Preroll| finishes on the layer tree.
208+
// Returns kResubmitFrame if the frame needs to be processed again, this is
209+
// after it does any requisite tasks needed to bring itself to a valid state.
210+
// Returns kSuccess if the view embedder is already in a valid state.
211+
virtual PostPrerollResult PostPrerollAction(
212+
fml::RefPtr<fml::GpuThreadMerger> gpu_thread_merger) {
213+
return PostPrerollResult::kSuccess;
214+
}
215+
208216
virtual std::vector<SkCanvas*> GetCurrentCanvases() = 0;
209217

210218
// Must be called on the UI thread.

fml/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ source_set("fml") {
2121
"eintr_wrapper.h",
2222
"file.cc",
2323
"file.h",
24+
"gpu_thread_merger.cc",
25+
"gpu_thread_merger.h",
2426
"icu_util.cc",
2527
"icu_util.h",
2628
"log_level.h",
@@ -200,6 +202,7 @@ executable("fml_unittests") {
200202
"base32_unittest.cc",
201203
"command_line_unittest.cc",
202204
"file_unittest.cc",
205+
"gpu_thread_merger_unittests.cc",
203206
"memory/ref_counted_unittest.cc",
204207
"memory/weak_ptr_unittest.cc",
205208
"message_loop_task_queues_merge_unmerge_unittests.cc",

fml/gpu_thread_merger.cc

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
5+
#define FML_USED_ON_EMBEDDER
6+
7+
#include "flutter/fml/gpu_thread_merger.h"
8+
#include "flutter/fml/message_loop_impl.h"
9+
10+
namespace fml {
11+
12+
const int GpuThreadMerger::kLeaseNotSet = -1;
13+
14+
GpuThreadMerger::GpuThreadMerger(fml::TaskQueueId platform_queue_id,
15+
fml::TaskQueueId gpu_queue_id)
16+
: platform_queue_id_(platform_queue_id),
17+
gpu_queue_id_(gpu_queue_id),
18+
task_queues_(fml::MessageLoopTaskQueues::GetInstance()),
19+
lease_term_(kLeaseNotSet) {
20+
is_merged_ = task_queues_->Owns(platform_queue_id_, gpu_queue_id_);
21+
}
22+
23+
void GpuThreadMerger::MergeWithLease(size_t lease_term) {
24+
FML_DCHECK(lease_term > 0) << "lease_term should be positive.";
25+
if (!is_merged_) {
26+
is_merged_ = task_queues_->Merge(platform_queue_id_, gpu_queue_id_);
27+
lease_term_ = lease_term;
28+
}
29+
}
30+
31+
bool GpuThreadMerger::IsOnRasterizingThread() {
32+
const auto current_queue_id = MessageLoop::GetCurrentTaskQueueId();
33+
if (is_merged_) {
34+
return current_queue_id == platform_queue_id_;
35+
} else {
36+
return current_queue_id == gpu_queue_id_;
37+
}
38+
}
39+
40+
void GpuThreadMerger::ExtendLeaseTo(size_t lease_term) {
41+
FML_DCHECK(lease_term > 0) << "lease_term should be positive.";
42+
if (lease_term_ != kLeaseNotSet && (int)lease_term > lease_term_) {
43+
lease_term_ = lease_term;
44+
}
45+
}
46+
47+
bool GpuThreadMerger::IsMerged() const {
48+
return is_merged_;
49+
}
50+
51+
GpuThreadStatus GpuThreadMerger::DecrementLease() {
52+
if (!is_merged_) {
53+
return GpuThreadStatus::kRemainsUnmerged;
54+
}
55+
56+
// we haven't been set to merge.
57+
if (lease_term_ == kLeaseNotSet) {
58+
return GpuThreadStatus::kRemainsUnmerged;
59+
}
60+
61+
FML_DCHECK(lease_term_ > 0)
62+
<< "lease_term should always be positive when merged.";
63+
lease_term_--;
64+
if (lease_term_ == 0) {
65+
bool success = task_queues_->Unmerge(platform_queue_id_);
66+
FML_CHECK(success) << "Unable to un-merge the GPU and platform threads.";
67+
is_merged_ = false;
68+
return GpuThreadStatus::kUnmergedNow;
69+
}
70+
71+
return GpuThreadStatus::kRemainsMerged;
72+
}
73+
74+
} // namespace fml

fml/gpu_thread_merger.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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+
5+
#ifndef FML_SHELL_COMMON_TASK_RUNNER_MERGER_H_
6+
#define FML_SHELL_COMMON_TASK_RUNNER_MERGER_H_
7+
8+
#include "flutter/fml/macros.h"
9+
#include "flutter/fml/memory/ref_counted.h"
10+
#include "flutter/fml/message_loop_task_queues.h"
11+
12+
namespace fml {
13+
14+
class MessageLoopImpl;
15+
16+
enum class GpuThreadStatus { kRemainsMerged, kRemainsUnmerged, kUnmergedNow };
17+
18+
class GpuThreadMerger : public fml::RefCountedThreadSafe<GpuThreadMerger> {
19+
public:
20+
// Merges the GPU thread into platform thread for the duration of
21+
// the lease term. Lease is managed by the caller by either calling
22+
// |ExtendLeaseTo| or |DecrementLease|.
23+
// When the caller merges with a lease term of say 2. The threads
24+
// are going to remain merged until 2 invocations of |DecreaseLease|,
25+
// unless an |ExtendLeaseTo| gets called.
26+
void MergeWithLease(size_t lease_term);
27+
28+
void ExtendLeaseTo(size_t lease_term);
29+
30+
// Returns |GpuThreadStatus::kUnmergedNow| if this call resulted in splitting
31+
// the GPU and platform threads. Reduces the lease term by 1.
32+
GpuThreadStatus DecrementLease();
33+
34+
bool IsMerged() const;
35+
36+
GpuThreadMerger(fml::TaskQueueId platform_queue_id,
37+
fml::TaskQueueId gpu_queue_id);
38+
39+
// Returns true if the the current thread owns rasterizing.
40+
// When the threads are merged, platform thread owns rasterizing.
41+
// When un-merged, gpu thread owns rasterizing.
42+
bool IsOnRasterizingThread();
43+
44+
private:
45+
static const int kLeaseNotSet;
46+
fml::TaskQueueId platform_queue_id_;
47+
fml::TaskQueueId gpu_queue_id_;
48+
fml::RefPtr<fml::MessageLoopTaskQueues> task_queues_;
49+
std::atomic_int lease_term_;
50+
bool is_merged_;
51+
52+
FML_FRIEND_REF_COUNTED_THREAD_SAFE(GpuThreadMerger);
53+
FML_FRIEND_MAKE_REF_COUNTED(GpuThreadMerger);
54+
FML_DISALLOW_COPY_AND_ASSIGN(GpuThreadMerger);
55+
};
56+
57+
} // namespace fml
58+
59+
#endif // FML_SHELL_COMMON_TASK_RUNNER_MERGER_H_

0 commit comments

Comments
 (0)