Commit e7062214 authored by Brian Anderson's avatar Brian Anderson Committed by Commit Bot

ui: Add SkippedFrameTracker for FrameMetrics

Sources that run on BeginFrames can delegate their skipped
frame logic to SkippedFrameTracker.

SkippedFrameTracker handles the following corner cases:
1) when non-consecutive BeginFrames are received.
2) when the producer is not trying to produce.

Bug: 790759
Change-Id: I99d4067d4eab79f220b892256d8d3d49d1af1209
Reviewed-on: https://chromium-review.googlesource.com/1070994
Commit-Queue: Brian Anderson <brianderson@chromium.org>
Reviewed-by: default avatarSadrul Chowdhury <sadrul@chromium.org>
Reviewed-by: default avatarTimothy Dresser <tdresser@chromium.org>
Cr-Commit-Position: refs/heads/master@{#565459}
parent 3f7cf703
...@@ -18,6 +18,8 @@ jumbo_source_set("latency") { ...@@ -18,6 +18,8 @@ jumbo_source_set("latency") {
"latency_info.h", "latency_info.h",
"latency_tracker.cc", "latency_tracker.cc",
"latency_tracker.h", "latency_tracker.h",
"skipped_frame_tracker.cc",
"skipped_frame_tracker.h",
"stream_analyzer.cc", "stream_analyzer.cc",
"stream_analyzer.h", "stream_analyzer.h",
"windowed_analyzer.cc", "windowed_analyzer.cc",
...@@ -53,6 +55,7 @@ test("latency_unittests") { ...@@ -53,6 +55,7 @@ test("latency_unittests") {
"frame_metrics_unittest.cc", "frame_metrics_unittest.cc",
"histograms_unittest.cc", "histograms_unittest.cc",
"latency_info_unittest.cc", "latency_info_unittest.cc",
"skipped_frame_tracker_unittest.cc",
"stream_analyzer_unittest.cc", "stream_analyzer_unittest.cc",
"windowed_analyzer_unittest.cc", "windowed_analyzer_unittest.cc",
] ]
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/trace_event/trace_event_argument.h" #include "base/trace_event/trace_event_argument.h"
#include "ui/latency/skipped_frame_tracker.h"
namespace ui { namespace ui {
namespace frame_metrics { namespace frame_metrics {
...@@ -92,19 +93,21 @@ struct FrameMetricsSettings { ...@@ -92,19 +93,21 @@ struct FrameMetricsSettings {
// Statistics will be reported automatically. Either periodically, based // Statistics will be reported automatically. Either periodically, based
// on the client interface, or on destruction if any samples were added since // on the client interface, or on destruction if any samples were added since
// the last call to StartNewReportPeriod. // the last call to StartNewReportPeriod.
class FrameMetrics { class FrameMetrics : public SkippedFrameTracker::Client {
public: public:
explicit FrameMetrics(FrameMetricsSettings settings); explicit FrameMetrics(FrameMetricsSettings settings);
virtual ~FrameMetrics(); ~FrameMetrics() override;
// Resets all data and history as if the class were just created. // Resets all data and history as if the class were just created.
void Reset(); void Reset();
// AddFrameProduced should be called every time a source produces a frame. // AddFrameProduced should be called every time a source produces a frame.
// The information added here affects the number of frames skipped. // The information added here affects the number of frames skipped.
// Note: If the FrameMetrics class is hooked up to an optional
// SkippedFrameTracker, the client should not call this directly.
void AddFrameProduced(base::TimeTicks source_timestamp, void AddFrameProduced(base::TimeTicks source_timestamp,
base::TimeDelta amount_produced, base::TimeDelta amount_produced,
base::TimeDelta amount_skipped); base::TimeDelta amount_skipped) override;
// AddFrameDisplayed should be called whenever a frame causes damage and // AddFrameDisplayed should be called whenever a frame causes damage and
// we know when the result became visible on the display. // we know when the result became visible on the display.
......
// Copyright 2018 The Chromium 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 "ui/latency/skipped_frame_tracker.h"
#include <cmath>
#include "ui/latency/frame_metrics.h"
namespace ui {
SkippedFrameTracker::SkippedFrameTracker(Client* client) : client_(client) {}
void SkippedFrameTracker::BeginFrame(base::TimeTicks frame_time,
base::TimeDelta interval) {
DCHECK(!inside_begin_frame_);
inside_begin_frame_ = true;
did_produce_this_frame_ = false;
frame_time_ = frame_time;
interval_ = interval;
// On our first frame of activity, we may need to initialize
// will_produce_frame_time_.
if (active_state_ == ActiveState::WillProduceFirst &&
will_produce_frame_time_.is_null()) {
will_produce_frame_time_ = frame_time_;
}
}
void SkippedFrameTracker::FinishFrame() {
DCHECK(inside_begin_frame_);
inside_begin_frame_ = false;
// Assume the source is idle if it hasn't attempted to produce for an entire
// BeginFrame.
if (!did_produce_this_frame_ && active_state_ == ActiveState::WasActive) {
will_produce_frame_time_ = base::TimeTicks();
active_state_ = ActiveState::Idle;
}
}
void SkippedFrameTracker::WillProduceFrame() {
// Make sure we don't transition out of the WillProduceFirst state until
// we've actually produced the first frame.
if (active_state_ == ActiveState::WillProduceFirst)
return;
// This is our first frame of activity.
if (active_state_ == ActiveState::Idle) {
active_state_ = ActiveState::WillProduceFirst;
// If we're already inside a BeginFrame when we first become active,
// we can initialize will_produce_frame_time_.
if (inside_begin_frame_)
will_produce_frame_time_ = frame_time_;
return;
}
active_state_ = ActiveState::WillProduce;
}
void SkippedFrameTracker::DidProduceFrame() {
// Ignore duplicate calls to DidProduceFrame.
if (did_produce_this_frame_)
return;
// Return early if frame was pulled by sink.
bool frame_was_pushed_by_source =
(active_state_ == ActiveState::WillProduceFirst &&
!will_produce_frame_time_.is_null()) ||
active_state_ == ActiveState::WillProduce;
if (!frame_was_pushed_by_source)
return;
DCHECK(!will_produce_frame_time_.is_null());
// Clamp the amount of time skipped to a positive value, since negative
// values aren't meaningful.
base::TimeDelta skipped_clamped =
std::max(base::TimeDelta(), (frame_time_ - will_produce_frame_time_));
// Snap the amount of time skipped to whole intervals in order to filter
// out jitter in the timing received by the BeginFrame source.
int skipped_intervals = (skipped_clamped + (interval_ / 2)) / interval_;
base::TimeDelta skipped_snapped = skipped_intervals * interval_;
DCHECK_GE(skipped_snapped, base::TimeDelta());
client_->AddFrameProduced(frame_time_, interval_, skipped_snapped);
// Predict the next BeginFrame's frame time, so we can detect if it gets
// dropped.
will_produce_frame_time_ = frame_time_ + interval_;
active_state_ = ActiveState::WasActive;
did_produce_this_frame_ = true;
}
} // namespace ui
// Copyright 2018 The Chromium 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 UI_LATENCY_SKIPPED_FRAME_TRACKER_H_
#define UI_LATENCY_SKIPPED_FRAME_TRACKER_H_
#include "base/macros.h"
#include "base/time/time.h"
namespace ui {
// SkippedFrameTracker tracks skipped BeginFrames. It can be used by sources
// attempting to produce at the display rate. It properly handles
// non-consecutive BeginFrames and tracks when the source is actualy trying to
// produce, rather than passively receiving BeginFrames.
class SkippedFrameTracker {
public:
// SkippedFrameTracker calls Client::AddFrameProduced from FinishFrame
// when necessary and with the correct values.
class Client {
public:
virtual ~Client() = default;
virtual void AddFrameProduced(base::TimeTicks source_timestamp,
base::TimeDelta amount_produced,
base::TimeDelta amount_skipped) = 0;
};
// SkippedFrameTracker will call |client|->AddFrameProduced
// with the appropriate info automatically as frames are produced.
explicit SkippedFrameTracker(Client* client);
// BeginFrame and FinishFrame must be called for each BeginFrame received.
// In order for this class to detect idle periods properly, the source must
// call Begin+FinishFrame without calling WillProduceFrame before going
// idle. This is necessary since there is otherwise no way to tell if a
// non-consecutive BeginFrame occured a) because we were slow or b) because
// we weren't trying to produce a frame.
void BeginFrame(base::TimeTicks frame_time, base::TimeDelta interval);
void FinishFrame();
// WillProduceFrame should be called when the source knows it wants to
// produce a frame. DidProduceFrame should be called when the source has
// actually submitted the frame.
// It is okay for DidProduceFrame to be called without WillProduceFrame,
// which can happen in cases where a frame is "pulled" from later in the
// pipeline rather than pushed from the source. Such calls to DidProduceFrame
// will be ignored.
void WillProduceFrame();
void DidProduceFrame();
protected:
Client* client_;
bool inside_begin_frame_ = false;
base::TimeTicks frame_time_;
base::TimeDelta interval_;
bool did_produce_this_frame_ = false;
base::TimeTicks will_produce_frame_time_;
enum class ActiveState {
// Idle: The initial and idle state.
// Goto WillProduceFirst on 1st call to WillProduceFrame.
Idle,
// WillProduceFirst: Producing the first frame out of idle.
// Goto WasActive on first FinishFrame after a DidProduceFrame.
// Counts missing BeginFrames as skipped: NO.
WillProduceFirst,
// WillProduce: Producing the (N > 1)'th frame of constant activity.
// Goto WasActive on first FinishFrame after a DidProduceFrame.
// Counts missing BeginFrames as skipped: YES.
WillProduce,
// WasActive: An intermediate state to determine if we are idle or not.
// Goto WillProduce on WillProduceFrame.
// Otherwise, goto Idle on next FinishFrame.
WasActive,
};
ActiveState active_state_ = ActiveState::Idle;
DISALLOW_COPY_AND_ASSIGN(SkippedFrameTracker);
};
} // namespace ui
#endif // UI_LATENCY_SKIPPED_FRAME_TRACKER_H_
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment