mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Synchronize main thread and gpu thread for first render frame (#9506)
Got rid of the black frame by synchronizing the main thread with the gpu thread to make sure a frame is rendered before presenting the view.
This commit is contained in:
parent
f600ae830d
commit
9776043ea5
@ -186,6 +186,7 @@ FILE: ../../../flutter/fml/platform/win/native_library_win.cc
|
||||
FILE: ../../../flutter/fml/platform/win/paths_win.cc
|
||||
FILE: ../../../flutter/fml/platform/win/wstring_conversion.h
|
||||
FILE: ../../../flutter/fml/size.h
|
||||
FILE: ../../../flutter/fml/status.h
|
||||
FILE: ../../../flutter/fml/synchronization/atomic_object.h
|
||||
FILE: ../../../flutter/fml/synchronization/count_down_latch.cc
|
||||
FILE: ../../../flutter/fml/synchronization/count_down_latch.h
|
||||
|
||||
81
fml/status.h
Normal file
81
fml/status.h
Normal file
@ -0,0 +1,81 @@
|
||||
// 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_FML_STATUS_H_
|
||||
#define FLUTTER_FML_STATUS_H_
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace fml {
|
||||
|
||||
enum class StatusCode {
|
||||
kOk,
|
||||
kCancelled,
|
||||
kUnknown,
|
||||
kInvalidArgument,
|
||||
kDeadlineExceeded,
|
||||
kNotFound,
|
||||
kAlreadyExists,
|
||||
kPermissionDenied,
|
||||
kResourceExhausted,
|
||||
kFailedPrecondition,
|
||||
kAborted,
|
||||
kOutOfRange,
|
||||
kUnimplemented,
|
||||
kInternal,
|
||||
kUnavailable,
|
||||
kDataLoss,
|
||||
kUnauthenticated
|
||||
};
|
||||
|
||||
/// Class that represents the resolution of the execution of a procedure. This
|
||||
/// is used similarly to how exceptions might be used, typically as the return
|
||||
/// value to a synchronous procedure or an argument to an asynchronous callback.
|
||||
class Status final {
|
||||
public:
|
||||
/// Creates an 'ok' status.
|
||||
Status();
|
||||
|
||||
Status(fml::StatusCode code, std::string_view message);
|
||||
|
||||
fml::StatusCode code() const;
|
||||
|
||||
/// A noop that helps with static analysis tools if you decide to ignore an
|
||||
/// error.
|
||||
void IgnoreError() const;
|
||||
|
||||
/// @return 'true' when the code is kOk.
|
||||
bool ok() const;
|
||||
|
||||
std::string_view message() const;
|
||||
|
||||
private:
|
||||
fml::StatusCode code_;
|
||||
std::string_view message_;
|
||||
};
|
||||
|
||||
inline Status::Status() : code_(fml::StatusCode::kOk), message_() {}
|
||||
|
||||
inline Status::Status(fml::StatusCode code, std::string_view message)
|
||||
: code_(code), message_(message) {}
|
||||
|
||||
inline fml::StatusCode Status::code() const {
|
||||
return code_;
|
||||
}
|
||||
|
||||
inline void Status::IgnoreError() const {
|
||||
// noop
|
||||
}
|
||||
|
||||
inline bool Status::ok() const {
|
||||
return code_ == fml::StatusCode::kOk;
|
||||
}
|
||||
|
||||
inline std::string_view Status::message() const {
|
||||
return message_;
|
||||
}
|
||||
|
||||
} // namespace fml
|
||||
|
||||
#endif // FLUTTER_FML_SIZE_H_
|
||||
@ -454,18 +454,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
|
||||
@ -787,10 +791,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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1226,4 +1237,26 @@ Rasterizer::Screenshot Shell::Screenshot(
|
||||
return screenshot;
|
||||
}
|
||||
|
||||
fml::Status Shell::WaitForFirstFrame(fml::TimeDelta timeout) {
|
||||
FML_DCHECK(is_setup_);
|
||||
if (task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread() ||
|
||||
task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread()) {
|
||||
return fml::Status(fml::StatusCode::kFailedPrecondition,
|
||||
"WaitForFirstFrame called from thread that can't wait "
|
||||
"because it is responsible for generating the frame.");
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
if (success) {
|
||||
return fml::Status();
|
||||
} else {
|
||||
return fml::Status(fml::StatusCode::kDeadlineExceeded, "timeout");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace flutter
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "flutter/fml/memory/ref_ptr.h"
|
||||
#include "flutter/fml/memory/thread_checker.h"
|
||||
#include "flutter/fml/memory/weak_ptr.h"
|
||||
#include "flutter/fml/status.h"
|
||||
#include "flutter/fml/synchronization/thread_annotations.h"
|
||||
#include "flutter/fml/synchronization/waitable_event.h"
|
||||
#include "flutter/fml/thread.h"
|
||||
@ -243,6 +244,15 @@ class Shell final : public PlatformView::Delegate,
|
||||
Rasterizer::Screenshot Screenshot(Rasterizer::ScreenshotType type,
|
||||
bool base64_encode);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
/// @brief Pauses the calling thread until the first frame is presented.
|
||||
///
|
||||
/// @return 'kOk' when the first frame has been presented before the timeout
|
||||
/// successfully, 'kFailedPrecondition' if called from the GPU or UI
|
||||
/// thread, 'kDeadlineExceeded' if there is a timeout.
|
||||
///
|
||||
fml::Status WaitForFirstFrame(fml::TimeDelta timeout);
|
||||
|
||||
private:
|
||||
using ServiceProtocolHandler =
|
||||
std::function<bool(const ServiceProtocol::Handler::ServiceProtocolMap&,
|
||||
@ -271,6 +281,9 @@ class Shell final : public PlatformView::Delegate,
|
||||
uint64_t next_pointer_flow_id_ = 0;
|
||||
|
||||
bool first_frame_rasterized_ = false;
|
||||
std::atomic<bool> waiting_for_first_frame_ = true;
|
||||
std::mutex waiting_for_first_frame_mutex_;
|
||||
std::condition_variable waiting_for_first_frame_condition_;
|
||||
|
||||
// Written in the UI thread and read from the GPU thread. Hence make it
|
||||
// atomic.
|
||||
|
||||
@ -518,5 +518,87 @@ TEST_F(ShellTest, ReportTimingsIsCalledImmediatelyAfterTheFirstFrame) {
|
||||
ASSERT_EQ(timestamps.size(), FrameTiming::kCount);
|
||||
}
|
||||
|
||||
TEST_F(ShellTest, WaitForFirstFrame) {
|
||||
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());
|
||||
fml::Status result =
|
||||
shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000));
|
||||
ASSERT_TRUE(result.ok());
|
||||
}
|
||||
|
||||
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));
|
||||
fml::Status result =
|
||||
shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(10));
|
||||
ASSERT_EQ(result.code(), fml::StatusCode::kDeadlineExceeded);
|
||||
}
|
||||
|
||||
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());
|
||||
fml::Status result =
|
||||
shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000));
|
||||
ASSERT_TRUE(result.ok());
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
result = shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1));
|
||||
ASSERT_TRUE(result.ok());
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes sure that WaitForFirstFrame works if we rendered a frame with the
|
||||
/// single-thread setup.
|
||||
TEST_F(ShellTest, WaitForFirstFrameInlined) {
|
||||
Settings settings = CreateSettingsForFixture();
|
||||
auto task_runner = GetThreadTaskRunner();
|
||||
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
|
||||
task_runner);
|
||||
std::unique_ptr<Shell> shell =
|
||||
CreateShell(std::move(settings), std::move(task_runners));
|
||||
|
||||
// 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());
|
||||
fml::AutoResetWaitableEvent event;
|
||||
task_runner->PostTask([&shell, &event] {
|
||||
fml::Status result =
|
||||
shell->WaitForFirstFrame(fml::TimeDelta::FromMilliseconds(1000));
|
||||
ASSERT_EQ(result.code(), fml::StatusCode::kFailedPrecondition);
|
||||
event.Signal();
|
||||
});
|
||||
ASSERT_FALSE(event.WaitWithTimeout(fml::TimeDelta::FromMilliseconds(1000)));
|
||||
}
|
||||
|
||||
} // namespace testing
|
||||
} // namespace flutter
|
||||
|
||||
@ -406,8 +406,9 @@ NSNotificationName const FlutterSemanticsUpdateNotification = @"FlutterSemantics
|
||||
|
||||
// Only recreate surface on subsequent appearances when viewport metrics are known.
|
||||
// First time surface creation is done on viewDidLayoutSubviews.
|
||||
if (_viewportMetrics.physical_width)
|
||||
if (_viewportMetrics.physical_width) {
|
||||
[self surfaceUpdated:YES];
|
||||
}
|
||||
[[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"];
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
@ -698,8 +699,22 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
|
||||
// This must run after updateViewportMetrics so that the surface creation tasks are queued after
|
||||
// the viewport metrics update tasks.
|
||||
if (firstViewBoundsUpdate)
|
||||
if (firstViewBoundsUpdate) {
|
||||
[self surfaceUpdated:YES];
|
||||
|
||||
flutter::Shell& shell = [_engine.get() shell];
|
||||
fml::TimeDelta waitTime =
|
||||
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
|
||||
fml::TimeDelta::FromMilliseconds(200);
|
||||
#else
|
||||
fml::TimeDelta::FromMilliseconds(100);
|
||||
#endif
|
||||
if (shell.WaitForFirstFrame(waitTime).code() == fml::StatusCode::kDeadlineExceeded) {
|
||||
FML_LOG(INFO) << "Timeout waiting for the first frame to render. This may happen in "
|
||||
<< "unoptimized builds. If this is a release build, you should load a less "
|
||||
<< "complex frame to avoid the timeout.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewSafeAreaInsetsDidChange {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user