-
Notifications
You must be signed in to change notification settings - Fork 6k
Synchronize main thread and gpu thread for first render frame #9506
Changes from 17 commits
5aa9df6
28f23c8
d93a5bc
6b55244
d246253
700f062
bc9cd95
ac7266b
040c146
48db249
8d63518
8a676f7
2915fe9
016c96d
d040af8
41406b9
6068985
1a7c9e1
82d19a7
9df3a65
b9d4c14
b6d35b5
4e25624
2b88ff8
f0b3a40
fdfa45c
aa12852
ad944f3
d55bf99
f7e2b8c
9c3b023
e713db8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -457,18 +457,22 @@ void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) { | |||
| // This is a synchronous operation because certain platforms depend on | ||||
| // setup/suspension of all activities that may be interacting with the GPU in | ||||
| // a synchronous fashion. | ||||
|
|
||||
| fml::AutoResetWaitableEvent latch; | ||||
| auto gpu_task = fml::MakeCopyable([rasterizer = rasterizer_->GetWeakPtr(), // | ||||
| surface = std::move(surface), // | ||||
| &latch]() mutable { | ||||
| if (rasterizer) { | ||||
| rasterizer->Setup(std::move(surface)); | ||||
| } | ||||
| // Step 3: All done. Signal the latch that the platform thread is waiting | ||||
| // on. | ||||
| latch.Signal(); | ||||
| }); | ||||
| auto gpu_task = | ||||
| fml::MakeCopyable([& waiting_for_first_frame = waiting_for_first_frame_, | ||||
| rasterizer = rasterizer_->GetWeakPtr(), // | ||||
| surface = std::move(surface), // | ||||
| &latch]() mutable { | ||||
| if (rasterizer) { | ||||
| rasterizer->Setup(std::move(surface)); | ||||
| } | ||||
|
|
||||
| waiting_for_first_frame.store(true); | ||||
|
|
||||
| // Step 3: All done. Signal the latch that the platform thread is | ||||
| // waiting on. | ||||
| latch.Signal(); | ||||
| }); | ||||
|
|
||||
| // The normal flow executed by this method is that the platform thread is | ||||
| // starting the sequence and waiting on the latch. Later the UI thread posts | ||||
|
|
@@ -564,7 +568,7 @@ void Shell::OnPlatformViewDestroyed() { | |||
| }; | ||||
|
|
||||
| // The normal flow executed by this method is that the platform thread is | ||||
| // starting the sequence and waiting on the latch. Later the UI thread posts | ||||
| // starting the sequence and w on the latch. Later the UI thread posts | ||||
| // gpu_task to the GPU thread triggers signaling the latch(on the IO thread). | ||||
| // If the GPU the and platform threads are the same this results in a deadlock | ||||
| // as the gpu_task will never be posted to the plaform/gpu thread that is | ||||
|
|
@@ -800,10 +804,17 @@ void Shell::OnAnimatorDraw(fml::RefPtr<Pipeline<flutter::LayerTree>> pipeline) { | |||
| FML_DCHECK(is_setup_); | ||||
|
|
||||
| task_runners_.GetGPUTaskRunner()->PostTask( | ||||
| [rasterizer = rasterizer_->GetWeakPtr(), | ||||
| [& waiting_for_first_frame = waiting_for_first_frame_, | ||||
| &waiting_for_first_frame_condition = waiting_for_first_frame_condition_, | ||||
| rasterizer = rasterizer_->GetWeakPtr(), | ||||
| pipeline = std::move(pipeline)]() { | ||||
| if (rasterizer) { | ||||
| rasterizer->Draw(pipeline); | ||||
|
|
||||
| if (waiting_for_first_frame.load()) { | ||||
| waiting_for_first_frame.store(false); | ||||
| waiting_for_first_frame_condition.notify_all(); | ||||
| } | ||||
| } | ||||
| }); | ||||
| } | ||||
|
|
@@ -1240,4 +1251,17 @@ Rasterizer::Screenshot Shell::Screenshot( | |||
| return screenshot; | ||||
| } | ||||
|
|
||||
| bool Shell::WaitForFirstFrame(fml::TimeDelta timeout) { | ||||
|
||||
| FML_DCHECK(is_setup_); | ||||
| FML_DCHECK(!task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); | ||||
|
||||
| FML_DCHECK(!task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread()); | ||||
| std::unique_lock<std::mutex> lock(waiting_for_first_frame_mutex_); | ||||
| bool success = waiting_for_first_frame_condition_.wait_for( | ||||
| lock, std::chrono::milliseconds(timeout.ToMilliseconds()), | ||||
| [& waiting_for_first_frame = waiting_for_first_frame_] { | ||||
| return !waiting_for_first_frame.load(); | ||||
| }); | ||||
| return !success; | ||||
|
||||
| bool WaitWithTimeout(TimeDelta timeout); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good finding. I think we probably should change that return type to enum too to reduce confusions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, i've added the Status class as discussed.
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -243,6 +243,12 @@ class Shell final : public PlatformView::Delegate, | |||
| Rasterizer::Screenshot Screenshot(Rasterizer::ScreenshotType type, | ||||
| bool base64_encode); | ||||
|
|
||||
| /// Pauses the calling thread until the first frame is presented. | ||||
| ///\details Don't call this from the GPU thread or the UI thread or you will | ||||
|
||||
| /// create a deadlock. | ||||
| ///\returns true when there has been a timeout. | ||||
| bool WaitForFirstFrame(fml::TimeDelta timeout); | ||||
|
||||
| bool WaitWithTimeout(TimeDelta timeout); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a Status class as discuss offline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: std::atomic_bool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::atomic<bool> is used later in the file.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -518,5 +518,58 @@ TEST_F(ShellTest, ReportTimingsIsCalledImmediatelyAfterTheFirstFrame) { | |
| ASSERT_EQ(timestamps.size(), FrameTiming::kCount); | ||
| } | ||
|
|
||
| TEST_F(ShellTest, WaitForFirstFrame) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please create a unit test for a shell in a single threaded configuration as well as one in which the platform and GPU task runners are the same. The former configuration is used by the test runners and the latter by iOS when there is a platform view composited inline.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| auto settings = CreateSettingsForFixture(); | ||
| std::unique_ptr<Shell> shell = CreateShell(settings); | ||
|
|
||
| // Create the surface needed by rasterizer | ||
| PlatformViewNotifyCreated(shell.get()); | ||
|
|
||
| auto configuration = RunConfiguration::InferFromSettings(settings); | ||
| configuration.SetEntrypoint("emptyMain"); | ||
|
|
||
| RunEngine(shell.get(), std::move(configuration)); | ||
| PumpOneFrame(shell.get()); | ||
| bool result = | ||
| shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000)); | ||
| ASSERT_FALSE(result); | ||
| } | ||
|
|
||
| TEST_F(ShellTest, WaitForFirstFrameTimeout) { | ||
| auto settings = CreateSettingsForFixture(); | ||
| std::unique_ptr<Shell> shell = CreateShell(settings); | ||
|
|
||
| // Create the surface needed by rasterizer | ||
| PlatformViewNotifyCreated(shell.get()); | ||
|
|
||
| auto configuration = RunConfiguration::InferFromSettings(settings); | ||
| configuration.SetEntrypoint("emptyMain"); | ||
|
|
||
| RunEngine(shell.get(), std::move(configuration)); | ||
| bool result = shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(10)); | ||
| ASSERT_TRUE(result); | ||
| } | ||
|
|
||
| TEST_F(ShellTest, WaitForFirstFrameMultiple) { | ||
| auto settings = CreateSettingsForFixture(); | ||
| std::unique_ptr<Shell> shell = CreateShell(settings); | ||
|
|
||
| // Create the surface needed by rasterizer | ||
| PlatformViewNotifyCreated(shell.get()); | ||
|
|
||
| auto configuration = RunConfiguration::InferFromSettings(settings); | ||
| configuration.SetEntrypoint("emptyMain"); | ||
|
|
||
| RunEngine(shell.get(), std::move(configuration)); | ||
| PumpOneFrame(shell.get()); | ||
| bool result = | ||
| shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000)); | ||
| ASSERT_FALSE(result); | ||
| for (int i = 0; i < 100; ++i) { | ||
| result = shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1)); | ||
| ASSERT_FALSE(result); | ||
| } | ||
| } | ||
|
|
||
| } // namespace testing | ||
| } // namespace flutter | ||
Uh oh!
There was an error while loading. Please reload this page.