Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5aa9df6
Got rid of the black frame by synchronizing the main thread with the
gaaclarke Jun 18, 2019
28f23c8
Fixed return result for WaitForFrameRender and added docstring.
gaaclarke Jun 26, 2019
d93a5bc
Fixed formatting.
gaaclarke Jun 26, 2019
6b55244
Made the bool parameter atomic since it was getting written to from
gaaclarke Jun 26, 2019
d246253
Removed AwaitTask since it was controversial and not absolutely
gaaclarke Jul 1, 2019
700f062
Added asserts to avoid deadlock.
gaaclarke Jul 1, 2019
bc9cd95
Updated documenation for WaitForFrameRender.
gaaclarke Jul 1, 2019
ac7266b
Made the CL much simplier by just waiting for the first frame instead
gaaclarke Jul 1, 2019
040c146
Update docstring.
gaaclarke Jul 1, 2019
48db249
Merge branch 'master' into black-frame2
gaaclarke Jul 2, 2019
8d63518
Updated timeout message.
gaaclarke Jul 2, 2019
8a676f7
Switched preprocessor macro for determining timeout for first frame.
gaaclarke Jul 2, 2019
2915fe9
missing semicolon, doh
gaaclarke Jul 2, 2019
016c96d
Removed captures of 'this'.
gaaclarke Jul 2, 2019
d040af8
Added unit test for wait for first frame.
gaaclarke Jul 2, 2019
41406b9
tweaked unit test.
gaaclarke Jul 2, 2019
6068985
Fixed typo
gaaclarke Jul 2, 2019
1a7c9e1
Updated doxygen format to match.
gaaclarke Jul 9, 2019
82d19a7
updated doxygen format again
gaaclarke Jul 9, 2019
9df3a65
1) Added Status class and made WaitForFirstFrame use it
gaaclarke Jul 9, 2019
b9d4c14
Added timeout to unit test.
gaaclarke Jul 9, 2019
b6d35b5
Added class docstring
gaaclarke Jul 9, 2019
4e25624
grammar typo
gaaclarke Jul 9, 2019
2b88ff8
Fixed typos
gaaclarke Jul 9, 2019
f0b3a40
Added status to the license golden.
gaaclarke Jul 9, 2019
fdfa45c
Updated the docstring to update the Status returns.
gaaclarke Jul 9, 2019
aa12852
Fixed comment formatting.
gaaclarke Jul 9, 2019
ad944f3
Moved to std::string_view.
gaaclarke Jul 9, 2019
d55bf99
Removed raw_code from Status.
gaaclarke Jul 9, 2019
f7e2b8c
Removed thread_host from test.
gaaclarke Jul 10, 2019
9c3b023
Removed enumeration values for StatusCodes.
gaaclarke Jul 10, 2019
e713db8
Merge branch 'master' of github.com:flutter/engine into black-frame2
gaaclarke Jul 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions fml/task_runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "flutter/fml/logging.h"
#include "flutter/fml/message_loop.h"
#include "flutter/fml/message_loop_impl.h"
#include "flutter/fml/synchronization/count_down_latch.h"

namespace fml {

Expand All @@ -23,6 +24,15 @@ void TaskRunner::PostTask(fml::closure task) {
loop_->PostTask(std::move(task), fml::TimePoint::Now());
}

void TaskRunner::AwaitTask(fml::closure task) {
CountDownLatch latch(1);
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
PostTask([&] {
task();
latch.CountDown();
});
latch.Wait();
}

void TaskRunner::PostTaskForTime(fml::closure task,
fml::TimePoint target_time) {
loop_->PostTask(std::move(task), target_time);
Expand Down
2 changes: 2 additions & 0 deletions fml/task_runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class TaskRunner : public fml::RefCountedThreadSafe<TaskRunner> {

virtual void PostTask(fml::closure task);

virtual void AwaitTask(fml::closure task);

virtual void PostTaskForTime(fml::closure task, fml::TimePoint target_time);

virtual void PostDelayedTask(fml::closure task, fml::TimeDelta delay);
Expand Down
7 changes: 7 additions & 0 deletions shell/common/animator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,11 @@ void Animator::AwaitVSync() {
delegate_.OnAnimatorNotifyIdle(dart_frame_deadline_);
}

void Animator::ForceVSync() {
regenerate_layer_tree_ = true;
frame_scheduled_ = true;
AwaitVSync();
waiter_->ForceVSync();
}

} // namespace flutter
2 changes: 2 additions & 0 deletions shell/common/animator.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class Animator final {
// will be ended during the next |BeginFrame|.
void EnqueueTraceFlowId(uint64_t trace_flow_id);

void ForceVSync();
Comment thread
gaaclarke marked this conversation as resolved.
Outdated

private:
using LayerTreePipeline = Pipeline<flutter::LayerTree>;

Expand Down
2 changes: 2 additions & 0 deletions shell/common/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ class Engine final : public RuntimeDelegate {
// |RuntimeDelegate|
FontCollection& GetFontCollection() override;

Animator* GetAnimator() { return animator_.get(); }
Comment thread
gaaclarke marked this conversation as resolved.
Outdated

private:
Engine::Delegate& delegate_;
const Settings settings_;
Expand Down
19 changes: 19 additions & 0 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,14 @@ void Shell::OnFrameRasterized(const FrameTiming& timing) {
FML_DCHECK(is_setup_);
FML_DCHECK(task_runners_.GetGPUTaskRunner()->RunsTasksOnCurrentThread());

if (waiting_for_frame_.load()) {
{
std::lock_guard<std::mutex> lock(waiting_for_frame_mutex_);
waiting_for_frame_.store(false);
}
waiting_for_frame_condition_.notify_all();
}

// The C++ callback defined in settings.h and set by Flutter runner. This is
// independent of the timings report to the Dart side.
if (settings_.frame_rasterized_callback) {
Expand Down Expand Up @@ -1229,4 +1237,15 @@ Rasterizer::Screenshot Shell::Screenshot(
return screenshot;
}

bool Shell::WaitForFrameRender(fml::TimeDelta timeout) {
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
std::unique_lock<std::mutex> lock(waiting_for_frame_mutex_);
task_runners_.GetUITaskRunner()->AwaitTask(
[this] { engine_->GetAnimator()->ForceVSync(); });
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
waiting_for_frame_.store(true);
bool success = waiting_for_frame_condition_.wait_for(
lock, std::chrono::milliseconds(timeout.ToMilliseconds()),
[this] { return !waiting_for_frame_.load(); });
return !success;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks to the tests added, I finally realized that this returns !success instead of success.

This is a little counter-intuitive as most wait_for_something returns success. Maybe create an enum FirstFrameWaitingResult {timedout, successful}; and make that as the return type. That will make it much clearer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

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.

Copy link
Copy Markdown
Contributor Author

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.

}

} // namespace flutter
7 changes: 7 additions & 0 deletions shell/common/shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ class Shell final : public PlatformView::Delegate,
Rasterizer::Screenshot Screenshot(Rasterizer::ScreenshotType type,
bool base64_encode);

/// Schedules a frame to be rendered and waits for it to finish.
///\returns true when there has been a timeout.
bool WaitForFrameRender(fml::TimeDelta timeout);
Comment thread
gaaclarke marked this conversation as resolved.
Outdated

private:
using ServiceProtocolHandler =
std::function<bool(const ServiceProtocol::Handler::ServiceProtocolMap&,
Expand Down Expand Up @@ -118,6 +122,9 @@ class Shell final : public PlatformView::Delegate,
uint64_t next_pointer_flow_id_ = 0;

bool first_frame_rasterized_ = false;
std::atomic<bool> waiting_for_frame_ = false;
std::mutex waiting_for_frame_mutex_;
std::condition_variable waiting_for_frame_condition_;

// Written in the UI thread and read from the GPU thread. Hence make it
// atomic.
Expand Down
2 changes: 2 additions & 0 deletions shell/common/vsync_waiter.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class VsyncWaiter : public std::enable_shared_from_this<VsyncWaiter> {
// Return kUnknownRefreshRateFPS if the refresh rate is unknown.
virtual float GetDisplayRefreshRate() const;

virtual void ForceVSync() {}

protected:
// On some backends, the |FireCallback| needs to be made from a static C
// method.
Expand Down
4 changes: 4 additions & 0 deletions shell/common/vsync_waiter_fallback.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ void VsyncWaiterFallback::AwaitVSync() {
FireCallback(next, next + kSingleFrameInterval);
}

void VsyncWaiterFallback::ForceVSync() {
AwaitVSync();
}

} // namespace flutter
2 changes: 2 additions & 0 deletions shell/common/vsync_waiter_fallback.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class VsyncWaiterFallback final : public VsyncWaiter {

~VsyncWaiterFallback() override;

void ForceVSync() override;

private:
fml::TimePoint phase_;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,9 @@ - (void)viewWillAppear:(BOOL)animated {

// 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) {
Comment thread
gaaclarke marked this conversation as resolved.
[self surfaceUpdated:YES];
}
[[_engine.get() lifecycleChannel] sendMessage:@"AppLifecycleState.inactive"];

[super viewWillAppear:animated];
Expand Down Expand Up @@ -689,8 +690,21 @@ - (void)viewDidLayoutSubviews {

// 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 NDEBUG
fml::TimeDelta::FromMilliseconds(100);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we introducing a new race condition by putting these timeouts here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@tvolkert The current state of affairs is we issue the rendering of a frame asynchronously and we don't pay attention to when it is finished and plow right along on the main thread. That is what is causing the first frame to be black. We should be issuing the async render and actually paying attention to when it is done before the main thread continues on. That's what this is doing.

The reason for the timeout is not because I'm afraid of deadlock, it's because our renderer is inefficient to the point where I'm saying "If rendering the first frame takes over 100ms it's better to show a black frame than to make the main thread wait any longer"

We have outstanding work to do to make the first frame render faster. Right now we've gated all our initialization logic on knowing the dimensions of the surface we are rendering to, which is arbitrary and inefficient. In addition to this synchronization we should decouple that initialization that needs dimensions vs initialization that doesn't need dimensions so we can make the first frame even faster. Right now first frame is ~2.5x slower that the second frame. Even when we optimize that, we will still need to synchronize. We can't just fire off an event and not care about when it is finished.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm curious about the difference between:

  1. Showing a black frame at 100ms when this times out
  2. Blocking everything until the first frame is rendered (say in 200ms)

To me, they seem equivalent. In both cases, the user won't be able to see any meaningful pixels or interact with the app at 100ms.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@liyuqian Both aren't desirable. Having the timeout is safer since in iOS if you occupy the main thread for too long the operating system will kill your app. Also, in the case where there is a bug that makes the attempt to request a frame ignored we won't get deadlocked.

#else
fml::TimeDelta::FromMilliseconds(200);
#endif
if (shell.WaitForFrameRender(waitTime)) {
Comment thread
gaaclarke marked this conversation as resolved.
Outdated
FML_LOG(INFO) << "Timeout waiting for first frame. This is possible in debug builds but "
<< "should be reported as an error on release builds.";
}
}
}

- (void)viewSafeAreaInsetsDidChange {
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class VsyncWaiterIOS final : public VsyncWaiter {

~VsyncWaiterIOS() override;

void ForceVSync() override;

private:
fml::scoped_nsobject<VSyncClient> client_;

Expand Down
10 changes: 10 additions & 0 deletions shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ - (void)await;

- (void)invalidate;

- (void)forceVSync;

@end

namespace flutter {
Expand All @@ -45,6 +47,10 @@ - (void)invalidate;
[client_.get() await];
}

void VsyncWaiterIOS::ForceVSync() {
[client_.get() forceVSync];
}

} // namespace flutter

@implementation VSyncClient {
Expand Down Expand Up @@ -97,4 +103,8 @@ - (void)dealloc {
[super dealloc];
}

- (void)forceVSync {
[self onDisplayLink:display_link_];
}

@end