blob: 4f8d0baf2eff35ac4c2e1aa3ea3337b1344fd6f9 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "src/ui/scenic/lib/scheduling/frame_timings.h"
#include <lib/async/default.h>
#include <lib/async/time.h>
namespace scheduling {
FrameTimings::FrameTimings(uint64_t frame_number, zx::time target_presentation_time,
zx::time latch_time, zx::time rendering_started_time,
OnTimingsRenderedCallback timings_rendered_callback,
OnTimingsPresentedCallback timings_presented_callback)
: frame_number_(frame_number),
target_presentation_time_(target_presentation_time),
latch_point_time_(latch_time),
rendering_started_time_(rendering_started_time),
timings_rendered_callback_(std::move(timings_rendered_callback)),
timings_presented_callback_(std::move(timings_presented_callback)),
weak_factory_(this) {}
void FrameTimings::RegisterSwapchains(size_t count) {
// All swapchains that we are timing must be added before any of them finish.
// The purpose of this is to verify that we cannot notify the FrameScheduler
// that the frame has finished before all swapchains have been added.
FX_DCHECK(frame_rendered_count_ == 0);
FX_DCHECK(frame_presented_count_ == 0);
FX_DCHECK(actual_presentation_time_ == kTimeUninitialized);
swapchain_records_.resize(count);
}
void FrameTimings::OnFrameUpdated(zx::time time) {
FX_DCHECK(!finalized()) << "Frame was finalized, cannot record update time";
FX_DCHECK(updates_finished_time_ == kTimeUninitialized) << "Error, update time already recorded.";
updates_finished_time_ = time;
FX_DCHECK(updates_finished_time_ >= latch_point_time_)
<< "Error, updates took negative time: latch_point_time_ = " << latch_point_time_.get()
<< ", updates_finished_time_ = " << updates_finished_time_.get();
}
void FrameTimings::OnFrameRendered(size_t swapchain_index, zx::time time) {
FX_DCHECK(swapchain_index < swapchain_records_.size());
FX_DCHECK(time.get() > 0);
auto& record = swapchain_records_[swapchain_index];
FX_DCHECK(record.frame_rendered_time == kTimeUninitialized)
<< "Frame render time already recorded for swapchain. Render time: "
<< record.frame_rendered_time.get();
record.frame_rendered_time = time;
++frame_rendered_count_;
if (!received_all_frame_rendered_callbacks()) {
return;
}
// TODO(SCN-1324): We currently only return the time of the longest received
// render time. This is not a problem right now, since we only have cases with
// a single swapchain/display, but need to figure out how to handle the
// general case.
//
// That was the last pending render, compute stats.
for (const auto& rec : swapchain_records_) {
if (rec.frame_rendered_time > rendering_finished_time_) {
rendering_finished_time_ = rec.frame_rendered_time;
}
}
FX_DCHECK(rendering_finished_time_ >= rendering_started_time_)
<< "Error, rendering took negative time";
// Note: Because there is a delay between when rendering is actually completed
// and when EventTimestamper generates the timestamp, it's possible that the
// rendering timestamp is adjusted when the present timestamp is applied. So,
// the render_done_time might change between the call to the
// |FrameScheduler::OnFrameRendered| and |finalized()|.
if (timings_rendered_callback_) {
timings_rendered_callback_(*this);
}
if (received_all_callbacks()) {
Finalize();
}
}
void FrameTimings::OnFramePresented(size_t swapchain_index, zx::time time) {
FX_DCHECK(swapchain_index < swapchain_records_.size());
FX_DCHECK(frame_presented_count_ < swapchain_records_.size());
FX_DCHECK(time.get() > 0);
auto& record = swapchain_records_[swapchain_index];
FX_DCHECK(record.frame_presented_time == kTimeUninitialized)
<< "Frame present time already recorded for swapchain. Present time: "
<< record.frame_presented_time.get();
record.frame_presented_time = time;
++frame_presented_count_;
if (!received_all_frame_presented_callbacks()) {
return;
}
// TODO(SCN-1324): We currently only return the time of the longest received
// render time. This is not a problem right now, since we only have cases with
// a single swapchain/display, but need to figure out how to handle the
// general case.
for (const auto& rec : swapchain_records_) {
if (rec.frame_presented_time > actual_presentation_time_) {
actual_presentation_time_ = rec.frame_presented_time;
}
}
if (received_all_callbacks()) {
Finalize();
}
}
void FrameTimings::OnFrameDropped(size_t swapchain_index) {
// Indicates that "frame was dropped".
actual_presentation_time_ = kTimeDropped;
frame_was_dropped_ = true;
// The record should also reflect that "frame was dropped". Additionally,
// update counts to simulate calls to OnFrameRendered/OnFramePresented; this
// maintains count-related invariants.
auto& record = swapchain_records_[swapchain_index];
record.frame_presented_time = kTimeDropped;
actual_presentation_time_ = kTimeDropped;
++frame_presented_count_;
// Do scheduler-related cleanup.
if (received_all_callbacks()) {
Finalize();
}
}
void FrameTimings::OnFrameSkipped() {
FX_CHECK(swapchain_records_.empty());
// Indicates that frame was skipped.
rendering_finished_time_ = zx::time(async_now(async_get_default_dispatcher()));
actual_presentation_time_ = zx::time(async_now(async_get_default_dispatcher()));
frame_was_skipped_ = true;
// Do scheduler-related cleanup.
if (received_all_callbacks()) {
Finalize();
}
}
void FrameTimings::OnFrameCpuRendered(zx::time time) {
rendering_cpu_finished_time_ = std::max(rendering_cpu_finished_time_, time);
}
FrameTimings::Timestamps FrameTimings::GetTimestamps() const {
// Copy the current time values to a Timestamps struct. Some callers may call
// this before all times are finalized - it is the caller's responsibility to
// check if this is |finalized()| if it wants timestamps that are guaranteed
// not to change. Additionally, some callers will maintain this struct beyond
// the lifetime of the FrameTimings object (ie for collecting FrameStats), and
// so the values are copied to allow the FrameTiming object to be destroyed.
FrameTimings::Timestamps timestamps = {
.latch_point_time = latch_point_time_,
.update_done_time = updates_finished_time_,
.render_start_time = rendering_started_time_,
.render_done_time = std::max(rendering_finished_time_, rendering_cpu_finished_time_),
.target_presentation_time = target_presentation_time_,
.actual_presentation_time = actual_presentation_time_,
};
return timestamps;
}
void FrameTimings::ValidateRenderTime() {
FX_DCHECK(rendering_finished_time_ != kTimeUninitialized);
FX_DCHECK(actual_presentation_time_ != kTimeUninitialized);
// NOTE: Because there is a delay between when rendering is actually
// completed and when EventTimestamper generates the timestamp, it's
// possible that the rendering timestamp is later than the present
// timestamp. Since we know that's actually impossible, adjust the render
// timestamp to make it a bit more accurate.
if (rendering_finished_time_ > actual_presentation_time_) {
// Reset redering_finished_time_ and adjust rendering times so that they are
// all less than or equal to the corresponding present time.
rendering_finished_time_ = kTimeUninitialized;
for (auto& record : swapchain_records_) {
FX_DCHECK(record.frame_rendered_time != kTimeUninitialized);
FX_DCHECK(record.frame_presented_time != kTimeUninitialized);
if (record.frame_rendered_time > record.frame_presented_time) {
record.frame_rendered_time = record.frame_presented_time;
}
if (record.frame_rendered_time > rendering_finished_time_) {
rendering_finished_time_ = record.frame_rendered_time;
}
}
}
}
void FrameTimings::Finalize() {
FX_DCHECK(!finalized());
finalized_ = true;
ValidateRenderTime();
if (timings_presented_callback_) {
timings_presented_callback_(*this);
}
}
} // namespace scheduling