Mozart: Move frame lag compensation to new class.
Moved code from Choreographer to FrameTracker to allow it to be reused
more broadly and without taking a dependency on //base.
BUG=
R=abarth@google.com
Review URL: https://codereview.chromium.org/1943153002 .
diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn
index 62678f5..2b9c79e 100644
--- a/mojo/BUILD.gn
+++ b/mojo/BUILD.gn
@@ -70,6 +70,8 @@
"//mojo/public/platform/native:tests",
"//mojo/services/files/c:apptests",
"//mojo/services/files/cpp:files_impl_apptests",
+ "//mojo/services/gfx/composition/cpp:apptests",
+ "//mojo/services/gfx/images/cpp:image_pipe_apptest",
"//mojo/services/gfx/images/cpp:image_pipe_apptest",
"//mojo/services/log/cpp:log_client_apptests",
"//mojo/tools:message_generator",
diff --git a/mojo/services/gfx/composition/cpp/BUILD.gn b/mojo/services/gfx/composition/cpp/BUILD.gn
index 8be2931..135df22 100644
--- a/mojo/services/gfx/composition/cpp/BUILD.gn
+++ b/mojo/services/gfx/composition/cpp/BUILD.gn
@@ -3,6 +3,7 @@
# found in the LICENSE file.
import("//build/module_args/mojo.gni")
+import("$mojo_sdk_root/mojo/public/mojo_application.gni")
import("$mojo_sdk_root/mojo/public/mojo_sdk.gni")
mojo_sdk_source_set("cpp") {
@@ -11,6 +12,8 @@
sources = [
"formatting.cc",
"formatting.h",
+ "frame_tracker.cc",
+ "frame_tracker.h",
]
deps = [
@@ -18,3 +21,21 @@
"../interfaces",
]
}
+
+mojo_native_application("apptests") {
+ output_name = "gfx_composition_apptests"
+
+ testonly = true
+
+ sources = [
+ "frame_tracker_apptest.cc",
+ ]
+
+ deps = [
+ ":cpp",
+ "$mojo_sdk_root/mojo/public/cpp/application:standalone",
+ "$mojo_sdk_root/mojo/public/cpp/application:test_support_standalone",
+ "../interfaces",
+ "//testing/gtest",
+ ]
+}
diff --git a/mojo/services/gfx/composition/cpp/frame_tracker.cc b/mojo/services/gfx/composition/cpp/frame_tracker.cc
new file mode 100644
index 0000000..d85246a
--- /dev/null
+++ b/mojo/services/gfx/composition/cpp/frame_tracker.cc
@@ -0,0 +1,88 @@
+// Copyright 2016 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 "mojo/services/gfx/composition/cpp/frame_tracker.h"
+
+#include "mojo/public/cpp/environment/logging.h"
+
+namespace mojo {
+namespace gfx {
+namespace composition {
+
+FrameTracker::FrameTracker() {}
+
+FrameTracker::~FrameTracker() {}
+
+void FrameTracker::Clear() {
+ frame_count_ = 0u;
+ frame_info_ = mojo::gfx::composition::FrameInfo();
+}
+
+uint64_t FrameTracker::Update(
+ const mojo::gfx::composition::FrameInfo& raw_frame_info,
+ MojoTimeTicks now) {
+ const int64_t old_frame_time = frame_info_.frame_time;
+ const int64_t old_presentation_time = frame_info_.presentation_time;
+ frame_info_ = raw_frame_info;
+
+ // Ensure frame info is sane since it comes from another service.
+ if (frame_info_.frame_time > now) {
+ MOJO_LOG(WARNING) << "Frame time is in the future: frame_time="
+ << frame_info_.frame_time << ", now=" << now;
+ frame_info_.frame_time = now;
+ }
+ if (frame_info_.frame_deadline < frame_info_.frame_time) {
+ MOJO_LOG(WARNING)
+ << "Frame deadline is earlier than frame time: frame_deadline="
+ << frame_info_.frame_deadline
+ << ", frame_time=" << frame_info_.frame_time << ", now=" << now;
+ frame_info_.frame_deadline = frame_info_.frame_time;
+ }
+ if (frame_info_.presentation_time < frame_info_.frame_deadline) {
+ MOJO_LOG(WARNING) << "Presentation time is earlier than frame deadline: "
+ "presentation_time="
+ << frame_info_.presentation_time
+ << ", frame_deadline=" << frame_info_.frame_deadline
+ << ", now=" << now;
+ frame_info_.presentation_time = frame_info_.frame_deadline;
+ }
+
+ // Compensate for significant lag by adjusting the frame time if needed
+ // to step past skipped frames.
+ uint64_t lag = now - frame_info_.frame_time;
+ if (frame_info_.frame_interval > 0u && lag >= frame_info_.frame_interval) {
+ uint64_t offset = lag % frame_info_.frame_interval;
+ uint64_t adjustment = now - offset - frame_info_.frame_time;
+ frame_info_.frame_time = now - offset;
+ frame_info_.frame_deadline += adjustment;
+ frame_info_.presentation_time += adjustment;
+
+ // Jank warning.
+ // TODO(jeffbrown): Suppress this once we're happy with things.
+ MOJO_LOG(WARNING) << "Missed " << frame_info_.frame_interval
+ << " us frame deadline by " << lag << " us, skipping "
+ << (lag / frame_info_.frame_interval) << " frames";
+ }
+
+ // Ensure monotonicity.
+ if (frame_count_++ == 0u)
+ return 0u;
+ if (frame_info_.frame_time < old_frame_time) {
+ MOJO_LOG(WARNING) << "Frame time is going backwards: new="
+ << frame_info_.frame_time << ", old=" << old_frame_time
+ << ", now=" << now;
+ frame_info_.frame_time = old_frame_time;
+ }
+ if (frame_info_.presentation_time < old_presentation_time) {
+ MOJO_LOG(WARNING) << "Presentation time is going backwards: new="
+ << frame_info_.presentation_time
+ << ", old=" << old_presentation_time << ", now=" << now;
+ frame_info_.presentation_time = old_presentation_time;
+ }
+ return frame_info_.frame_time - old_frame_time;
+}
+
+} // namespace composition
+} // namespace gfx
+} // namespace mojo
diff --git a/mojo/services/gfx/composition/cpp/frame_tracker.h b/mojo/services/gfx/composition/cpp/frame_tracker.h
new file mode 100644
index 0000000..1093462
--- /dev/null
+++ b/mojo/services/gfx/composition/cpp/frame_tracker.h
@@ -0,0 +1,57 @@
+// Copyright 2016 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 MOJO_SERVICES_GFX_COMPOSITION_CPP_SCHEDULING_H_
+#define MOJO_SERVICES_GFX_COMPOSITION_CPP_SCHEDULING_H_
+
+#include "mojo/services/gfx/composition/interfaces/scheduling.mojom.h"
+
+namespace mojo {
+namespace gfx {
+namespace composition {
+
+// Tracks frame scheduling information.
+class FrameTracker {
+ public:
+ FrameTracker();
+ ~FrameTracker();
+
+ // Returns the number of frames that have been tracked.
+ uint64_t frame_count() const { return frame_count_; }
+
+ // Returns the current frame info.
+ // This value is not meaningful when |frame_count()| is zero.
+ const mojo::gfx::composition::FrameInfo& frame_info() const {
+ return frame_info_;
+ }
+
+ // Clears the frame tracker's state such that the next update will be
+ // treated as if it were the first.
+ void Clear();
+
+ // Updates |frame_info()| with new frame scheduling information
+ // from |raw_frame_info| and applies compensation for lag.
+ //
+ // |now| should come from a recent call to |mojo::GetTimeTicksNow()|.
+ //
+ // Whenever an application receives new frame scheduling information from the
+ // system, it should call this function before using it.
+ //
+ // Returns the time delta between the previous frame and the current frame
+ // in microseconds, or 0 if this is the first frame.
+ uint64_t Update(const mojo::gfx::composition::FrameInfo& raw_frame_info,
+ MojoTimeTicks now);
+
+ private:
+ uint64_t frame_count_ = 0u;
+ mojo::gfx::composition::FrameInfo frame_info_;
+
+ MOJO_DISALLOW_COPY_AND_ASSIGN(FrameTracker);
+};
+
+} // namespace composition
+} // namespace gfx
+} // namespace mojo
+
+#endif // MOJO_SERVICES_GFX_COMPOSITION_CPP_SCHEDULING_H_
diff --git a/mojo/services/gfx/composition/cpp/frame_tracker_apptest.cc b/mojo/services/gfx/composition/cpp/frame_tracker_apptest.cc
new file mode 100644
index 0000000..0591ed0
--- /dev/null
+++ b/mojo/services/gfx/composition/cpp/frame_tracker_apptest.cc
@@ -0,0 +1,186 @@
+// Copyright 2016 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 "mojo/services/gfx/composition/cpp/frame_tracker.h"
+
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "mojo/public/cpp/system/macros.h"
+
+namespace test {
+
+class FrameTrackerTest : public mojo::test::ApplicationTestBase {
+ public:
+ FrameTrackerTest() {}
+ ~FrameTrackerTest() override {}
+
+ protected:
+ mojo::gfx::composition::FrameTracker frame_tracker_;
+
+ uint64_t Update(int64_t frame_time,
+ uint64_t frame_interval,
+ int64_t frame_deadline,
+ int64_t presentation_time,
+ MojoTimeTicks now) {
+ mojo::gfx::composition::FrameInfo frame_info;
+ frame_info.frame_time = frame_time;
+ frame_info.frame_interval = frame_interval;
+ frame_info.frame_deadline = frame_deadline;
+ frame_info.presentation_time = presentation_time;
+ return frame_tracker_.Update(frame_info, now);
+ }
+
+ private:
+ MOJO_DISALLOW_COPY_AND_ASSIGN(FrameTrackerTest);
+};
+
+namespace {
+
+TEST_F(FrameTrackerTest, InitialState) {
+ EXPECT_EQ(0u, frame_tracker_.frame_count());
+ EXPECT_EQ(0, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(0u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(0, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(0, frame_tracker_.frame_info().presentation_time);
+}
+
+TEST_F(FrameTrackerTest, ClearResetsEverything) {
+ Update(10, 10u, 10, 10, 10);
+
+ frame_tracker_.Clear();
+ EXPECT_EQ(0u, frame_tracker_.frame_count());
+ EXPECT_EQ(0, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(0u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(0, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(0, frame_tracker_.frame_info().presentation_time);
+}
+
+TEST_F(FrameTrackerTest, TypicalUpdate) {
+ // Signalled right at frame time.
+ // No corrections.
+ EXPECT_EQ(0u, Update(12, 10u, 24, 28, 12));
+ EXPECT_EQ(1u, frame_tracker_.frame_count());
+ EXPECT_EQ(12, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(24, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(28, frame_tracker_.frame_info().presentation_time);
+
+ // Signalled 1 ms after frame time.
+ // No corrections.
+ EXPECT_EQ(10u, Update(22, 10u, 34, 38, 22 + 1));
+ EXPECT_EQ(2u, frame_tracker_.frame_count());
+ EXPECT_EQ(22, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(34, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(38, frame_tracker_.frame_info().presentation_time);
+
+ // Signalled 9 ms after frame time (frame interval is 10 ms).
+ // No corrections.
+ EXPECT_EQ(10u, Update(32, 10u, 44, 48, 32 + 9));
+ EXPECT_EQ(3u, frame_tracker_.frame_count());
+ EXPECT_EQ(32, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(44, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(48, frame_tracker_.frame_info().presentation_time);
+
+ // Frame interval changed.
+ // No corrections.
+ EXPECT_EQ(14u, Update(46, 15u, 59, 62, 46 + 2));
+ EXPECT_EQ(4u, frame_tracker_.frame_count());
+ EXPECT_EQ(46, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(15u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(59, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(62, frame_tracker_.frame_info().presentation_time);
+}
+
+TEST_F(FrameTrackerTest, LagCompensation) {
+ // Received signal exactly when next frame should begin.
+ // Skip 1 frame.
+ EXPECT_EQ(0u, Update(12, 10u, 24, 28, 12 + 10));
+ EXPECT_EQ(1u, frame_tracker_.frame_count());
+ EXPECT_EQ(22, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(34, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(38, frame_tracker_.frame_info().presentation_time);
+
+ // Received signal 2 ms after next frame should begin.
+ // Skip 1 frame.
+ EXPECT_EQ(20u, Update(32, 10u, 44, 48, 32 + 10 + 2));
+ EXPECT_EQ(2u, frame_tracker_.frame_count());
+ EXPECT_EQ(42, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(54, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(58, frame_tracker_.frame_info().presentation_time);
+
+ // Received signal 35 ms after next frame should begin.
+ // Skip 4 frames.
+ EXPECT_EQ(50u, Update(52, 10u, 64, 68, 52 + 10 + 35));
+ EXPECT_EQ(3u, frame_tracker_.frame_count());
+ EXPECT_EQ(92, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(104, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(108, frame_tracker_.frame_info().presentation_time);
+}
+
+TEST_F(FrameTrackerTest, FrameTimeInPast) {
+ // Frame time is in the future.
+ // Clamp frame time to present.
+ EXPECT_EQ(0u, Update(12, 10u, 24, 28, 12 - 1));
+ EXPECT_EQ(1u, frame_tracker_.frame_count());
+ EXPECT_EQ(11, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(24, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(28, frame_tracker_.frame_info().presentation_time);
+}
+
+TEST_F(FrameTrackerTest, FrameDeadlineBehindFrameTime) {
+ // Frame deadline is earlier than frame time.
+ // Clamp frame deadline time to frame time.
+ EXPECT_EQ(0u, Update(12, 10u, 12 - 1, 28, 12));
+ EXPECT_EQ(1u, frame_tracker_.frame_count());
+ EXPECT_EQ(12, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(12, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(28, frame_tracker_.frame_info().presentation_time);
+}
+
+TEST_F(FrameTrackerTest, PresentationTimeBehindFrameDeadline) {
+ // Presentation time is earlier than frame deadline.
+ // Clamp presentation time to frame deadline.
+ EXPECT_EQ(0u, Update(12, 10u, 24, 24 - 1, 12));
+ EXPECT_EQ(1u, frame_tracker_.frame_count());
+ EXPECT_EQ(12, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(24, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(24, frame_tracker_.frame_info().presentation_time);
+}
+
+TEST_F(FrameTrackerTest, NonMonotonicFrameTime) {
+ Update(12, 10u, 24, 28, 12);
+
+ // Frame time is going backwards.
+ // Clamp frame time to old frame time.
+ EXPECT_EQ(0u, Update(10, 10u, 24, 28, 13));
+ EXPECT_EQ(2u, frame_tracker_.frame_count());
+ EXPECT_EQ(12, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(24, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(28, frame_tracker_.frame_info().presentation_time);
+}
+
+TEST_F(FrameTrackerTest, NonMonotonicPresentationTime) {
+ Update(12, 10u, 24, 28, 12);
+
+ // Presentation time is going backwards.
+ // Clamp presentation time to old presentation time.
+ EXPECT_EQ(10u, Update(22, 10u, 26, 27, 22));
+ EXPECT_EQ(2u, frame_tracker_.frame_count());
+ EXPECT_EQ(22, frame_tracker_.frame_info().frame_time);
+ EXPECT_EQ(10u, frame_tracker_.frame_info().frame_interval);
+ EXPECT_EQ(26, frame_tracker_.frame_info().frame_deadline);
+ EXPECT_EQ(28, frame_tracker_.frame_info().presentation_time);
+}
+
+} // namespace
+} // namespace mojo
diff --git a/mojo/services/gfx/composition/interfaces/scheduling.mojom b/mojo/services/gfx/composition/interfaces/scheduling.mojom
index dac60f9..7b5dee1 100644
--- a/mojo/services/gfx/composition/interfaces/scheduling.mojom
+++ b/mojo/services/gfx/composition/interfaces/scheduling.mojom
@@ -42,6 +42,12 @@
// Provides timestamp information about a frame which has been scheduled
// to be drawn.
+//
+// As there may be latency in receiving an updated |FrameInfo| from the
+// system, the consumer of this structure should apply compensation for
+// skipped frames.
+//
+// See |mojo::gfx::composition::FrameTracker|.
struct FrameInfo {
// A timestamp indicating when the work of updating the frame was scheduled
// to begin which may be used to coordinate animations that require a
diff --git a/mojo/ui/BUILD.gn b/mojo/ui/BUILD.gn
index f9ec43d..b239d8c 100644
--- a/mojo/ui/BUILD.gn
+++ b/mojo/ui/BUILD.gn
@@ -22,6 +22,7 @@
"//mojo/common",
"//mojo/public/cpp/bindings",
"//mojo/public/interfaces/application",
+ "//mojo/services/gfx/composition/cpp",
"//mojo/services/gfx/composition/interfaces",
"//mojo/services/ui/input/interfaces",
"//mojo/services/ui/views/interfaces",
diff --git a/mojo/ui/choreographer.cc b/mojo/ui/choreographer.cc
index 16a6b32..758b70e 100644
--- a/mojo/ui/choreographer.cc
+++ b/mojo/ui/choreographer.cc
@@ -58,60 +58,9 @@
// this exacerbates starvation issues in the Mojo message pump.
// ScheduleFrame();
- // Ensure frame info is sane since it comes from another service.
- // TODO(jeffbrown): Would be better to report an error to the client
- // who can shut things down if needed.
- MojoTimeTicks now = mojo::GetTimeTicksNow();
- if (frame_info->frame_time > now) {
- LOG(WARNING) << "Frame time is in the future: frame_time="
- << frame_info->frame_time << ", now=" << now;
- frame_info->frame_time = now;
- }
- if (frame_info->frame_deadline < frame_info->frame_time) {
- LOG(WARNING)
- << "Frame deadline is earlier than frame time: frame_deadline="
- << frame_info->frame_deadline
- << ", frame_time=" << frame_info->frame_time << ", now=" << now;
- frame_info->frame_deadline = frame_info->frame_time;
- }
- if (frame_info->presentation_time < frame_info->frame_deadline) {
- LOG(WARNING) << "Presentation time is earlier than frame deadline: "
- "presentation_time="
- << frame_info->presentation_time
- << ", frame_deadline=" << frame_info->frame_deadline
- << ", now=" << now;
- frame_info->presentation_time = frame_info->frame_deadline;
- }
-
- // Compensate for significant lag by adjusting the frame time if needed
- // to step past skipped frames.
- uint64_t lag = now - frame_info->frame_time;
- if (frame_info->frame_interval > 0u && lag > frame_info->frame_interval) {
- uint64_t offset = lag % frame_info->frame_interval;
- uint64_t adjustment = now - offset - frame_info->frame_time;
- frame_info->frame_time = now - offset;
- frame_info->frame_deadline += adjustment;
- frame_info->presentation_time += adjustment;
-
- // Jank warning.
- // TODO(jeffbrown): Suppress this once we're happy with things.
- LOG(WARNING) << "Missed " << frame_info->frame_interval
- << " us frame deadline by " << lag << " us, skipping "
- << (lag / frame_info->frame_interval) << " frames";
- }
-
- // Ensure frame time isn't going backwards, just in case the compositor's
- // timing is seriously broken.
- base::TimeDelta time_delta;
- if (last_frame_info_) {
- DCHECK(frame_info->frame_time >= last_frame_info_->frame_time);
- time_delta = base::TimeDelta::FromMicroseconds(
- frame_info->frame_time - last_frame_info_->frame_time);
- }
-
- // Invoke the callback.
- last_frame_info_ = frame_info.Pass();
- delegate_->OnDraw(*last_frame_info_, time_delta);
+ base::TimeDelta time_delta = base::TimeDelta::FromMicroseconds(
+ frame_tracker_.Update(*frame_info, mojo::GetTimeTicksNow()));
+ delegate_->OnDraw(frame_tracker_.frame_info(), time_delta);
}
}
diff --git a/mojo/ui/choreographer.h b/mojo/ui/choreographer.h
index 3708cc2..4e0cad5 100644
--- a/mojo/ui/choreographer.h
+++ b/mojo/ui/choreographer.h
@@ -7,6 +7,7 @@
#include "base/time/time.h"
#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/gfx/composition/cpp/frame_tracker.h"
#include "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
#include "mojo/services/gfx/composition/interfaces/scheduling.mojom.h"
@@ -27,7 +28,7 @@
// const mojo::ui::ViewProvider::CreateViewCallback&
// create_view_callback)
// : BaseView(app_impl, "MyView", create_view_callback),
-// choreographer_(scene_scheduler(), this) {}
+// choreographer_(scene(), this) {}
// ~MyView() override {}
//
// private:
@@ -51,9 +52,9 @@
return scene_scheduler_.get();
}
- // Gets the most recent frame info, or null if none.
- mojo::gfx::composition::FrameInfo* last_frame_info() {
- return last_frame_info_.get();
+ // Gets the frame tracker.
+ mojo::gfx::composition::FrameTracker& frame_tracker() {
+ return frame_tracker_;
}
// Schedules a call to the delegate's |OnDraw| using the scene scheduler.
@@ -62,7 +63,7 @@
private:
mojo::gfx::composition::SceneSchedulerPtr scene_scheduler_;
ChoreographerDelegate* delegate_;
- mojo::gfx::composition::FrameInfoPtr last_frame_info_;
+ mojo::gfx::composition::FrameTracker frame_tracker_;
void ScheduleFrame();
void DoFrame(mojo::gfx::composition::FrameInfoPtr frame_info);