blob: 7f84ff1bc57bc14c3967ada2b193042fd6b794c0 [file] [log] [blame]
// 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.
#include "gfx_session_connection.h"
#include <lib/async/cpp/time.h>
#include <lib/async/default.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/fit/function.h>
#include "flutter/fml/make_copyable.h"
#include "flutter/fml/time/time_point.h"
#include "flutter/fml/trace_event.h"
#include "vsync_waiter.h"
namespace flutter_runner {
namespace {
fml::TimePoint Now() {
return fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds(
async::Now(async_get_default_dispatcher()).get()));
}
} // namespace
// This function takes in all relevant information to determining when should
// the next frame be scheduled. It returns a pair of (frame_start_time,
// frame_end_time) to be passed into FireCallback().
//
// Importantly, there are two invariants for correct and performant scheduling
// that this function upholds:
// 1. Schedule the next frame at least half a vsync interval from the previous
// one. In practice, this means that every vsync interval Flutter produces
// exactly one frame in steady state behavior.
// 2. Only produce frames beginning in the future.
//
// |vsync_offset| - the time from the next vsync that the animator should begin
// working on the next frame. For instance if vsyncs are at 0ms, 16ms, and 33ms,
// and the |vsync_offset| is 5ms, then frames should begin at 11ms and 28ms.
//
// |vsync_interval| - the interval between vsyncs. Would be 16.6ms for a 60Hz
// display.
//
// |last_targeted_vsync| - the last vsync targeted, which is usually the
// frame_end_time returned from the last invocation of this function
//
// |now| - the current time
//
// |next_vsync| - the next vsync after |now|. This can be generated using the
// SnapToNextPhase function.
FlutterFrameTimes GfxSessionConnection::GetTargetTimes(
fml::TimeDelta vsync_offset,
fml::TimeDelta vsync_interval,
fml::TimePoint last_targeted_vsync,
fml::TimePoint now,
fml::TimePoint next_vsync) {
FML_DCHECK(vsync_offset <= vsync_interval);
FML_DCHECK(vsync_interval > fml::TimeDelta::FromMilliseconds(0));
FML_DCHECK(now < next_vsync && next_vsync < now + vsync_interval);
// This makes the math much easier below, since we live in a (mod
// vsync_interval) world.
if (vsync_offset == fml::TimeDelta::FromNanoseconds(0)) {
vsync_offset = vsync_interval;
}
// Start the frame after Scenic has finished its CPU work. This is
// accomplished using the vsync_offset.
fml::TimeDelta vsync_offset2 = vsync_interval - vsync_offset;
fml::TimePoint frame_start_time =
(next_vsync - vsync_interval) + vsync_offset2;
fml::TimePoint frame_end_time = next_vsync;
// Advance to next available slot, keeping in mind the two invariants.
while (frame_end_time < (last_targeted_vsync + (vsync_interval / 2)) ||
frame_start_time < now) {
frame_start_time = frame_start_time + vsync_interval;
frame_end_time = frame_end_time + vsync_interval;
}
// Useful knowledge for analyzing traces.
fml::TimePoint previous_vsync = next_vsync - vsync_interval;
TRACE_DURATION(
"flutter", "GfxSessionConnection::GetTargetTimes", "previous_vsync(ms)",
previous_vsync.ToEpochDelta().ToMilliseconds(), "last_targeted(ms)",
last_targeted_vsync.ToEpochDelta().ToMilliseconds(), "now(ms)",
now.ToEpochDelta().ToMilliseconds(), "next_vsync(ms))",
next_vsync.ToEpochDelta().ToMilliseconds(), "frame_start_time(ms)",
frame_start_time.ToEpochDelta().ToMilliseconds(), "frame_end_time(ms)",
frame_end_time.ToEpochDelta().ToMilliseconds());
return {frame_start_time, frame_end_time};
}
fml::TimePoint GfxSessionConnection::CalculateNextLatchPoint(
fml::TimePoint present_requested_time,
fml::TimePoint now,
fml::TimePoint last_latch_point_targeted,
fml::TimeDelta flutter_frame_build_time,
fml::TimeDelta vsync_interval,
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>&
future_presentation_infos) {
// The minimum latch point is the largest of:
// Now
// When we expect the Flutter work for the frame to be completed
// The last latch point targeted
fml::TimePoint minimum_latch_point_to_target =
std::max(std::max(now, present_requested_time + flutter_frame_build_time),
last_latch_point_targeted);
for (auto& info : future_presentation_infos) {
fml::TimePoint latch_point = info.first;
if (latch_point >= minimum_latch_point_to_target) {
return latch_point;
}
}
// We could not find a suitable latch point in the list given to us from
// Scenic, so aim for the smallest safe value.
return minimum_latch_point_to_target;
}
/// Returns the system time at which the next frame is likely to be presented.
///
/// Consider the following scenarios, where in both the
/// scenarios the result will be the same.
///
/// Scenario 1:
/// presentation_interval is 2
/// ^ ^ ^ ^ ^
/// + + + + +
/// 0--1--2--3--4--5--6--7--8--9--
/// + + +
/// | | +---------> result: next_presentation_time
/// | v
/// v now
/// last_presentation_time
///
/// Scenario 2:
/// presentation_interval is 2
/// ^ ^ ^ ^ ^
/// + + + + +
/// 0--1--2--3--4--5--6--7--8--9--
/// + + +
/// | | +--------->result: next_presentation_time
/// | |
/// | +>now
/// |
/// +->last_presentation_time
fml::TimePoint GfxSessionConnection::SnapToNextPhase(
const fml::TimePoint now,
const fml::TimePoint last_frame_presentation_time,
const fml::TimeDelta presentation_interval) {
if (presentation_interval <= fml::TimeDelta::Zero()) {
FML_LOG(WARNING)
<< "Presentation interval must be positive. The value was: "
<< presentation_interval.ToMilliseconds() << "ms.";
return now;
}
if (last_frame_presentation_time > now) {
FML_LOG(WARNING)
<< "Last frame was presented in the future. Clamping to now.";
return now + presentation_interval;
}
const fml::TimeDelta time_since_last_presentation =
now - last_frame_presentation_time;
// this will be the most likely scenario if we are rendering at a good
// frame rate, short circuiting the other checks in this case.
if (time_since_last_presentation < presentation_interval) {
return last_frame_presentation_time + presentation_interval;
} else {
const int64_t num_phases_passed =
(time_since_last_presentation / presentation_interval);
return last_frame_presentation_time +
(presentation_interval * (num_phases_passed + 1));
}
}
GfxSessionConnection::GfxSessionConnection(
std::string debug_label,
inspect::Node inspect_node,
fidl::InterfaceHandle<fuchsia::ui::scenic::Session> session,
fml::closure session_error_callback,
on_frame_presented_event on_frame_presented_callback,
uint64_t max_frames_in_flight,
fml::TimeDelta vsync_offset)
: session_wrapper_(session.Bind(), nullptr),
inspect_node_(std::move(inspect_node)),
secondary_vsyncs_completed_(
inspect_node_.CreateUint("SecondaryVsyncsCompleted", 0u)),
vsyncs_requested_(inspect_node_.CreateUint("VsyncsRequested", 0u)),
vsyncs_completed_(inspect_node_.CreateUint("VsyncsCompleted", 0u)),
presents_requested_(inspect_node_.CreateUint("PresentsRequested", 0u)),
presents_submitted_(inspect_node_.CreateUint("PresentsSubmitted", 0u)),
presents_completed_(inspect_node_.CreateUint("PresentsCompleted", 0u)),
last_secondary_vsync_completed_(
inspect_node_.CreateInt("LastSecondaryVsyncCompleteTime", 0)),
last_vsync_requested_(inspect_node_.CreateInt("LastVsyncRequestTime", 0)),
last_vsync_completed_(
inspect_node_.CreateInt("LastVsyncCompleteTime", 0)),
last_frame_requested_(
inspect_node_.CreateInt("LastPresentRequestTime", 0)),
last_frame_presented_(
inspect_node_.CreateInt("LastPresentSubmitTime", 0)),
last_frame_completed_(
inspect_node_.CreateInt("LastSubmitCompleteTime", 0)),
inspect_dispatcher_(async_get_default_dispatcher()),
on_frame_presented_callback_(std::move(on_frame_presented_callback)),
kMaxFramesInFlight(max_frames_in_flight),
vsync_offset_(vsync_offset),
weak_factory_(this) {
FML_CHECK(kMaxFramesInFlight > 0);
last_presentation_time_ = Now();
next_presentation_info_.set_presentation_time(0);
session_wrapper_.set_error_handler(
[callback = session_error_callback](zx_status_t status) { callback(); });
// Set the |fuchsia::ui::scenic::OnFramePresented()| event handler that will
// fire every time a set of one or more frames is presented.
session_wrapper_.set_on_frame_presented_handler(
[weak = weak_factory_.GetWeakPtr()](
fuchsia::scenic::scheduling::FramePresentedInfo info) {
if (!weak) {
return;
}
std::lock_guard<std::mutex> lock(weak->mutex_);
// Update Scenic's limit for our remaining frames in flight allowed.
size_t num_presents_handled = info.presentation_infos.size();
// A frame was presented: Update our |frames_in_flight| to match the
// updated unfinalized present requests.
weak->frames_in_flight_ -= num_presents_handled;
TRACE_DURATION("gfx", "OnFramePresented5", "frames_in_flight",
weak->frames_in_flight_, "max_frames_in_flight",
weak->kMaxFramesInFlight, "num_presents_handled",
num_presents_handled);
FML_DCHECK(weak->frames_in_flight_ >= 0);
weak->last_presentation_time_ = fml::TimePoint::FromEpochDelta(
fml::TimeDelta::FromNanoseconds(info.actual_presentation_time));
// Scenic retired a given number of frames, so mark them as completed.
// Inspect updates must run on the inspect dispatcher.
//
// TODO(akbiggs): It might not be necessary to post an async task for
// the inspect updates. Read over the Inspect API's thread safety and
// adjust accordingly.
async::PostTask(weak->inspect_dispatcher_, [weak, now = Now(),
num_presents_handled]() {
if (!weak) {
return;
}
weak->presents_completed_.Add(num_presents_handled);
weak->last_frame_completed_.Set(now.ToEpochDelta().ToNanoseconds());
});
if (weak->fire_callback_request_pending_) {
weak->FireCallbackMaybe();
}
if (weak->present_session_pending_) {
weak->PresentSession();
}
// Call the client-provided callback once we are done using |info|.
weak->on_frame_presented_callback_(std::move(info));
});
session_wrapper_.SetDebugName(debug_label);
// Get information to finish initialization and only then allow Present()s.
session_wrapper_.RequestPresentationTimes(
/*requested_prediction_span=*/0,
[weak = weak_factory_.GetWeakPtr()](
fuchsia::scenic::scheduling::FuturePresentationTimes info) {
if (!weak) {
return;
}
std::lock_guard<std::mutex> lock(weak->mutex_);
weak->next_presentation_info_ = UpdatePresentationInfo(
std::move(info), weak->next_presentation_info_);
weak->initialized_ = true;
weak->PresentSession();
});
FML_LOG(INFO) << "Flutter GfxSessionConnection: Set vsync_offset to "
<< vsync_offset_.ToMicroseconds() << "us";
}
GfxSessionConnection::~GfxSessionConnection() = default;
void GfxSessionConnection::Present() {
std::lock_guard<std::mutex> lock(mutex_);
TRACE_DURATION("gfx", "GfxSessionConnection::Present", "frames_in_flight",
frames_in_flight_, "max_frames_in_flight", kMaxFramesInFlight);
TRACE_FLOW_BEGIN("gfx", "GfxSessionConnection::PresentSession",
next_present_session_trace_id_);
++next_present_session_trace_id_;
auto now = Now();
present_requested_time_ = now;
// Flutter is requesting a frame here, so mark it as such.
// Inspect updates must run on the inspect dispatcher.
async::PostTask(inspect_dispatcher_, [this, now]() {
presents_requested_.Add(1);
last_frame_requested_.Set(now.ToEpochDelta().ToNanoseconds());
});
// Throttle frame submission to Scenic if we already have the maximum amount
// of frames in flight. This allows the paint tasks for this frame to execute
// in parallel with the presentation of previous frame but still provides
// back-pressure to prevent us from enqueuing even more work.
if (initialized_ && frames_in_flight_ < kMaxFramesInFlight) {
PresentSession();
} else {
// We should never exceed the max frames in flight.
FML_CHECK(frames_in_flight_ <= kMaxFramesInFlight);
present_session_pending_ = true;
}
}
void GfxSessionConnection::AwaitVsync(FireCallbackCallback callback) {
std::lock_guard<std::mutex> lock(mutex_);
TRACE_DURATION("flutter", "GfxSessionConnection::AwaitVsync");
fire_callback_ = callback;
// Flutter is requesting a vsync here, so mark it as such.
// Inspect updates must run on the inspect dispatcher.
async::PostTask(inspect_dispatcher_, [this, now = Now()]() {
vsyncs_requested_.Add(1);
last_vsync_requested_.Set(now.ToEpochDelta().ToNanoseconds());
});
FireCallbackMaybe();
}
void GfxSessionConnection::AwaitVsyncForSecondaryCallback(
FireCallbackCallback callback) {
std::lock_guard<std::mutex> lock(mutex_);
TRACE_DURATION("flutter",
"GfxSessionConnection::AwaitVsyncForSecondaryCallback");
fire_callback_ = callback;
// Flutter is requesting a secondary vsync here, so mark it as such.
// Inspect updates must run on the inspect dispatcher.
async::PostTask(inspect_dispatcher_, [this, now = Now()]() {
secondary_vsyncs_completed_.Add(1);
last_secondary_vsync_completed_.Set(now.ToEpochDelta().ToNanoseconds());
});
FlutterFrameTimes times = GetTargetTimesHelper(/*secondary_callback=*/true);
fire_callback_(times.frame_start, times.frame_target);
}
// Precondition: |mutex_| is held
void GfxSessionConnection::PresentSession() {
TRACE_DURATION("gfx", "GfxSessionConnection::PresentSession");
present_session_pending_ = false;
while (processed_present_session_trace_id_ < next_present_session_trace_id_) {
TRACE_FLOW_END("gfx", "GfxSessionConnection::PresentSession",
processed_present_session_trace_id_);
++processed_present_session_trace_id_;
}
TRACE_FLOW_BEGIN("gfx", "Session::Present", next_present_trace_id_);
++next_present_trace_id_;
++frames_in_flight_;
fml::TimeDelta presentation_interval =
GetCurrentVsyncInfo().presentation_interval;
fml::TimePoint next_latch_point = CalculateNextLatchPoint(
Now(), present_requested_time_, last_latch_point_targeted_,
fml::TimeDelta::FromMicroseconds(0), // flutter_frame_build_time
presentation_interval, future_presentation_infos_);
last_latch_point_targeted_ = next_latch_point;
// Flutter is presenting a frame here, so mark it as such.
// Inspect updates must run on the inspect dispatcher.
async::PostTask(inspect_dispatcher_, [this, now = Now()]() {
presents_submitted_.Add(1);
last_frame_presented_.Set(now.ToEpochDelta().ToNanoseconds());
});
session_wrapper_.Present2(
/*requested_presentation_time=*/next_latch_point.ToEpochDelta()
.ToNanoseconds(),
/*requested_prediction_span=*/presentation_interval.ToNanoseconds() * 10,
[weak = weak_factory_.GetWeakPtr()](
fuchsia::scenic::scheduling::FuturePresentationTimes info) {
if (!weak) {
return;
}
std::lock_guard<std::mutex> lock(weak->mutex_);
// Clear |future_presentation_infos_| and replace it with the updated
// information.
std::deque<std::pair<fml::TimePoint, fml::TimePoint>>().swap(
weak->future_presentation_infos_);
for (fuchsia::scenic::scheduling::PresentationInfo& presentation_info :
info.future_presentations) {
weak->future_presentation_infos_.push_back(
{fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds(
presentation_info.latch_point())),
fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds(
presentation_info.presentation_time()))});
}
weak->next_presentation_info_ = UpdatePresentationInfo(
std::move(info), weak->next_presentation_info_);
});
}
// Precondition: |mutex_| is held.
//
// Postcondition: Either a frame is scheduled or fire_callback_request_pending_
// is set to true, meaning we will attempt to schedule a frame on the next
// |OnFramePresented|.
void GfxSessionConnection::FireCallbackMaybe() {
TRACE_DURATION("flutter", "FireCallbackMaybe");
if (frames_in_flight_ < kMaxFramesInFlight) {
FlutterFrameTimes times =
GetTargetTimesHelper(/*secondary_callback=*/false);
last_targeted_vsync_ = times.frame_target;
fire_callback_request_pending_ = false;
// Scenic completed a vsync here, so mark it as such.
// Inspect updates must run on the inspect dispatcher.
async::PostTask(inspect_dispatcher_, [this, now = Now()]() {
vsyncs_completed_.Add(1);
last_vsync_completed_.Set(now.ToEpochDelta().ToNanoseconds());
});
fire_callback_(times.frame_start, times.frame_target);
} else {
fire_callback_request_pending_ = true;
}
}
// Precondition: |mutex_| is held
//
// A helper function for GetTargetTimes(), since many of the fields it takes
// have to be derived from other state.
FlutterFrameTimes GfxSessionConnection::GetTargetTimesHelper(
bool secondary_callback) {
fml::TimeDelta presentation_interval =
GetCurrentVsyncInfo().presentation_interval;
fml::TimePoint next_vsync = GetCurrentVsyncInfo().presentation_time;
fml::TimePoint now = Now();
fml::TimePoint last_presentation_time = last_presentation_time_;
if (next_vsync <= now) {
next_vsync =
SnapToNextPhase(now, last_presentation_time, presentation_interval);
}
fml::TimePoint last_targeted_vsync =
secondary_callback ? fml::TimePoint::Min() : last_targeted_vsync_;
return GetTargetTimes(vsync_offset_, presentation_interval,
last_targeted_vsync, now, next_vsync);
}
fuchsia::scenic::scheduling::PresentationInfo
GfxSessionConnection::UpdatePresentationInfo(
fuchsia::scenic::scheduling::FuturePresentationTimes future_info,
fuchsia::scenic::scheduling::PresentationInfo& presentation_info) {
fuchsia::scenic::scheduling::PresentationInfo new_presentation_info;
new_presentation_info.set_presentation_time(
presentation_info.presentation_time());
auto next_time = presentation_info.presentation_time();
// Get the earliest vsync time that is after our recorded |presentation_time|.
for (auto& presentation_info : future_info.future_presentations) {
auto current_time = presentation_info.presentation_time();
if (current_time > next_time) {
new_presentation_info.set_presentation_time(current_time);
return new_presentation_info;
}
}
return new_presentation_info;
}
// Precondition: |mutex_| is held
VsyncInfo GfxSessionConnection::GetCurrentVsyncInfo() const {
return {fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromNanoseconds(
next_presentation_info_.presentation_time())),
kDefaultPresentationInterval};
}
} // namespace flutter_runner