summaryrefslogtreecommitdiffstats
path: root/cc/scheduler
diff options
context:
space:
mode:
authorjamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-18 08:36:31 +0000
committerjamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-18 08:36:31 +0000
commitbe4655a91f88479e913499a8587e47453dda308f (patch)
treec337ce7463df87ab3f50ec80964b5893ad6bbaef /cc/scheduler
parente12dd0e802b2a80112cb40e01fabcc5c0475f05b (diff)
downloadchromium_src-be4655a91f88479e913499a8587e47453dda308f.zip
chromium_src-be4655a91f88479e913499a8587e47453dda308f.tar.gz
chromium_src-be4655a91f88479e913499a8587e47453dda308f.tar.bz2
Part 9 of cc/ directory shuffles: scheduler
Continuation of https://src.chromium.org/viewvc/chrome?view=rev&revision=188681 BUG=190824 TBR=enne@chromium.org Review URL: https://codereview.chromium.org/12471008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@188697 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'cc/scheduler')
-rw-r--r--cc/scheduler/delay_based_time_source.cc234
-rw-r--r--cc/scheduler/delay_based_time_source.h81
-rw-r--r--cc/scheduler/delay_based_time_source_unittest.cc374
-rw-r--r--cc/scheduler/frame_rate_controller.cc164
-rw-r--r--cc/scheduler/frame_rate_controller.h91
-rw-r--r--cc/scheduler/frame_rate_controller_unittest.cc169
-rw-r--r--cc/scheduler/rate_limiter.cc57
-rw-r--r--cc/scheduler/rate_limiter.h57
-rw-r--r--cc/scheduler/scheduler.cc202
-rw-r--r--cc/scheduler/scheduler.h127
-rw-r--r--cc/scheduler/scheduler_settings.cc13
-rw-r--r--cc/scheduler/scheduler_settings.h22
-rw-r--r--cc/scheduler/scheduler_state_machine.cc433
-rw-r--r--cc/scheduler/scheduler_state_machine.h193
-rw-r--r--cc/scheduler/scheduler_state_machine_unittest.cc1081
-rw-r--r--cc/scheduler/scheduler_unittest.cc520
-rw-r--r--cc/scheduler/texture_uploader.cc351
-rw-r--r--cc/scheduler/texture_uploader.h125
-rw-r--r--cc/scheduler/texture_uploader_unittest.cc243
-rw-r--r--cc/scheduler/time_source.h46
-rw-r--r--cc/scheduler/vsync_time_source.cc69
-rw-r--r--cc/scheduler/vsync_time_source.h67
-rw-r--r--cc/scheduler/vsync_time_source_unittest.cc103
23 files changed, 4822 insertions, 0 deletions
diff --git a/cc/scheduler/delay_based_time_source.cc b/cc/scheduler/delay_based_time_source.cc
new file mode 100644
index 0000000..f7fb1b7
--- /dev/null
+++ b/cc/scheduler/delay_based_time_source.cc
@@ -0,0 +1,234 @@
+// Copyright 2011 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 "cc/scheduler/delay_based_time_source.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "cc/base/thread.h"
+
+namespace cc {
+
+namespace {
+
+// doubleTickThreshold prevents ticks from running within the specified fraction of an interval.
+// This helps account for jitter in the timebase as well as quick timer reactivation.
+const double doubleTickThreshold = 0.25;
+
+// intervalChangeThreshold is the fraction of the interval that will trigger an immediate interval change.
+// phaseChangeThreshold is the fraction of the interval that will trigger an immediate phase change.
+// If the changes are within the thresholds, the change will take place on the next tick.
+// If either change is outside the thresholds, the next tick will be canceled and reissued immediately.
+const double intervalChangeThreshold = 0.25;
+const double phaseChangeThreshold = 0.25;
+
+} // namespace
+
+scoped_refptr<DelayBasedTimeSource> DelayBasedTimeSource::create(base::TimeDelta interval, Thread* thread)
+{
+ return make_scoped_refptr(new DelayBasedTimeSource(interval, thread));
+}
+
+DelayBasedTimeSource::DelayBasedTimeSource(base::TimeDelta interval, Thread* thread)
+ : m_client(0)
+ , m_hasTickTarget(false)
+ , m_currentParameters(interval, base::TimeTicks())
+ , m_nextParameters(interval, base::TimeTicks())
+ , m_state(STATE_INACTIVE)
+ , m_thread(thread)
+ , m_weakFactory(ALLOW_THIS_IN_INITIALIZER_LIST(this))
+{
+}
+
+DelayBasedTimeSource::~DelayBasedTimeSource()
+{
+}
+
+void DelayBasedTimeSource::setActive(bool active)
+{
+ TRACE_EVENT1("cc", "DelayBasedTimeSource::setActive", "active", active);
+ if (!active) {
+ m_state = STATE_INACTIVE;
+ m_weakFactory.InvalidateWeakPtrs();
+ return;
+ }
+
+ if (m_state == STATE_STARTING || m_state == STATE_ACTIVE)
+ return;
+
+ if (!m_hasTickTarget) {
+ // Becoming active the first time is deferred: we post a 0-delay task. When
+ // it runs, we use that to establish the timebase, become truly active, and
+ // fire the first tick.
+ m_state = STATE_STARTING;
+ m_thread->PostTask(base::Bind(&DelayBasedTimeSource::onTimerFired, m_weakFactory.GetWeakPtr()));
+ return;
+ }
+
+ m_state = STATE_ACTIVE;
+
+ postNextTickTask(now());
+}
+
+bool DelayBasedTimeSource::active() const
+{
+ return m_state != STATE_INACTIVE;
+}
+
+base::TimeTicks DelayBasedTimeSource::lastTickTime()
+{
+ return m_lastTickTime;
+}
+
+base::TimeTicks DelayBasedTimeSource::nextTickTime()
+{
+ return active() ? m_currentParameters.tickTarget : base::TimeTicks();
+}
+
+void DelayBasedTimeSource::onTimerFired()
+{
+ DCHECK(m_state != STATE_INACTIVE);
+
+ base::TimeTicks now = this->now();
+ m_lastTickTime = now;
+
+ if (m_state == STATE_STARTING) {
+ setTimebaseAndInterval(now, m_currentParameters.interval);
+ m_state = STATE_ACTIVE;
+ }
+
+ postNextTickTask(now);
+
+ // Fire the tick
+ if (m_client)
+ m_client->onTimerTick();
+}
+
+void DelayBasedTimeSource::setClient(TimeSourceClient* client)
+{
+ m_client = client;
+}
+
+void DelayBasedTimeSource::setTimebaseAndInterval(base::TimeTicks timebase, base::TimeDelta interval)
+{
+ m_nextParameters.interval = interval;
+ m_nextParameters.tickTarget = timebase;
+ m_hasTickTarget = true;
+
+ if (m_state != STATE_ACTIVE) {
+ // If we aren't active, there's no need to reset the timer.
+ return;
+ }
+
+ // If the change in interval is larger than the change threshold,
+ // request an immediate reset.
+ double intervalDelta = std::abs((interval - m_currentParameters.interval).InSecondsF());
+ double intervalChange = intervalDelta / interval.InSecondsF();
+ if (intervalChange > intervalChangeThreshold) {
+ setActive(false);
+ setActive(true);
+ return;
+ }
+
+ // If the change in phase is greater than the change threshold in either
+ // direction, request an immediate reset. This logic might result in a false
+ // negative if there is a simultaneous small change in the interval and the
+ // fmod just happens to return something near zero. Assuming the timebase
+ // is very recent though, which it should be, we'll still be ok because the
+ // old clock and new clock just happen to line up.
+ double targetDelta = std::abs((timebase - m_currentParameters.tickTarget).InSecondsF());
+ double phaseChange = fmod(targetDelta, interval.InSecondsF()) / interval.InSecondsF();
+ if (phaseChange > phaseChangeThreshold && phaseChange < (1.0 - phaseChangeThreshold)) {
+ setActive(false);
+ setActive(true);
+ return;
+ }
+}
+
+base::TimeTicks DelayBasedTimeSource::now() const
+{
+ return base::TimeTicks::Now();
+}
+
+// This code tries to achieve an average tick rate as close to m_interval as possible.
+// To do this, it has to deal with a few basic issues:
+// 1. postDelayedTask can delay only at a millisecond granularity. So, 16.666 has to
+// posted as 16 or 17.
+// 2. A delayed task may come back a bit late (a few ms), or really late (frames later)
+//
+// The basic idea with this scheduler here is to keep track of where we *want* to run in
+// m_tickTarget. We update this with the exact interval.
+//
+// Then, when we post our task, we take the floor of (m_tickTarget and now()). If we
+// started at now=0, and 60FPs (all times in milliseconds):
+// now=0 target=16.667 postDelayedTask(16)
+//
+// When our callback runs, we figure out how far off we were from that goal. Because of the flooring
+// operation, and assuming our timer runs exactly when it should, this yields:
+// now=16 target=16.667
+//
+// Since we can't post a 0.667 ms task to get to now=16, we just treat this as a tick. Then,
+// we update target to be 33.333. We now post another task based on the difference between our target
+// and now:
+// now=16 tickTarget=16.667 newTarget=33.333 --> postDelayedTask(floor(33.333 - 16)) --> postDelayedTask(17)
+//
+// Over time, with no late tasks, this leads to us posting tasks like this:
+// now=0 tickTarget=0 newTarget=16.667 --> tick(), postDelayedTask(16)
+// now=16 tickTarget=16.667 newTarget=33.333 --> tick(), postDelayedTask(17)
+// now=33 tickTarget=33.333 newTarget=50.000 --> tick(), postDelayedTask(17)
+// now=50 tickTarget=50.000 newTarget=66.667 --> tick(), postDelayedTask(16)
+//
+// We treat delays in tasks differently depending on the amount of delay we encounter. Suppose we
+// posted a task with a target=16.667:
+// Case 1: late but not unrecoverably-so
+// now=18 tickTarget=16.667
+//
+// Case 2: so late we obviously missed the tick
+// now=25.0 tickTarget=16.667
+//
+// We treat the first case as a tick anyway, and assume the delay was
+// unusual. Thus, we compute the newTarget based on the old timebase:
+// now=18 tickTarget=16.667 newTarget=33.333 --> tick(), postDelayedTask(floor(33.333-18)) --> postDelayedTask(15)
+// This brings us back to 18+15 = 33, which was where we would have been if the task hadn't been late.
+//
+// For the really late delay, we we move to the next logical tick. The timebase is not reset.
+// now=37 tickTarget=16.667 newTarget=50.000 --> tick(), postDelayedTask(floor(50.000-37)) --> postDelayedTask(13)
+base::TimeTicks DelayBasedTimeSource::nextTickTarget(base::TimeTicks now)
+{
+ base::TimeDelta newInterval = m_nextParameters.interval;
+ int intervalsElapsed = static_cast<int>(floor((now - m_nextParameters.tickTarget).InSecondsF() / newInterval.InSecondsF()));
+ base::TimeTicks lastEffectiveTick = m_nextParameters.tickTarget + newInterval * intervalsElapsed;
+ base::TimeTicks newTickTarget = lastEffectiveTick + newInterval;
+ DCHECK(newTickTarget > now);
+
+ // Avoid double ticks when:
+ // 1) Turning off the timer and turning it right back on.
+ // 2) Jittery data is passed to setTimebaseAndInterval().
+ if (newTickTarget - m_lastTickTime <= newInterval / static_cast<int>(1.0 / doubleTickThreshold))
+ newTickTarget += newInterval;
+
+ return newTickTarget;
+}
+
+void DelayBasedTimeSource::postNextTickTask(base::TimeTicks now)
+{
+ base::TimeTicks newTickTarget = nextTickTarget(now);
+
+ // Post another task *before* the tick and update state
+ base::TimeDelta delay = newTickTarget - now;
+ DCHECK(delay.InMillisecondsF() <=
+ m_nextParameters.interval.InMillisecondsF() * (1.0 + doubleTickThreshold));
+ m_thread->PostDelayedTask(base::Bind(&DelayBasedTimeSource::onTimerFired,
+ m_weakFactory.GetWeakPtr()),
+ delay);
+
+ m_nextParameters.tickTarget = newTickTarget;
+ m_currentParameters = m_nextParameters;
+}
+
+} // namespace cc
diff --git a/cc/scheduler/delay_based_time_source.h b/cc/scheduler/delay_based_time_source.h
new file mode 100644
index 0000000..d2e2a78
--- /dev/null
+++ b/cc/scheduler/delay_based_time_source.h
@@ -0,0 +1,81 @@
+// Copyright 2011 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 CC_SCHEDULER_DELAY_BASED_TIME_SOURCE_H_
+#define CC_SCHEDULER_DELAY_BASED_TIME_SOURCE_H_
+
+#include "base/memory/weak_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/scheduler/time_source.h"
+
+namespace cc {
+
+class Thread;
+
+// This timer implements a time source that achieves the specified interval
+// in face of millisecond-precision delayed callbacks and random queueing delays.
+class CC_EXPORT DelayBasedTimeSource : public TimeSource {
+public:
+ static scoped_refptr<DelayBasedTimeSource> create(base::TimeDelta interval, Thread* thread);
+
+ virtual void setClient(TimeSourceClient* client) OVERRIDE;
+
+ // TimeSource implementation
+ virtual void setTimebaseAndInterval(base::TimeTicks timebase, base::TimeDelta interval) OVERRIDE;
+
+ virtual void setActive(bool) OVERRIDE;
+ virtual bool active() const OVERRIDE;
+
+ // Get the last and next tick times. nextTimeTime() returns null when
+ // inactive.
+ virtual base::TimeTicks lastTickTime() OVERRIDE;
+ virtual base::TimeTicks nextTickTime() OVERRIDE;
+
+
+ // Virtual for testing.
+ virtual base::TimeTicks now() const;
+
+protected:
+ DelayBasedTimeSource(base::TimeDelta interval, Thread* thread);
+ virtual ~DelayBasedTimeSource();
+
+ base::TimeTicks nextTickTarget(base::TimeTicks now);
+ void postNextTickTask(base::TimeTicks now);
+ void onTimerFired();
+
+ enum State {
+ STATE_INACTIVE,
+ STATE_STARTING,
+ STATE_ACTIVE,
+ };
+
+ struct Parameters {
+ Parameters(base::TimeDelta interval, base::TimeTicks tickTarget)
+ : interval(interval), tickTarget(tickTarget)
+ { }
+ base::TimeDelta interval;
+ base::TimeTicks tickTarget;
+ };
+
+ TimeSourceClient* m_client;
+ bool m_hasTickTarget;
+ base::TimeTicks m_lastTickTime;
+
+ // m_currentParameters should only be written by postNextTickTask.
+ // m_nextParameters will take effect on the next call to postNextTickTask.
+ // Maintaining a pending set of parameters allows nextTickTime() to always
+ // reflect the actual time we expect onTimerFired to be called.
+ Parameters m_currentParameters;
+ Parameters m_nextParameters;
+
+ State m_state;
+
+ Thread* m_thread;
+ base::WeakPtrFactory<DelayBasedTimeSource> m_weakFactory;
+ DISALLOW_COPY_AND_ASSIGN(DelayBasedTimeSource);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_DELAY_BASED_TIME_SOURCE_H_
diff --git a/cc/scheduler/delay_based_time_source_unittest.cc b/cc/scheduler/delay_based_time_source_unittest.cc
new file mode 100644
index 0000000..de4e11e
--- /dev/null
+++ b/cc/scheduler/delay_based_time_source_unittest.cc
@@ -0,0 +1,374 @@
+// Copyright 2011 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 "cc/scheduler/delay_based_time_source.h"
+
+#include "cc/base/thread.h"
+#include "cc/test/scheduler_test_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+base::TimeDelta interval()
+{
+ return base::TimeDelta::FromMicroseconds(base::Time::kMicrosecondsPerSecond / 60);
+}
+
+TEST(DelayBasedTimeSourceTest, TaskPostedAndTickCalled)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+
+ timer->setActive(true);
+ EXPECT_TRUE(timer->active());
+ EXPECT_TRUE(thread.hasPendingTask());
+
+ timer->setNow(timer->now() + base::TimeDelta::FromMilliseconds(16));
+ thread.runPendingTask();
+ EXPECT_TRUE(timer->active());
+ EXPECT_TRUE(client.tickCalled());
+}
+
+TEST(DelayBasedTimeSource, TickNotCalledWithTaskPosted)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ EXPECT_TRUE(thread.hasPendingTask());
+ timer->setActive(false);
+ thread.runPendingTask();
+ EXPECT_FALSE(client.tickCalled());
+}
+
+TEST(DelayBasedTimeSource, StartTwiceEnqueuesOneTask)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ EXPECT_TRUE(thread.hasPendingTask());
+ thread.reset();
+ timer->setActive(true);
+ EXPECT_FALSE(thread.hasPendingTask());
+}
+
+TEST(DelayBasedTimeSource, StartWhenRunningDoesntTick)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ thread.runPendingTask();
+ thread.reset();
+ timer->setActive(true);
+ EXPECT_FALSE(thread.hasPendingTask());
+}
+
+// At 60Hz, when the tick returns at exactly the requested next time, make sure
+// a 16ms next delay is posted.
+TEST(DelayBasedTimeSource, NextDelaySaneWhenExactlyOnRequestedTime)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ // Run the first task, as that activates the timer and picks up a timebase.
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ timer->setNow(timer->now() + interval());
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+}
+
+// At 60Hz, when the tick returns at slightly after the requested next time, make sure
+// a 16ms next delay is posted.
+TEST(DelayBasedTimeSource, NextDelaySaneWhenSlightlyAfterRequestedTime)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ // Run the first task, as that activates the timer and picks up a timebase.
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ timer->setNow(timer->now() + interval() + base::TimeDelta::FromMicroseconds(1));
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+}
+
+// At 60Hz, when the tick returns at exactly 2*interval after the requested next time, make sure
+// a 16ms next delay is posted.
+TEST(DelayBasedTimeSource, NextDelaySaneWhenExactlyTwiceAfterRequestedTime)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ // Run the first task, as that activates the timer and picks up a timebase.
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ timer->setNow(timer->now() + 2 * interval());
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+}
+
+// At 60Hz, when the tick returns at 2*interval and a bit after the requested next time, make sure
+// a 16ms next delay is posted.
+TEST(DelayBasedTimeSource, NextDelaySaneWhenSlightlyAfterTwiceRequestedTime)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ // Run the first task, as that activates the timer and picks up a timebase.
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ timer->setNow(timer->now() + 2 * interval() + base::TimeDelta::FromMicroseconds(1));
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+}
+
+// At 60Hz, when the tick returns halfway to the next frame time, make sure
+// a correct next delay value is posted.
+TEST(DelayBasedTimeSource, NextDelaySaneWhenHalfAfterRequestedTime)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ // Run the first task, as that activates the timer and picks up a timebase.
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ timer->setNow(timer->now() + interval() + base::TimeDelta::FromMilliseconds(8));
+ thread.runPendingTask();
+
+ EXPECT_EQ(8, thread.pendingDelayMs());
+}
+
+// If the timebase and interval are updated with a jittery source, we want to
+// make sure we do not double tick.
+TEST(DelayBasedTimeSource, SaneHandlingOfJitteryTimebase)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ // Run the first task, as that activates the timer and picks up a timebase.
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ // Jitter timebase ~1ms late
+ timer->setNow(timer->now() + interval());
+ timer->setTimebaseAndInterval(timer->now() + base::TimeDelta::FromMilliseconds(1), interval());
+ thread.runPendingTask();
+
+ // Without double tick prevention, pendingDelayMs would be 1.
+ EXPECT_EQ(17, thread.pendingDelayMs());
+
+ // Jitter timebase ~1ms early
+ timer->setNow(timer->now() + interval());
+ timer->setTimebaseAndInterval(timer->now() - base::TimeDelta::FromMilliseconds(1), interval());
+ thread.runPendingTask();
+
+ EXPECT_EQ(15, thread.pendingDelayMs());
+}
+
+TEST(DelayBasedTimeSource, HandlesSignificantTimebaseChangesImmediately)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ // Run the first task, as that activates the timer and picks up a timebase.
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ // Tick, then shift timebase by +7ms.
+ timer->setNow(timer->now() + interval());
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ client.reset();
+ thread.runPendingTaskOnOverwrite(true);
+ base::TimeDelta jitter = base::TimeDelta::FromMilliseconds(7) + base::TimeDelta::FromMicroseconds(1);
+ timer->setTimebaseAndInterval(timer->now() + jitter, interval());
+ thread.runPendingTaskOnOverwrite(false);
+
+ EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled.
+ EXPECT_EQ(7, thread.pendingDelayMs());
+
+ // Tick, then shift timebase by -7ms.
+ timer->setNow(timer->now() + jitter);
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ client.reset();
+ thread.runPendingTaskOnOverwrite(true);
+ timer->setTimebaseAndInterval(base::TimeTicks() + interval(), interval());
+ thread.runPendingTaskOnOverwrite(false);
+
+ EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled.
+ EXPECT_EQ(16-7, thread.pendingDelayMs());
+}
+
+TEST(DelayBasedTimeSource, HanldlesSignificantIntervalChangesImmediately)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+ // Run the first task, as that activates the timer and picks up a timebase.
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ // Tick, then double the interval.
+ timer->setNow(timer->now() + interval());
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ client.reset();
+ thread.runPendingTaskOnOverwrite(true);
+ timer->setTimebaseAndInterval(base::TimeTicks() + interval(), interval() * 2);
+ thread.runPendingTaskOnOverwrite(false);
+
+ EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled.
+ EXPECT_EQ(33, thread.pendingDelayMs());
+
+ // Tick, then halve the interval.
+ timer->setNow(timer->now() + interval() * 2);
+ thread.runPendingTask();
+
+ EXPECT_EQ(33, thread.pendingDelayMs());
+
+ client.reset();
+ thread.runPendingTaskOnOverwrite(true);
+ timer->setTimebaseAndInterval(base::TimeTicks() + interval() * 3, interval());
+ thread.runPendingTaskOnOverwrite(false);
+
+ EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled.
+ EXPECT_EQ(16, thread.pendingDelayMs());
+}
+
+TEST(DelayBasedTimeSourceTest, AchievesTargetRateWithNoNoise)
+{
+ int numIterations = 10;
+
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true);
+
+ double totalFrameTime = 0;
+ for (int i = 0; i < numIterations; ++i) {
+ long long delayMs = thread.pendingDelayMs();
+
+ // accumulate the "delay"
+ totalFrameTime += delayMs / 1000.0;
+
+ // Run the callback exactly when asked
+ timer->setNow(timer->now() + base::TimeDelta::FromMilliseconds(delayMs));
+ thread.runPendingTask();
+ }
+ double averageInterval = totalFrameTime / static_cast<double>(numIterations);
+ EXPECT_NEAR(1.0 / 60.0, averageInterval, 0.1);
+}
+
+TEST(DelayBasedTimeSource, TestDeactivateWhilePending)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+ timer->setActive(true); // Should post a task.
+ timer->setActive(false);
+ timer = NULL;
+ thread.runPendingTask(); // Should run the posted task without crashing.
+}
+
+TEST(DelayBasedTimeSource, TestDeactivateAndReactivateBeforeNextTickTime)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+
+ // Should run the activate task, and pick up a new timebase.
+ timer->setActive(true);
+ thread.runPendingTask();
+
+ // Stop the timer
+ timer->setActive(false);
+
+ // Task will be pending anyway, run it
+ thread.runPendingTask();
+
+ // Start the timer again, but before the next tick time the timer previously
+ // planned on using. That same tick time should still be targeted.
+ timer->setNow(timer->now() + base::TimeDelta::FromMilliseconds(4));
+ timer->setActive(true);
+ EXPECT_EQ(12, thread.pendingDelayMs());
+}
+
+TEST(DelayBasedTimeSource, TestDeactivateAndReactivateAfterNextTickTime)
+{
+ FakeThread thread;
+ FakeTimeSourceClient client;
+ scoped_refptr<FakeDelayBasedTimeSource> timer = FakeDelayBasedTimeSource::create(interval(), &thread);
+ timer->setClient(&client);
+
+ // Should run the activate task, and pick up a new timebase.
+ timer->setActive(true);
+ thread.runPendingTask();
+
+ // Stop the timer
+ timer->setActive(false);
+
+ // Task will be pending anyway, run it
+ thread.runPendingTask();
+
+ // Start the timer again, but before the next tick time the timer previously
+ // planned on using. That same tick time should still be targeted.
+ timer->setNow(timer->now() + base::TimeDelta::FromMilliseconds(20));
+ timer->setActive(true);
+ EXPECT_EQ(13, thread.pendingDelayMs());
+}
+
+} // namespace
+} // namespace cc
diff --git a/cc/scheduler/frame_rate_controller.cc b/cc/scheduler/frame_rate_controller.cc
new file mode 100644
index 0000000..de7b26a
--- /dev/null
+++ b/cc/scheduler/frame_rate_controller.cc
@@ -0,0 +1,164 @@
+// Copyright 2011 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 "cc/scheduler/frame_rate_controller.h"
+
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+#include "cc/base/thread.h"
+#include "cc/scheduler/delay_based_time_source.h"
+#include "cc/scheduler/time_source.h"
+
+namespace cc {
+
+class FrameRateControllerTimeSourceAdapter : public TimeSourceClient {
+public:
+ static scoped_ptr<FrameRateControllerTimeSourceAdapter> Create(FrameRateController* frameRateController) {
+ return make_scoped_ptr(new FrameRateControllerTimeSourceAdapter(frameRateController));
+ }
+ virtual ~FrameRateControllerTimeSourceAdapter() {}
+
+ virtual void onTimerTick() OVERRIDE {
+ m_frameRateController->onTimerTick();
+ }
+
+private:
+ explicit FrameRateControllerTimeSourceAdapter(FrameRateController* frameRateController)
+ : m_frameRateController(frameRateController) {}
+
+ FrameRateController* m_frameRateController;
+};
+
+FrameRateController::FrameRateController(scoped_refptr<TimeSource> timer)
+ : m_client(0)
+ , m_numFramesPending(0)
+ , m_maxFramesPending(0)
+ , m_timeSource(timer)
+ , m_active(false)
+ , m_swapBuffersCompleteSupported(true)
+ , m_isTimeSourceThrottling(true)
+ , m_thread(0)
+ , m_weakFactory(ALLOW_THIS_IN_INITIALIZER_LIST(this))
+{
+ m_timeSourceClientAdapter = FrameRateControllerTimeSourceAdapter::Create(this);
+ m_timeSource->setClient(m_timeSourceClientAdapter.get());
+}
+
+FrameRateController::FrameRateController(Thread* thread)
+ : m_client(0)
+ , m_numFramesPending(0)
+ , m_maxFramesPending(0)
+ , m_active(false)
+ , m_swapBuffersCompleteSupported(true)
+ , m_isTimeSourceThrottling(false)
+ , m_thread(thread)
+ , m_weakFactory(ALLOW_THIS_IN_INITIALIZER_LIST(this))
+{
+}
+
+FrameRateController::~FrameRateController()
+{
+ if (m_isTimeSourceThrottling)
+ m_timeSource->setActive(false);
+}
+
+void FrameRateController::setActive(bool active)
+{
+ if (m_active == active)
+ return;
+ TRACE_EVENT1("cc", "FrameRateController::setActive", "active", active);
+ m_active = active;
+
+ if (m_isTimeSourceThrottling)
+ m_timeSource->setActive(active);
+ else {
+ if (active)
+ postManualTick();
+ else
+ m_weakFactory.InvalidateWeakPtrs();
+ }
+}
+
+void FrameRateController::setMaxFramesPending(int maxFramesPending)
+{
+ DCHECK_GE(maxFramesPending, 0);
+ m_maxFramesPending = maxFramesPending;
+}
+
+void FrameRateController::setTimebaseAndInterval(base::TimeTicks timebase, base::TimeDelta interval)
+{
+ if (m_isTimeSourceThrottling)
+ m_timeSource->setTimebaseAndInterval(timebase, interval);
+}
+
+void FrameRateController::setSwapBuffersCompleteSupported(bool supported)
+{
+ m_swapBuffersCompleteSupported = supported;
+}
+
+void FrameRateController::onTimerTick()
+{
+ DCHECK(m_active);
+
+ // Check if we have too many frames in flight.
+ bool throttled = m_maxFramesPending && m_numFramesPending >= m_maxFramesPending;
+ TRACE_COUNTER_ID1("cc", "ThrottledVSyncInterval", m_thread, throttled);
+
+ if (m_client)
+ m_client->vsyncTick(throttled);
+
+ if (m_swapBuffersCompleteSupported && !m_isTimeSourceThrottling && !throttled)
+ postManualTick();
+}
+
+void FrameRateController::postManualTick()
+{
+ if (m_active)
+ m_thread->PostTask(base::Bind(&FrameRateController::manualTick, m_weakFactory.GetWeakPtr()));
+}
+
+void FrameRateController::manualTick()
+{
+ onTimerTick();
+}
+
+void FrameRateController::didBeginFrame()
+{
+ if (m_swapBuffersCompleteSupported)
+ m_numFramesPending++;
+ else if (!m_isTimeSourceThrottling)
+ postManualTick();
+}
+
+void FrameRateController::didFinishFrame()
+{
+ DCHECK(m_swapBuffersCompleteSupported);
+
+ m_numFramesPending--;
+ if (!m_isTimeSourceThrottling)
+ postManualTick();
+}
+
+void FrameRateController::didAbortAllPendingFrames()
+{
+ m_numFramesPending = 0;
+}
+
+base::TimeTicks FrameRateController::nextTickTime()
+{
+ if (m_isTimeSourceThrottling)
+ return m_timeSource->nextTickTime();
+
+ return base::TimeTicks();
+}
+
+base::TimeTicks FrameRateController::lastTickTime()
+{
+ if (m_isTimeSourceThrottling)
+ return m_timeSource->lastTickTime();
+
+ return base::TimeTicks::Now();
+}
+
+} // namespace cc
diff --git a/cc/scheduler/frame_rate_controller.h b/cc/scheduler/frame_rate_controller.h
new file mode 100644
index 0000000..46fa592
--- /dev/null
+++ b/cc/scheduler/frame_rate_controller.h
@@ -0,0 +1,91 @@
+// Copyright 2011 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 CC_SCHEDULER_FRAME_RATE_CONTROLLER_H_
+#define CC_SCHEDULER_FRAME_RATE_CONTROLLER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class Thread;
+class TimeSource;
+
+class CC_EXPORT FrameRateControllerClient {
+public:
+ // Throttled is true when we have a maximum number of frames pending.
+ virtual void vsyncTick(bool throttled) = 0;
+
+protected:
+ virtual ~FrameRateControllerClient() {}
+};
+
+class FrameRateControllerTimeSourceAdapter;
+
+class CC_EXPORT FrameRateController {
+public:
+ enum {
+ kDefaultMaxFramesPending = 2
+ };
+
+ explicit FrameRateController(scoped_refptr<TimeSource>);
+ // Alternate form of FrameRateController with unthrottled frame-rate.
+ explicit FrameRateController(Thread*);
+ virtual ~FrameRateController();
+
+ void setClient(FrameRateControllerClient* client) { m_client = client; }
+
+ void setActive(bool);
+
+ // Use the following methods to adjust target frame rate.
+ //
+ // Multiple frames can be in-progress, but for every didBeginFrame, a
+ // didFinishFrame should be posted.
+ //
+ // If the rendering pipeline crashes, call didAbortAllPendingFrames.
+ void didBeginFrame();
+ void didFinishFrame();
+ void didAbortAllPendingFrames();
+ void setMaxFramesPending(int); // 0 for unlimited.
+ int maxFramesPending() const { return m_maxFramesPending; }
+
+ // This returns null for unthrottled frame-rate.
+ base::TimeTicks nextTickTime();
+
+ // This returns now for unthrottled frame-rate.
+ base::TimeTicks lastTickTime();
+
+ void setTimebaseAndInterval(base::TimeTicks timebase, base::TimeDelta interval);
+ void setSwapBuffersCompleteSupported(bool);
+
+protected:
+ friend class FrameRateControllerTimeSourceAdapter;
+ void onTimerTick();
+
+ void postManualTick();
+ void manualTick();
+
+ FrameRateControllerClient* m_client;
+ int m_numFramesPending;
+ int m_maxFramesPending;
+ scoped_refptr<TimeSource> m_timeSource;
+ scoped_ptr<FrameRateControllerTimeSourceAdapter> m_timeSourceClientAdapter;
+ bool m_active;
+ bool m_swapBuffersCompleteSupported;
+
+ // Members for unthrottled frame-rate.
+ bool m_isTimeSourceThrottling;
+ base::WeakPtrFactory<FrameRateController> m_weakFactory;
+ Thread* m_thread;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameRateController);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_FRAME_RATE_CONTROLLER_H_
diff --git a/cc/scheduler/frame_rate_controller_unittest.cc b/cc/scheduler/frame_rate_controller_unittest.cc
new file mode 100644
index 0000000..4eb7e60
--- /dev/null
+++ b/cc/scheduler/frame_rate_controller_unittest.cc
@@ -0,0 +1,169 @@
+// Copyright 2011 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 "cc/scheduler/frame_rate_controller.h"
+
+#include "cc/test/scheduler_test_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class FakeFrameRateControllerClient : public cc::FrameRateControllerClient {
+public:
+ FakeFrameRateControllerClient() { reset(); }
+
+ void reset() { m_vsyncTicked = false; }
+ bool vsyncTicked() const { return m_vsyncTicked; }
+
+ virtual void vsyncTick(bool throttled) OVERRIDE {
+ m_vsyncTicked = !throttled;
+ }
+
+protected:
+ bool m_vsyncTicked;
+};
+
+
+TEST(FrameRateControllerTest, TestFrameThrottling_ImmediateAck)
+{
+ FakeThread thread;
+ FakeFrameRateControllerClient client;
+ base::TimeDelta interval = base::TimeDelta::FromMicroseconds(base::Time::kMicrosecondsPerSecond / 60);
+ scoped_refptr<FakeDelayBasedTimeSource> timeSource = FakeDelayBasedTimeSource::create(interval, &thread);
+ FrameRateController controller(timeSource);
+
+ controller.setClient(&client);
+ controller.setActive(true);
+
+ base::TimeTicks elapsed; // Muck around with time a bit
+
+ // Trigger one frame, make sure the vsync callback is called
+ elapsed += base::TimeDelta::FromMilliseconds(thread.pendingDelayMs());
+ timeSource->setNow(elapsed);
+ thread.runPendingTask();
+ EXPECT_TRUE(client.vsyncTicked());
+ client.reset();
+
+ // Tell the controller we drew
+ controller.didBeginFrame();
+
+ // Tell the controller the frame ended 5ms later
+ timeSource->setNow(timeSource->now() + base::TimeDelta::FromMilliseconds(5));
+ controller.didFinishFrame();
+
+ // Trigger another frame, make sure vsync runs again
+ elapsed += base::TimeDelta::FromMilliseconds(thread.pendingDelayMs());
+ EXPECT_TRUE(elapsed >= timeSource->now()); // Sanity check that previous code didn't move time backward.
+ timeSource->setNow(elapsed);
+ thread.runPendingTask();
+ EXPECT_TRUE(client.vsyncTicked());
+}
+
+TEST(FrameRateControllerTest, TestFrameThrottling_TwoFramesInFlight)
+{
+ FakeThread thread;
+ FakeFrameRateControllerClient client;
+ base::TimeDelta interval = base::TimeDelta::FromMicroseconds(base::Time::kMicrosecondsPerSecond / 60);
+ scoped_refptr<FakeDelayBasedTimeSource> timeSource = FakeDelayBasedTimeSource::create(interval, &thread);
+ FrameRateController controller(timeSource);
+
+ controller.setClient(&client);
+ controller.setActive(true);
+ controller.setMaxFramesPending(2);
+
+ base::TimeTicks elapsed; // Muck around with time a bit
+
+ // Trigger one frame, make sure the vsync callback is called
+ elapsed += base::TimeDelta::FromMilliseconds(thread.pendingDelayMs());
+ timeSource->setNow(elapsed);
+ thread.runPendingTask();
+ EXPECT_TRUE(client.vsyncTicked());
+ client.reset();
+
+ // Tell the controller we drew
+ controller.didBeginFrame();
+
+ // Trigger another frame, make sure vsync callback runs again
+ elapsed += base::TimeDelta::FromMilliseconds(thread.pendingDelayMs());
+ EXPECT_TRUE(elapsed >= timeSource->now()); // Sanity check that previous code didn't move time backward.
+ timeSource->setNow(elapsed);
+ thread.runPendingTask();
+ EXPECT_TRUE(client.vsyncTicked());
+ client.reset();
+
+ // Tell the controller we drew, again.
+ controller.didBeginFrame();
+
+ // Trigger another frame. Since two frames are pending, we should not draw.
+ elapsed += base::TimeDelta::FromMilliseconds(thread.pendingDelayMs());
+ EXPECT_TRUE(elapsed >= timeSource->now()); // Sanity check that previous code didn't move time backward.
+ timeSource->setNow(elapsed);
+ thread.runPendingTask();
+ EXPECT_FALSE(client.vsyncTicked());
+
+ // Tell the controller the first frame ended 5ms later
+ timeSource->setNow(timeSource->now() + base::TimeDelta::FromMilliseconds(5));
+ controller.didFinishFrame();
+
+ // Tick should not have been called
+ EXPECT_FALSE(client.vsyncTicked());
+
+ // Trigger yet another frame. Since one frames is pending, another vsync callback should run.
+ elapsed += base::TimeDelta::FromMilliseconds(thread.pendingDelayMs());
+ EXPECT_TRUE(elapsed >= timeSource->now()); // Sanity check that previous code didn't move time backward.
+ timeSource->setNow(elapsed);
+ thread.runPendingTask();
+ EXPECT_TRUE(client.vsyncTicked());
+}
+
+TEST(FrameRateControllerTest, TestFrameThrottling_Unthrottled)
+{
+ FakeThread thread;
+ FakeFrameRateControllerClient client;
+ FrameRateController controller(&thread);
+
+ controller.setClient(&client);
+ controller.setMaxFramesPending(2);
+
+ // setActive triggers 1st frame, make sure the vsync callback is called
+ controller.setActive(true);
+ thread.runPendingTask();
+ EXPECT_TRUE(client.vsyncTicked());
+ client.reset();
+
+ // Even if we don't call didBeginFrame, FrameRateController should
+ // still attempt to vsync tick multiple times until it does result in
+ // a didBeginFrame.
+ thread.runPendingTask();
+ EXPECT_TRUE(client.vsyncTicked());
+ client.reset();
+
+ thread.runPendingTask();
+ EXPECT_TRUE(client.vsyncTicked());
+ client.reset();
+
+ // didBeginFrame triggers 2nd frame, make sure the vsync callback is called
+ controller.didBeginFrame();
+ thread.runPendingTask();
+ EXPECT_TRUE(client.vsyncTicked());
+ client.reset();
+
+ // didBeginFrame triggers 3rd frame (> maxFramesPending), make sure the vsync callback is NOT called
+ controller.didBeginFrame();
+ thread.runPendingTask();
+ EXPECT_FALSE(client.vsyncTicked());
+ client.reset();
+
+ // Make sure there is no pending task since we can't do anything until we receive a didFinishFrame anyway.
+ EXPECT_FALSE(thread.hasPendingTask());
+
+ // didFinishFrame triggers a frame, make sure the vsync callback is called
+ controller.didFinishFrame();
+ thread.runPendingTask();
+ EXPECT_TRUE(client.vsyncTicked());
+}
+
+} // namespace
+} // namespace cc
diff --git a/cc/scheduler/rate_limiter.cc b/cc/scheduler/rate_limiter.cc
new file mode 100644
index 0000000..df11acd
--- /dev/null
+++ b/cc/scheduler/rate_limiter.cc
@@ -0,0 +1,57 @@
+// Copyright 2011 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 "cc/scheduler/rate_limiter.h"
+
+#include "base/debug/trace_event.h"
+#include "cc/base/thread.h"
+#include "third_party/WebKit/Source/Platform/chromium/public/WebGraphicsContext3D.h"
+
+namespace cc {
+
+scoped_refptr<RateLimiter> RateLimiter::Create(
+ WebKit::WebGraphicsContext3D* context,
+ RateLimiterClient* client,
+ Thread* thread) {
+ return make_scoped_refptr(new RateLimiter(context, client, thread));
+}
+
+RateLimiter::RateLimiter(WebKit::WebGraphicsContext3D* context,
+ RateLimiterClient* client,
+ Thread* thread)
+ : thread_(thread),
+ context_(context),
+ active_(false),
+ client_(client) {
+ DCHECK(context);
+}
+
+RateLimiter::~RateLimiter() {}
+
+void RateLimiter::Start() {
+ if (active_)
+ return;
+
+ TRACE_EVENT0("cc", "RateLimiter::Start");
+ active_ = true;
+ thread_->PostTask(base::Bind(&RateLimiter::RateLimitContext, this));
+}
+
+void RateLimiter::Stop() {
+ TRACE_EVENT0("cc", "RateLimiter::Stop");
+ client_ = NULL;
+}
+
+void RateLimiter::RateLimitContext() {
+ if (!client_)
+ return;
+
+ TRACE_EVENT0("cc", "RateLimiter::RateLimitContext");
+
+ active_ = false;
+ client_->RateLimit();
+ context_->rateLimitOffscreenContextCHROMIUM();
+}
+
+} // namespace cc
diff --git a/cc/scheduler/rate_limiter.h b/cc/scheduler/rate_limiter.h
new file mode 100644
index 0000000..522528f
--- /dev/null
+++ b/cc/scheduler/rate_limiter.h
@@ -0,0 +1,57 @@
+// Copyright 2011 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 CC_SCHEDULER_RATE_LIMITER_H_
+#define CC_SCHEDULER_RATE_LIMITER_H_
+
+#include "base/memory/ref_counted.h"
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace cc {
+
+class Thread;
+
+class RateLimiterClient {
+ public:
+ virtual void RateLimit() = 0;
+};
+
+// A RateLimiter can be used to make sure that a single context does not
+// dominate all execution time. To use, construct a RateLimiter class around
+// the context and call Start() whenever calls are made on the context outside
+// of normal flow control. RateLimiter will block if the context is too far
+// ahead of the compositor.
+class RateLimiter : public base::RefCounted<RateLimiter> {
+ public:
+ static scoped_refptr<RateLimiter> Create(
+ WebKit::WebGraphicsContext3D* context,
+ RateLimiterClient* client,
+ Thread* thread);
+
+ void Start();
+
+ // Context and client will not be accessed after Stop().
+ void Stop();
+
+ private:
+ friend class base::RefCounted<RateLimiter>;
+
+ RateLimiter(WebKit::WebGraphicsContext3D* context,
+ RateLimiterClient* client,
+ Thread* thread);
+ ~RateLimiter();
+
+ void RateLimitContext();
+
+ WebKit::WebGraphicsContext3D* context_;
+ bool active_;
+ RateLimiterClient* client_;
+ Thread* thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(RateLimiter);
+};
+
+}
+#endif // CC_SCHEDULER_RATE_LIMITER_H_
diff --git a/cc/scheduler/scheduler.cc b/cc/scheduler/scheduler.cc
new file mode 100644
index 0000000..6a6f1f2
--- /dev/null
+++ b/cc/scheduler/scheduler.cc
@@ -0,0 +1,202 @@
+// Copyright 2011 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 "cc/scheduler/scheduler.h"
+
+#include "base/auto_reset.h"
+#include "base/debug/trace_event.h"
+#include "base/logging.h"
+
+namespace cc {
+
+Scheduler::Scheduler(SchedulerClient* client,
+ scoped_ptr<FrameRateController> frame_rate_controller,
+ const SchedulerSettings& scheduler_settings)
+ : settings_(scheduler_settings),
+ client_(client),
+ frame_rate_controller_(frame_rate_controller.Pass()),
+ state_machine_(scheduler_settings),
+ inside_process_scheduled_actions_(false) {
+ DCHECK(client_);
+ frame_rate_controller_->setClient(this);
+ DCHECK(!state_machine_.VSyncCallbackNeeded());
+}
+
+Scheduler::~Scheduler() { frame_rate_controller_->setActive(false); }
+
+void Scheduler::SetCanBeginFrame(bool can) {
+ state_machine_.SetCanBeginFrame(can);
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetVisible(bool visible) {
+ state_machine_.SetVisible(visible);
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetCanDraw(bool can_draw) {
+ state_machine_.SetCanDraw(can_draw);
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetHasPendingTree(bool has_pending_tree) {
+ state_machine_.SetHasPendingTree(has_pending_tree);
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetNeedsCommit() {
+ state_machine_.SetNeedsCommit();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetNeedsForcedCommit() {
+ state_machine_.SetNeedsCommit();
+ state_machine_.SetNeedsForcedCommit();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetNeedsRedraw() {
+ state_machine_.SetNeedsRedraw();
+ ProcessScheduledActions();
+}
+
+void Scheduler::DidSwapUseIncompleteTile() {
+ state_machine_.DidSwapUseIncompleteTile();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetNeedsForcedRedraw() {
+ state_machine_.SetNeedsForcedRedraw();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetMainThreadNeedsLayerTextures() {
+ state_machine_.SetMainThreadNeedsLayerTextures();
+ ProcessScheduledActions();
+}
+
+void Scheduler::BeginFrameComplete() {
+ TRACE_EVENT0("cc", "Scheduler::beginFrameComplete");
+ state_machine_.BeginFrameComplete();
+ ProcessScheduledActions();
+}
+
+void Scheduler::BeginFrameAborted() {
+ TRACE_EVENT0("cc", "Scheduler::beginFrameAborted");
+ state_machine_.BeginFrameAborted();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetMaxFramesPending(int max_frames_pending) {
+ frame_rate_controller_->setMaxFramesPending(max_frames_pending);
+}
+
+int Scheduler::MaxFramesPending() const {
+ return frame_rate_controller_->maxFramesPending();
+}
+
+void Scheduler::SetSwapBuffersCompleteSupported(bool supported) {
+ frame_rate_controller_->setSwapBuffersCompleteSupported(supported);
+}
+
+void Scheduler::DidSwapBuffersComplete() {
+ TRACE_EVENT0("cc", "Scheduler::didSwapBuffersComplete");
+ frame_rate_controller_->didFinishFrame();
+}
+
+void Scheduler::DidLoseOutputSurface() {
+ TRACE_EVENT0("cc", "Scheduler::didLoseOutputSurface");
+ frame_rate_controller_->didAbortAllPendingFrames();
+ state_machine_.DidLoseOutputSurface();
+ ProcessScheduledActions();
+}
+
+void Scheduler::DidRecreateOutputSurface() {
+ TRACE_EVENT0("cc", "Scheduler::didRecreateOutputSurface");
+ state_machine_.DidRecreateOutputSurface();
+ ProcessScheduledActions();
+}
+
+void Scheduler::SetTimebaseAndInterval(base::TimeTicks timebase,
+ base::TimeDelta interval) {
+ frame_rate_controller_->setTimebaseAndInterval(timebase, interval);
+}
+
+base::TimeTicks Scheduler::AnticipatedDrawTime() {
+ return frame_rate_controller_->nextTickTime();
+}
+
+base::TimeTicks Scheduler::LastVSyncTime() {
+ return frame_rate_controller_->lastTickTime();
+}
+
+void Scheduler::vsyncTick(bool throttled) {
+ TRACE_EVENT1("cc", "Scheduler::vsyncTick", "throttled", throttled);
+ if (!throttled)
+ state_machine_.DidEnterVSync();
+ ProcessScheduledActions();
+ if (!throttled)
+ state_machine_.DidLeaveVSync();
+}
+
+void Scheduler::ProcessScheduledActions() {
+ // We do not allow ProcessScheduledActions to be recursive.
+ // The top-level call will iteratively execute the next action for us anyway.
+ if (inside_process_scheduled_actions_)
+ return;
+
+ base::AutoReset<bool> mark_inside(&inside_process_scheduled_actions_, true);
+
+ SchedulerStateMachine::Action action = state_machine_.NextAction();
+ while (action != SchedulerStateMachine::ACTION_NONE) {
+ state_machine_.UpdateState(action);
+ TRACE_EVENT1(
+ "cc", "Scheduler::ProcessScheduledActions()", "action", action);
+
+ switch (action) {
+ case SchedulerStateMachine::ACTION_NONE:
+ break;
+ case SchedulerStateMachine::ACTION_BEGIN_FRAME:
+ client_->ScheduledActionBeginFrame();
+ break;
+ case SchedulerStateMachine::ACTION_COMMIT:
+ client_->ScheduledActionCommit();
+ break;
+ case SchedulerStateMachine::ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS:
+ client_->ScheduledActionCheckForCompletedTileUploads();
+ break;
+ case SchedulerStateMachine::ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED:
+ client_->ScheduledActionActivatePendingTreeIfNeeded();
+ break;
+ case SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE: {
+ ScheduledActionDrawAndSwapResult result =
+ client_->ScheduledActionDrawAndSwapIfPossible();
+ state_machine_.DidDrawIfPossibleCompleted(result.did_draw);
+ if (result.did_swap)
+ frame_rate_controller_->didBeginFrame();
+ break;
+ }
+ case SchedulerStateMachine::ACTION_DRAW_FORCED: {
+ ScheduledActionDrawAndSwapResult result =
+ client_->ScheduledActionDrawAndSwapForced();
+ if (result.did_swap)
+ frame_rate_controller_->didBeginFrame();
+ break;
+ }
+ case SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_RECREATION:
+ client_->ScheduledActionBeginContextRecreation();
+ break;
+ case SchedulerStateMachine::ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD:
+ client_->ScheduledActionAcquireLayerTexturesForMainThread();
+ break;
+ }
+ action = state_machine_.NextAction();
+ }
+
+ // Activate or deactivate the frame rate controller.
+ frame_rate_controller_->setActive(state_machine_.VSyncCallbackNeeded());
+ client_->DidAnticipatedDrawTimeChange(frame_rate_controller_->nextTickTime());
+}
+
+} // namespace cc
diff --git a/cc/scheduler/scheduler.h b/cc/scheduler/scheduler.h
new file mode 100644
index 0000000..c91f604
--- /dev/null
+++ b/cc/scheduler/scheduler.h
@@ -0,0 +1,127 @@
+// Copyright 2011 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 CC_SCHEDULER_SCHEDULER_H_
+#define CC_SCHEDULER_SCHEDULER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time.h"
+#include "cc/base/cc_export.h"
+#include "cc/scheduler/frame_rate_controller.h"
+#include "cc/scheduler/scheduler_settings.h"
+#include "cc/scheduler/scheduler_state_machine.h"
+#include "cc/trees/layer_tree_host.h"
+
+namespace cc {
+
+class Thread;
+
+struct ScheduledActionDrawAndSwapResult {
+ ScheduledActionDrawAndSwapResult()
+ : did_draw(false),
+ did_swap(false) {}
+ ScheduledActionDrawAndSwapResult(bool did_draw, bool did_swap)
+ : did_draw(did_draw),
+ did_swap(did_swap) {}
+ bool did_draw;
+ bool did_swap;
+};
+
+class SchedulerClient {
+ public:
+ virtual void ScheduledActionBeginFrame() = 0;
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapIfPossible() = 0;
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapForced() = 0;
+ virtual void ScheduledActionCommit() = 0;
+ virtual void ScheduledActionCheckForCompletedTileUploads() = 0;
+ virtual void ScheduledActionActivatePendingTreeIfNeeded() = 0;
+ virtual void ScheduledActionBeginContextRecreation() = 0;
+ virtual void ScheduledActionAcquireLayerTexturesForMainThread() = 0;
+ virtual void DidAnticipatedDrawTimeChange(base::TimeTicks time) = 0;
+
+ protected:
+ virtual ~SchedulerClient() {}
+};
+
+class CC_EXPORT Scheduler : FrameRateControllerClient {
+ public:
+ static scoped_ptr<Scheduler> Create(
+ SchedulerClient* client,
+ scoped_ptr<FrameRateController> frame_rate_controller,
+ const SchedulerSettings& scheduler_settings) {
+ return make_scoped_ptr(new Scheduler(
+ client, frame_rate_controller.Pass(), scheduler_settings));
+ }
+
+ virtual ~Scheduler();
+
+ void SetCanBeginFrame(bool can);
+
+ void SetVisible(bool visible);
+ void SetCanDraw(bool can_draw);
+ void SetHasPendingTree(bool has_pending_tree);
+
+ void SetNeedsCommit();
+
+ // Like SetNeedsCommit(), but ensures a commit will definitely happen even if
+ // we are not visible.
+ void SetNeedsForcedCommit();
+
+ void SetNeedsRedraw();
+
+ void SetMainThreadNeedsLayerTextures();
+
+ // Like SetNeedsRedraw(), but ensures the draw will definitely happen even if
+ // we are not visible.
+ void SetNeedsForcedRedraw();
+
+ void DidSwapUseIncompleteTile();
+
+ void BeginFrameComplete();
+ void BeginFrameAborted();
+
+ void SetMaxFramesPending(int max);
+ int MaxFramesPending() const;
+
+ void SetSwapBuffersCompleteSupported(bool supported);
+ void DidSwapBuffersComplete();
+
+ void DidLoseOutputSurface();
+ void DidRecreateOutputSurface();
+
+ bool CommitPending() const { return state_machine_.CommitPending(); }
+ bool RedrawPending() const { return state_machine_.RedrawPending(); }
+
+ void SetTimebaseAndInterval(base::TimeTicks timebase,
+ base::TimeDelta interval);
+
+ base::TimeTicks AnticipatedDrawTime();
+
+ base::TimeTicks LastVSyncTime();
+
+ // FrameRateControllerClient implementation
+ virtual void vsyncTick(bool throttled) OVERRIDE;
+
+ private:
+ Scheduler(SchedulerClient* client,
+ scoped_ptr<FrameRateController> frame_rate_controller,
+ const SchedulerSettings& scheduler_settings);
+
+ void ProcessScheduledActions();
+
+ const SchedulerSettings settings_;
+ SchedulerClient* client_;
+ scoped_ptr<FrameRateController> frame_rate_controller_;
+ SchedulerStateMachine state_machine_;
+ bool inside_process_scheduled_actions_;
+
+ DISALLOW_COPY_AND_ASSIGN(Scheduler);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_SCHEDULER_H_
diff --git a/cc/scheduler/scheduler_settings.cc b/cc/scheduler/scheduler_settings.cc
new file mode 100644
index 0000000..7c37276
--- /dev/null
+++ b/cc/scheduler/scheduler_settings.cc
@@ -0,0 +1,13 @@
+// Copyright 2013 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 "cc/scheduler/scheduler_settings.h"
+
+namespace cc {
+
+SchedulerSettings::SchedulerSettings() : impl_side_painting(false) {}
+
+SchedulerSettings::~SchedulerSettings() {}
+
+} // namespace cc
diff --git a/cc/scheduler/scheduler_settings.h b/cc/scheduler/scheduler_settings.h
new file mode 100644
index 0000000..d96b5a6
--- /dev/null
+++ b/cc/scheduler/scheduler_settings.h
@@ -0,0 +1,22 @@
+// Copyright 2013 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 CC_SCHEDULER_SCHEDULER_SETTINGS_H_
+#define CC_SCHEDULER_SCHEDULER_SETTINGS_H_
+
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class CC_EXPORT SchedulerSettings {
+ public:
+ SchedulerSettings();
+ ~SchedulerSettings();
+
+ bool impl_side_painting;
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_SCHEDULER_SETTINGS_H_
diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc
new file mode 100644
index 0000000..ce2742b
--- /dev/null
+++ b/cc/scheduler/scheduler_state_machine.cc
@@ -0,0 +1,433 @@
+// Copyright 2011 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 "cc/scheduler/scheduler_state_machine.h"
+
+#include "base/logging.h"
+#include "base/stringprintf.h"
+
+namespace cc {
+
+SchedulerStateMachine::SchedulerStateMachine(const SchedulerSettings& settings)
+ : settings_(settings),
+ commit_state_(COMMIT_STATE_IDLE),
+ current_frame_number_(0),
+ last_frame_number_where_draw_was_called_(-1),
+ last_frame_number_where_tree_activation_attempted_(-1),
+ last_frame_number_where_check_for_completed_tile_uploads_called_(-1),
+ consecutive_failed_draws_(0),
+ maximum_number_of_failed_draws_before_draw_is_forced_(3),
+ needs_redraw_(false),
+ swap_used_incomplete_tile_(false),
+ needs_forced_redraw_(false),
+ needs_forced_redraw_after_next_commit_(false),
+ needs_commit_(false),
+ needs_forced_commit_(false),
+ expect_immediate_begin_frame_(false),
+ main_thread_needs_layer_textures_(false),
+ inside_vsync_(false),
+ visible_(false),
+ can_begin_frame_(false),
+ can_draw_(false),
+ has_pending_tree_(false),
+ draw_if_possible_failed_(false),
+ texture_state_(LAYER_TEXTURE_STATE_UNLOCKED),
+ output_surface_state_(OUTPUT_SURFACE_ACTIVE) {}
+
+std::string SchedulerStateMachine::ToString() {
+ std::string str;
+ base::StringAppendF(&str,
+ "settings_.impl_side_painting = %d; ",
+ settings_.impl_side_painting);
+ base::StringAppendF(&str, "commit_state_ = %d; ", commit_state_);
+ base::StringAppendF(
+ &str, "current_frame_number_ = %d; ", current_frame_number_);
+ base::StringAppendF(&str,
+ "last_frame_number_where_draw_was_called_ = %d; ",
+ last_frame_number_where_draw_was_called_);
+ base::StringAppendF(
+ &str,
+ "last_frame_number_where_tree_activation_attempted_ = %d; ",
+ last_frame_number_where_tree_activation_attempted_);
+ base::StringAppendF(
+ &str,
+ "last_frame_number_where_check_for_completed_tile_uploads_called_ = %d; ",
+ last_frame_number_where_check_for_completed_tile_uploads_called_);
+ base::StringAppendF(
+ &str, "consecutive_failed_draws_ = %d; ", consecutive_failed_draws_);
+ base::StringAppendF(
+ &str,
+ "maximum_number_of_failed_draws_before_draw_is_forced_ = %d; ",
+ maximum_number_of_failed_draws_before_draw_is_forced_);
+ base::StringAppendF(&str, "needs_redraw_ = %d; ", needs_redraw_);
+ base::StringAppendF(
+ &str, "swap_used_incomplete_tile_ = %d; ", swap_used_incomplete_tile_);
+ base::StringAppendF(
+ &str, "needs_forced_redraw_ = %d; ", needs_forced_redraw_);
+ base::StringAppendF(&str,
+ "needs_forced_redraw_after_next_commit_ = %d; ",
+ needs_forced_redraw_after_next_commit_);
+ base::StringAppendF(&str, "needs_commit_ = %d; ", needs_commit_);
+ base::StringAppendF(
+ &str, "needs_forced_commit_ = %d; ", needs_forced_commit_);
+ base::StringAppendF(&str,
+ "expect_immediate_begin_frame_ = %d; ",
+ expect_immediate_begin_frame_);
+ base::StringAppendF(&str,
+ "main_thread_needs_layer_textures_ = %d; ",
+ main_thread_needs_layer_textures_);
+ base::StringAppendF(&str, "inside_vsync_ = %d; ", inside_vsync_);
+ base::StringAppendF(&str, "visible_ = %d; ", visible_);
+ base::StringAppendF(&str, "can_begin_frame_ = %d; ", can_begin_frame_);
+ base::StringAppendF(&str, "can_draw_ = %d; ", can_draw_);
+ base::StringAppendF(
+ &str, "draw_if_possible_failed_ = %d; ", draw_if_possible_failed_);
+ base::StringAppendF(&str, "has_pending_tree_ = %d; ", has_pending_tree_);
+ base::StringAppendF(&str, "texture_state_ = %d; ", texture_state_);
+ base::StringAppendF(
+ &str, "output_surface_state_ = %d; ", output_surface_state_);
+ return str;
+}
+
+bool SchedulerStateMachine::HasDrawnThisFrame() const {
+ return current_frame_number_ == last_frame_number_where_draw_was_called_;
+}
+
+bool SchedulerStateMachine::HasAttemptedTreeActivationThisFrame() const {
+ return current_frame_number_ ==
+ last_frame_number_where_tree_activation_attempted_;
+}
+
+bool SchedulerStateMachine::HasCheckedForCompletedTileUploadsThisFrame() const {
+ return current_frame_number_ ==
+ last_frame_number_where_check_for_completed_tile_uploads_called_;
+}
+
+bool SchedulerStateMachine::DrawSuspendedUntilCommit() const {
+ if (!can_draw_)
+ return true;
+ if (!visible_)
+ return true;
+ if (texture_state_ == LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD)
+ return true;
+ return false;
+}
+
+bool SchedulerStateMachine::ScheduledToDraw() const {
+ if (!needs_redraw_)
+ return false;
+ if (DrawSuspendedUntilCommit())
+ return false;
+ return true;
+}
+
+bool SchedulerStateMachine::ShouldDraw() const {
+ if (needs_forced_redraw_)
+ return true;
+
+ if (!ScheduledToDraw())
+ return false;
+ if (!inside_vsync_)
+ return false;
+ if (HasDrawnThisFrame())
+ return false;
+ if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE)
+ return false;
+ return true;
+}
+
+bool SchedulerStateMachine::ShouldAttemptTreeActivation() const {
+ return has_pending_tree_ && inside_vsync_ &&
+ !HasAttemptedTreeActivationThisFrame();
+}
+
+bool SchedulerStateMachine::ShouldCheckForCompletedTileUploads() const {
+ if (!settings_.impl_side_painting)
+ return false;
+ if (HasCheckedForCompletedTileUploadsThisFrame())
+ return false;
+
+ return ShouldAttemptTreeActivation() || ShouldDraw() ||
+ swap_used_incomplete_tile_;
+}
+
+bool SchedulerStateMachine::ShouldAcquireLayerTexturesForMainThread() const {
+ if (!main_thread_needs_layer_textures_)
+ return false;
+ if (texture_state_ == LAYER_TEXTURE_STATE_UNLOCKED)
+ return true;
+ DCHECK_EQ(texture_state_, LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD);
+ // Transfer the lock from impl thread to main thread immediately if the
+ // impl thread is not even scheduled to draw. Guards against deadlocking.
+ if (!ScheduledToDraw())
+ return true;
+ if (!VSyncCallbackNeeded())
+ return true;
+ return false;
+}
+
+SchedulerStateMachine::Action SchedulerStateMachine::NextAction() const {
+ if (ShouldAcquireLayerTexturesForMainThread())
+ return ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD;
+
+ switch (commit_state_) {
+ case COMMIT_STATE_IDLE:
+ if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE &&
+ needs_forced_redraw_)
+ return ACTION_DRAW_FORCED;
+ if (output_surface_state_ != OUTPUT_SURFACE_ACTIVE &&
+ needs_forced_commit_)
+ // TODO(enne): Should probably drop the active tree on force commit.
+ return has_pending_tree_ ? ACTION_NONE : ACTION_BEGIN_FRAME;
+ if (output_surface_state_ == OUTPUT_SURFACE_LOST)
+ return ACTION_BEGIN_OUTPUT_SURFACE_RECREATION;
+ if (output_surface_state_ == OUTPUT_SURFACE_RECREATING)
+ return ACTION_NONE;
+ if (ShouldCheckForCompletedTileUploads())
+ return ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS;
+ if (ShouldAttemptTreeActivation())
+ return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED;
+ if (ShouldDraw()) {
+ return needs_forced_redraw_ ? ACTION_DRAW_FORCED
+ : ACTION_DRAW_IF_POSSIBLE;
+ }
+ if (needs_commit_ &&
+ ((visible_ && can_begin_frame_) || needs_forced_commit_))
+ // TODO(enne): Should probably drop the active tree on force commit.
+ return has_pending_tree_ ? ACTION_NONE : ACTION_BEGIN_FRAME;
+ return ACTION_NONE;
+
+ case COMMIT_STATE_FRAME_IN_PROGRESS:
+ if (ShouldCheckForCompletedTileUploads())
+ return ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS;
+ if (ShouldAttemptTreeActivation())
+ return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED;
+ if (ShouldDraw()) {
+ return needs_forced_redraw_ ? ACTION_DRAW_FORCED
+ : ACTION_DRAW_IF_POSSIBLE;
+ }
+ return ACTION_NONE;
+
+ case COMMIT_STATE_READY_TO_COMMIT:
+ return ACTION_COMMIT;
+
+ case COMMIT_STATE_WAITING_FOR_FIRST_DRAW: {
+ if (ShouldCheckForCompletedTileUploads())
+ return ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS;
+ if (ShouldAttemptTreeActivation())
+ return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED;
+ if (ShouldDraw() || output_surface_state_ == OUTPUT_SURFACE_LOST) {
+ return needs_forced_redraw_ ? ACTION_DRAW_FORCED
+ : ACTION_DRAW_IF_POSSIBLE;
+ }
+ // COMMIT_STATE_WAITING_FOR_FIRST_DRAW wants to enforce a draw. If
+ // can_draw_ is false or textures are not available, proceed to the next
+ // step (similar as in COMMIT_STATE_IDLE).
+ bool can_commit = visible_ || needs_forced_commit_;
+ if (needs_commit_ && can_commit && DrawSuspendedUntilCommit())
+ return has_pending_tree_ ? ACTION_NONE : ACTION_BEGIN_FRAME;
+ return ACTION_NONE;
+ }
+
+ case COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW:
+ if (ShouldCheckForCompletedTileUploads())
+ return ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS;
+ if (ShouldAttemptTreeActivation())
+ return ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED;
+ if (needs_forced_redraw_)
+ return ACTION_DRAW_FORCED;
+ return ACTION_NONE;
+ }
+ NOTREACHED();
+ return ACTION_NONE;
+}
+
+void SchedulerStateMachine::UpdateState(Action action) {
+ switch (action) {
+ case ACTION_NONE:
+ return;
+
+ case ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS:
+ last_frame_number_where_check_for_completed_tile_uploads_called_ =
+ current_frame_number_;
+ return;
+
+ case ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED:
+ last_frame_number_where_tree_activation_attempted_ =
+ current_frame_number_;
+ return;
+
+ case ACTION_BEGIN_FRAME:
+ DCHECK(!has_pending_tree_);
+ DCHECK(visible_ || needs_forced_commit_);
+ commit_state_ = COMMIT_STATE_FRAME_IN_PROGRESS;
+ needs_commit_ = false;
+ needs_forced_commit_ = false;
+ return;
+
+ case ACTION_COMMIT:
+ if (expect_immediate_begin_frame_)
+ commit_state_ = COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW;
+ else
+ commit_state_ = COMMIT_STATE_WAITING_FOR_FIRST_DRAW;
+ // When impl-side painting, we draw on activation instead of on commit.
+ if (!settings_.impl_side_painting)
+ needs_redraw_ = true;
+ if (draw_if_possible_failed_)
+ last_frame_number_where_draw_was_called_ = -1;
+
+ if (needs_forced_redraw_after_next_commit_) {
+ needs_forced_redraw_after_next_commit_ = false;
+ needs_forced_redraw_ = true;
+ }
+
+ texture_state_ = LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD;
+ return;
+
+ case ACTION_DRAW_FORCED:
+ case ACTION_DRAW_IF_POSSIBLE:
+ needs_redraw_ = false;
+ needs_forced_redraw_ = false;
+ draw_if_possible_failed_ = false;
+ swap_used_incomplete_tile_ = false;
+ if (inside_vsync_)
+ last_frame_number_where_draw_was_called_ = current_frame_number_;
+ if (commit_state_ == COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW) {
+ DCHECK(expect_immediate_begin_frame_);
+ commit_state_ = COMMIT_STATE_FRAME_IN_PROGRESS;
+ expect_immediate_begin_frame_ = false;
+ } else if (commit_state_ == COMMIT_STATE_WAITING_FOR_FIRST_DRAW) {
+ commit_state_ = COMMIT_STATE_IDLE;
+ }
+ if (texture_state_ == LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD)
+ texture_state_ = LAYER_TEXTURE_STATE_UNLOCKED;
+ return;
+
+ case ACTION_BEGIN_OUTPUT_SURFACE_RECREATION:
+ DCHECK_EQ(commit_state_, COMMIT_STATE_IDLE);
+ DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_LOST);
+ output_surface_state_ = OUTPUT_SURFACE_RECREATING;
+ return;
+
+ case ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD:
+ texture_state_ = LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD;
+ main_thread_needs_layer_textures_ = false;
+ if (commit_state_ != COMMIT_STATE_FRAME_IN_PROGRESS)
+ needs_commit_ = true;
+ return;
+ }
+}
+
+void SchedulerStateMachine::SetMainThreadNeedsLayerTextures() {
+ DCHECK(!main_thread_needs_layer_textures_);
+ DCHECK_NE(texture_state_, LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD);
+ main_thread_needs_layer_textures_ = true;
+}
+
+bool SchedulerStateMachine::VSyncCallbackNeeded() const {
+ // If we have a pending tree, need to keep getting notifications until
+ // the tree is ready to be swapped.
+ if (has_pending_tree_)
+ return true;
+
+ // If we can't draw, don't tick until we are notified that we can draw again.
+ if (!can_draw_)
+ return false;
+
+ if (needs_forced_redraw_)
+ return true;
+
+ if (visible_ && swap_used_incomplete_tile_)
+ return true;
+
+ return needs_redraw_ && visible_ &&
+ output_surface_state_ == OUTPUT_SURFACE_ACTIVE;
+}
+
+void SchedulerStateMachine::DidEnterVSync() { inside_vsync_ = true; }
+
+void SchedulerStateMachine::DidLeaveVSync() {
+ current_frame_number_++;
+ inside_vsync_ = false;
+}
+
+void SchedulerStateMachine::SetVisible(bool visible) { visible_ = visible; }
+
+void SchedulerStateMachine::SetNeedsRedraw() { needs_redraw_ = true; }
+
+void SchedulerStateMachine::DidSwapUseIncompleteTile() {
+ swap_used_incomplete_tile_ = true;
+}
+
+void SchedulerStateMachine::SetNeedsForcedRedraw() {
+ needs_forced_redraw_ = true;
+}
+
+void SchedulerStateMachine::DidDrawIfPossibleCompleted(bool success) {
+ draw_if_possible_failed_ = !success;
+ if (draw_if_possible_failed_) {
+ needs_redraw_ = true;
+ needs_commit_ = true;
+ consecutive_failed_draws_++;
+ if (consecutive_failed_draws_ >=
+ maximum_number_of_failed_draws_before_draw_is_forced_) {
+ consecutive_failed_draws_ = 0;
+ // We need to force a draw, but it doesn't make sense to do this until
+ // we've committed and have new textures.
+ needs_forced_redraw_after_next_commit_ = true;
+ }
+ } else {
+ consecutive_failed_draws_ = 0;
+ }
+}
+
+void SchedulerStateMachine::SetNeedsCommit() { needs_commit_ = true; }
+
+void SchedulerStateMachine::SetNeedsForcedCommit() {
+ needs_forced_commit_ = true;
+ expect_immediate_begin_frame_ = true;
+}
+
+void SchedulerStateMachine::BeginFrameComplete() {
+ DCHECK(commit_state_ == COMMIT_STATE_FRAME_IN_PROGRESS ||
+ (expect_immediate_begin_frame_ && commit_state_ != COMMIT_STATE_IDLE))
+ << ToString();
+ commit_state_ = COMMIT_STATE_READY_TO_COMMIT;
+}
+
+void SchedulerStateMachine::BeginFrameAborted() {
+ DCHECK_EQ(commit_state_, COMMIT_STATE_FRAME_IN_PROGRESS);
+ if (expect_immediate_begin_frame_) {
+ expect_immediate_begin_frame_ = false;
+ } else {
+ commit_state_ = COMMIT_STATE_IDLE;
+ SetNeedsCommit();
+ }
+}
+
+void SchedulerStateMachine::DidLoseOutputSurface() {
+ if (output_surface_state_ == OUTPUT_SURFACE_LOST ||
+ output_surface_state_ == OUTPUT_SURFACE_RECREATING)
+ return;
+ output_surface_state_ = OUTPUT_SURFACE_LOST;
+}
+
+void SchedulerStateMachine::SetHasPendingTree(bool has_pending_tree) {
+ has_pending_tree_ = has_pending_tree;
+}
+
+void SchedulerStateMachine::SetCanDraw(bool can) { can_draw_ = can; }
+
+void SchedulerStateMachine::DidRecreateOutputSurface() {
+ DCHECK_EQ(output_surface_state_, OUTPUT_SURFACE_RECREATING);
+ output_surface_state_ = OUTPUT_SURFACE_ACTIVE;
+ SetNeedsCommit();
+}
+
+void SchedulerStateMachine::SetMaximumNumberOfFailedDrawsBeforeDrawIsForced(
+ int num_draws) {
+ maximum_number_of_failed_draws_before_draw_is_forced_ = num_draws;
+}
+
+} // namespace cc
diff --git a/cc/scheduler/scheduler_state_machine.h b/cc/scheduler/scheduler_state_machine.h
new file mode 100644
index 0000000..8471acc
--- /dev/null
+++ b/cc/scheduler/scheduler_state_machine.h
@@ -0,0 +1,193 @@
+// Copyright 2011 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 CC_SCHEDULER_SCHEDULER_STATE_MACHINE_H_
+#define CC_SCHEDULER_SCHEDULER_STATE_MACHINE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "cc/base/cc_export.h"
+#include "cc/scheduler/scheduler_settings.h"
+
+namespace cc {
+
+// The SchedulerStateMachine decides how to coordinate main thread activites
+// like painting/running javascript with rendering and input activities on the
+// impl thread.
+//
+// The state machine tracks internal state but is also influenced by external
+// state. Internal state includes things like whether a frame has been
+// requested, while external state includes things like the current time being
+// near to the vblank time.
+//
+// The scheduler seperates "what to do next" from the updating of its internal
+// state to make testing cleaner.
+class CC_EXPORT SchedulerStateMachine {
+ public:
+ // settings must be valid for the lifetime of this class.
+ SchedulerStateMachine(const SchedulerSettings& settings);
+
+ enum CommitState {
+ COMMIT_STATE_IDLE,
+ COMMIT_STATE_FRAME_IN_PROGRESS,
+ COMMIT_STATE_READY_TO_COMMIT,
+ COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ };
+
+ enum TextureState {
+ LAYER_TEXTURE_STATE_UNLOCKED,
+ LAYER_TEXTURE_STATE_ACQUIRED_BY_MAIN_THREAD,
+ LAYER_TEXTURE_STATE_ACQUIRED_BY_IMPL_THREAD,
+ };
+
+ enum OutputSurfaceState {
+ OUTPUT_SURFACE_ACTIVE,
+ OUTPUT_SURFACE_LOST,
+ OUTPUT_SURFACE_RECREATING,
+ };
+
+ bool CommitPending() const {
+ return commit_state_ == COMMIT_STATE_FRAME_IN_PROGRESS ||
+ commit_state_ == COMMIT_STATE_READY_TO_COMMIT;
+ }
+
+ bool RedrawPending() const { return needs_redraw_; }
+
+ enum Action {
+ ACTION_NONE,
+ ACTION_BEGIN_FRAME,
+ ACTION_COMMIT,
+ ACTION_CHECK_FOR_COMPLETED_TILE_UPLOADS,
+ ACTION_ACTIVATE_PENDING_TREE_IF_NEEDED,
+ ACTION_DRAW_IF_POSSIBLE,
+ ACTION_DRAW_FORCED,
+ ACTION_BEGIN_OUTPUT_SURFACE_RECREATION,
+ ACTION_ACQUIRE_LAYER_TEXTURES_FOR_MAIN_THREAD,
+ };
+ Action NextAction() const;
+ void UpdateState(Action action);
+
+ // Indicates whether the scheduler needs a vsync callback in order to make
+ // progress.
+ bool VSyncCallbackNeeded() const;
+
+ // Indicates that the system has entered and left a vsync callback.
+ // The scheduler will not draw more than once in a given vsync callback.
+ void DidEnterVSync();
+ void DidLeaveVSync();
+
+ // Indicates whether the LayerTreeHostImpl is visible.
+ void SetVisible(bool visible);
+
+ // Indicates that a redraw is required, either due to the impl tree changing
+ // or the screen being damaged and simply needing redisplay.
+ void SetNeedsRedraw();
+
+ // As SetNeedsRedraw(), but ensures the draw will definitely happen even if
+ // we are not visible.
+ void SetNeedsForcedRedraw();
+
+ // Indicates that a redraw is required because we are currently rendering
+ // with a low resolution or checkerboarded tile.
+ void DidSwapUseIncompleteTile();
+
+ // Indicates whether ACTION_DRAW_IF_POSSIBLE drew to the screen or not.
+ void DidDrawIfPossibleCompleted(bool success);
+
+ // Indicates that a new commit flow needs to be performed, either to pull
+ // updates from the main thread to the impl, or to push deltas from the impl
+ // thread to main.
+ void SetNeedsCommit();
+
+ // As SetNeedsCommit(), but ensures the beginFrame will definitely happen even
+ // if we are not visible. After this call we expect to go through the forced
+ // commit flow and then return to waiting for a non-forced beginFrame to
+ // finish.
+ void SetNeedsForcedCommit();
+
+ // Call this only in response to receiving an ACTION_BEGIN_FRAME
+ // from nextState. Indicates that all painting is complete.
+ void BeginFrameComplete();
+
+ // Call this only in response to receiving an ACTION_BEGIN_FRAME
+ // from nextState if the client rejects the beginFrame message.
+ void BeginFrameAborted();
+
+ // Request exclusive access to the textures that back single buffered
+ // layers on behalf of the main thread. Upon acquisition,
+ // ACTION_DRAW_IF_POSSIBLE will not draw until the main thread releases the
+ // textures to the impl thread by committing the layers.
+ void SetMainThreadNeedsLayerTextures();
+
+ // Indicates whether we can successfully begin a frame at this time.
+ void SetCanBeginFrame(bool can) { can_begin_frame_ = can; }
+
+ // Indicates whether drawing would, at this time, make sense.
+ // canDraw can be used to supress flashes or checkerboarding
+ // when such behavior would be undesirable.
+ void SetCanDraw(bool can);
+
+ // Indicates whether or not there is a pending tree. This influences
+ // whether or not we can succesfully commit at this time. If the
+ // last commit is still being processed (but not blocking), it may not
+ // be possible to take another commit yet. This overrides force commit,
+ // as a commit is already still in flight.
+ void SetHasPendingTree(bool has_pending_tree);
+ bool has_pending_tree() const { return has_pending_tree_; }
+
+ void DidLoseOutputSurface();
+ void DidRecreateOutputSurface();
+
+ // Exposed for testing purposes.
+ void SetMaximumNumberOfFailedDrawsBeforeDrawIsForced(int num_draws);
+
+ std::string ToString();
+
+ protected:
+ bool ShouldDrawForced() const;
+ bool DrawSuspendedUntilCommit() const;
+ bool ScheduledToDraw() const;
+ bool ShouldDraw() const;
+ bool ShouldAttemptTreeActivation() const;
+ bool ShouldAcquireLayerTexturesForMainThread() const;
+ bool ShouldCheckForCompletedTileUploads() const;
+ bool HasDrawnThisFrame() const;
+ bool HasAttemptedTreeActivationThisFrame() const;
+ bool HasCheckedForCompletedTileUploadsThisFrame() const;
+
+ const SchedulerSettings settings_;
+
+ CommitState commit_state_;
+
+ int current_frame_number_;
+ int last_frame_number_where_draw_was_called_;
+ int last_frame_number_where_tree_activation_attempted_;
+ int last_frame_number_where_check_for_completed_tile_uploads_called_;
+ int consecutive_failed_draws_;
+ int maximum_number_of_failed_draws_before_draw_is_forced_;
+ bool needs_redraw_;
+ bool swap_used_incomplete_tile_;
+ bool needs_forced_redraw_;
+ bool needs_forced_redraw_after_next_commit_;
+ bool needs_commit_;
+ bool needs_forced_commit_;
+ bool expect_immediate_begin_frame_;
+ bool main_thread_needs_layer_textures_;
+ bool inside_vsync_;
+ bool visible_;
+ bool can_begin_frame_;
+ bool can_draw_;
+ bool has_pending_tree_;
+ bool draw_if_possible_failed_;
+ TextureState texture_state_;
+ OutputSurfaceState output_surface_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(SchedulerStateMachine);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_SCHEDULER_STATE_MACHINE_H_
diff --git a/cc/scheduler/scheduler_state_machine_unittest.cc b/cc/scheduler/scheduler_state_machine_unittest.cc
new file mode 100644
index 0000000..cc26509
--- /dev/null
+++ b/cc/scheduler/scheduler_state_machine_unittest.cc
@@ -0,0 +1,1081 @@
+// Copyright 2011 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 "cc/scheduler/scheduler_state_machine.h"
+
+#include "cc/scheduler/scheduler.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+
+namespace {
+
+const SchedulerStateMachine::CommitState all_commit_states[] = {
+ SchedulerStateMachine::COMMIT_STATE_IDLE,
+ SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW
+};
+
+// Exposes the protected state fields of the SchedulerStateMachine for testing
+class StateMachine : public SchedulerStateMachine {
+ public:
+ StateMachine(const SchedulerSettings& scheduler_settings)
+ : SchedulerStateMachine(scheduler_settings) {}
+ void SetCommitState(CommitState cs) { commit_state_ = cs; }
+ CommitState CommitState() const { return commit_state_; }
+
+ bool NeedsCommit() const { return needs_commit_; }
+
+ void SetNeedsRedraw(bool b) { needs_redraw_ = b; }
+ bool NeedsRedraw() const { return needs_redraw_; }
+
+ void SetNeedsForcedRedraw(bool b) { needs_forced_redraw_ = b; }
+ bool NeedsForcedRedraw() const { return needs_forced_redraw_; }
+
+ bool CanDraw() const { return can_draw_; }
+ bool InsideVSync() const { return inside_vsync_; }
+ bool Visible() const { return visible_; }
+};
+
+TEST(SchedulerStateMachineTest, TestNextActionBeginsFrameIfNeeded) {
+ SchedulerSettings default_scheduler_settings;
+
+ // If no commit needed, do nothing.
+ {
+ StateMachine state(default_scheduler_settings);
+ state.SetCommitState(SchedulerStateMachine::COMMIT_STATE_IDLE);
+ state.SetCanBeginFrame(true);
+ state.SetNeedsRedraw(false);
+ state.SetVisible(true);
+
+ EXPECT_FALSE(state.VSyncCallbackNeeded());
+
+ state.DidLeaveVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ }
+
+ // If commit requested but canBeginFrame is still false, do nothing.
+ {
+ StateMachine state(default_scheduler_settings);
+ state.SetCommitState(SchedulerStateMachine::COMMIT_STATE_IDLE);
+ state.SetNeedsRedraw(false);
+ state.SetVisible(true);
+
+ EXPECT_FALSE(state.VSyncCallbackNeeded());
+
+ state.DidLeaveVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ }
+
+ // If commit requested, begin a frame.
+ {
+ StateMachine state(default_scheduler_settings);
+ state.SetCommitState(SchedulerStateMachine::COMMIT_STATE_IDLE);
+ state.SetCanBeginFrame(true);
+ state.SetNeedsRedraw(false);
+ state.SetVisible(true);
+ EXPECT_FALSE(state.VSyncCallbackNeeded());
+ }
+
+ // Begin the frame, make sure needsCommit and commitState update correctly.
+ {
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.UpdateState(SchedulerStateMachine::ACTION_BEGIN_FRAME);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+ EXPECT_FALSE(state.NeedsCommit());
+ EXPECT_FALSE(state.VSyncCallbackNeeded());
+ }
+}
+
+TEST(SchedulerStateMachineTest, TestSetForcedRedrawDoesNotSetsNormalRedraw) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanDraw(true);
+ state.SetNeedsForcedRedraw();
+ EXPECT_FALSE(state.RedrawPending());
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestFailedDrawSetsNeedsCommitAndDoesNotDrawAgain) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.RedrawPending());
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+
+ // We're drawing now.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.RedrawPending());
+ EXPECT_FALSE(state.CommitPending());
+
+ // Failing the draw makes us require a commit.
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_BEGIN_FRAME);
+ EXPECT_TRUE(state.RedrawPending());
+ EXPECT_TRUE(state.CommitPending());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestsetNeedsRedrawDuringFailedDrawDoesNotRemoveNeedsRedraw) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.RedrawPending());
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+
+ // We're drawing now.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.RedrawPending());
+ EXPECT_FALSE(state.CommitPending());
+
+ // While still in the same vsync callback, set needs redraw again.
+ // This should not redraw.
+ state.SetNeedsRedraw();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Failing the draw makes us require a commit.
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+ EXPECT_TRUE(state.RedrawPending());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestCommitAfterFailedDrawAllowsDrawInSameFrame) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start a commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_BEGIN_FRAME);
+ EXPECT_TRUE(state.CommitPending());
+
+ // Then initiate a draw.
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ EXPECT_TRUE(state.RedrawPending());
+
+ // Fail the draw.
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_TRUE(state.RedrawPending());
+ // But the commit is ongoing.
+ EXPECT_TRUE(state.CommitPending());
+
+ // Finish the commit.
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_TRUE(state.RedrawPending());
+
+ // And we should be allowed to draw again.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestCommitAfterFailedAndSuccessfulDrawDoesNotAllowDrawInSameFrame) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start a commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_BEGIN_FRAME);
+ EXPECT_TRUE(state.CommitPending());
+
+ // Then initiate a draw.
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ EXPECT_TRUE(state.RedrawPending());
+
+ // Fail the draw.
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_TRUE(state.RedrawPending());
+ // But the commit is ongoing.
+ EXPECT_TRUE(state.CommitPending());
+
+ // Force a draw.
+ state.SetNeedsForcedRedraw();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+
+ // Do the forced draw.
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_FORCED);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ EXPECT_FALSE(state.RedrawPending());
+ // And the commit is still ongoing.
+ EXPECT_TRUE(state.CommitPending());
+
+ // Finish the commit.
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_TRUE(state.RedrawPending());
+
+ // And we should not be allowed to draw again in the same frame..
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestFailedDrawsWillEventuallyForceADrawAfterTheNextCommit) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetMaximumNumberOfFailedDrawsBeforeDrawIsForced(1);
+
+ // Start a commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_BEGIN_FRAME);
+ EXPECT_TRUE(state.CommitPending());
+
+ // Then initiate a draw.
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ EXPECT_TRUE(state.RedrawPending());
+
+ // Fail the draw.
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_TRUE(state.RedrawPending());
+ // But the commit is ongoing.
+ EXPECT_TRUE(state.CommitPending());
+
+ // Finish the commit. Note, we should not yet be forcing a draw, but should
+ // continue the commit as usual.
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_TRUE(state.RedrawPending());
+
+ // The redraw should be forced in this case.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestFailedDrawIsRetriedNextVSync) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start a draw.
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ EXPECT_TRUE(state.RedrawPending());
+
+ // Fail the draw.
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.DidDrawIfPossibleCompleted(false);
+ EXPECT_TRUE(state.RedrawPending());
+
+ // We should not be trying to draw again now, but we have a commit pending.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+
+ state.DidLeaveVSync();
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+
+ // We should try draw again in the next vsync.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestDoestDrawTwiceInSameFrame) {
+ SchedulerSettings default_scheduler_settings;
+ SchedulerStateMachine state(default_scheduler_settings);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetNeedsRedraw();
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+
+ // While still in the same vsync callback, set needs redraw again.
+ // This should not redraw.
+ state.SetNeedsRedraw();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Move to another frame. This should now draw.
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveVSync();
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ state.DidEnterVSync();
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ state.DidDrawIfPossibleCompleted(true);
+ EXPECT_FALSE(state.VSyncCallbackNeeded());
+}
+
+TEST(SchedulerStateMachineTest, TestNextActionDrawsOnVSync) {
+ SchedulerSettings default_scheduler_settings;
+
+ // When not on vsync, or on vsync but not visible, don't draw.
+ size_t num_commit_states =
+ sizeof(all_commit_states) / sizeof(SchedulerStateMachine::CommitState);
+ for (size_t i = 0; i < num_commit_states; ++i) {
+ for (size_t j = 0; j < 2; ++j) {
+ StateMachine state(default_scheduler_settings);
+ state.SetCommitState(all_commit_states[i]);
+ bool visible = j;
+ if (!visible) {
+ state.DidEnterVSync();
+ state.SetVisible(false);
+ } else {
+ state.SetVisible(true);
+ }
+
+ // Case 1: needsCommit=false
+ EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE,
+ state.NextAction());
+
+ // Case 2: needsCommit=true
+ state.SetNeedsCommit();
+ EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE,
+ state.NextAction());
+ }
+ }
+
+ // When on vsync, or not on vsync but needsForcedRedraw set, should always
+ // draw except if you're ready to commit, in which case commit.
+ for (size_t i = 0; i < num_commit_states; ++i) {
+ for (size_t j = 0; j < 2; ++j) {
+ StateMachine state(default_scheduler_settings);
+ state.SetCanDraw(true);
+ state.SetCommitState(all_commit_states[i]);
+ bool forced_draw = j;
+ if (!forced_draw) {
+ state.DidEnterVSync();
+ state.SetNeedsRedraw(true);
+ state.SetVisible(true);
+ } else {
+ state.SetNeedsForcedRedraw(true);
+ }
+
+ SchedulerStateMachine::Action expected_action;
+ if (all_commit_states[i] !=
+ SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT) {
+ expected_action =
+ forced_draw ? SchedulerStateMachine::ACTION_DRAW_FORCED
+ : SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE;
+ } else {
+ expected_action = SchedulerStateMachine::ACTION_COMMIT;
+ }
+
+ // Case 1: needsCommit=false.
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ EXPECT_EQ(expected_action, state.NextAction());
+
+ // Case 2: needsCommit=true.
+ state.SetNeedsCommit();
+ EXPECT_TRUE(state.VSyncCallbackNeeded());
+ EXPECT_EQ(expected_action, state.NextAction());
+ }
+ }
+}
+
+TEST(SchedulerStateMachineTest, TestNoCommitStatesRedrawWhenInvisible) {
+ SchedulerSettings default_scheduler_settings;
+
+ size_t num_commit_states =
+ sizeof(all_commit_states) / sizeof(SchedulerStateMachine::CommitState);
+ for (size_t i = 0; i < num_commit_states; ++i) {
+ // There shouldn't be any drawing regardless of vsync.
+ for (size_t j = 0; j < 2; ++j) {
+ StateMachine state(default_scheduler_settings);
+ state.SetCommitState(all_commit_states[i]);
+ state.SetVisible(false);
+ state.SetNeedsRedraw(true);
+ state.SetNeedsForcedRedraw(false);
+ if (j == 1)
+ state.DidEnterVSync();
+
+ // Case 1: needsCommit=false.
+ EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE,
+ state.NextAction());
+
+ // Case 2: needsCommit=true.
+ state.SetNeedsCommit();
+ EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE,
+ state.NextAction());
+ }
+ }
+}
+
+TEST(SchedulerStateMachineTest, TestCanRedraw_StopsDraw) {
+ SchedulerSettings default_scheduler_settings;
+
+ size_t num_commit_states =
+ sizeof(all_commit_states) / sizeof(SchedulerStateMachine::CommitState);
+ for (size_t i = 0; i < num_commit_states; ++i) {
+ // There shouldn't be any drawing regardless of vsync.
+ for (size_t j = 0; j < 2; ++j) {
+ StateMachine state(default_scheduler_settings);
+ state.SetCommitState(all_commit_states[i]);
+ state.SetVisible(false);
+ state.SetNeedsRedraw(true);
+ state.SetNeedsForcedRedraw(false);
+ if (j == 1)
+ state.DidEnterVSync();
+
+ state.SetCanDraw(false);
+ EXPECT_NE(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE,
+ state.NextAction());
+ }
+ }
+}
+
+TEST(SchedulerStateMachineTest,
+ TestCanRedrawWithWaitingForFirstDrawMakesProgress) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCommitState(
+ SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW);
+ state.SetCanBeginFrame(true);
+ state.SetNeedsCommit();
+ state.SetNeedsRedraw(true);
+ state.SetVisible(true);
+ state.SetCanDraw(false);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestsetNeedsCommitIsNotLost) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetNeedsCommit();
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Begin the frame.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+ state.UpdateState(state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+
+ // Now, while the frame is in progress, set another commit.
+ state.SetNeedsCommit();
+ EXPECT_TRUE(state.NeedsCommit());
+
+ // Let the frame finish.
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+
+ // Expect to commit regardless of vsync state.
+ state.DidLeaveVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+
+ // Commit and make sure we draw on next vsync
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ state.DidDrawIfPossibleCompleted(true);
+
+ // Verify that another commit will begin.
+ state.DidLeaveVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestFullCycle) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start clean and set commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+
+ // Begin the frame.
+ state.UpdateState(SchedulerStateMachine::ACTION_BEGIN_FRAME);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+ EXPECT_FALSE(state.NeedsCommit());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Tell the scheduler the frame finished.
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+
+ // Commit.
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+ EXPECT_TRUE(state.NeedsRedraw());
+
+ // Expect to do nothing until vsync.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // At vsync, draw.
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveVSync();
+
+ // Should be synchronized, no draw needed, no action needed.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_FALSE(state.NeedsRedraw());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestFullCycleWithCommitRequestInbetween) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start clean and set commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+
+ // Begin the frame.
+ state.UpdateState(SchedulerStateMachine::ACTION_BEGIN_FRAME);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+ EXPECT_FALSE(state.NeedsCommit());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Request another commit while the commit is in flight.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Tell the scheduler the frame finished.
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+
+ // Commit.
+ state.UpdateState(SchedulerStateMachine::ACTION_COMMIT);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+ EXPECT_TRUE(state.NeedsRedraw());
+
+ // Expect to do nothing until vsync.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // At vsync, draw.
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE);
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveVSync();
+
+ // Should be synchronized, no draw needed, no action needed.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_FALSE(state.NeedsRedraw());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestRequestCommitInvisible) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestGoesInvisibleBeforeBeginFrameCompletes) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start clean and set commit.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+
+ // Begin the frame while visible.
+ state.UpdateState(SchedulerStateMachine::ACTION_BEGIN_FRAME);
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+ EXPECT_FALSE(state.NeedsCommit());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Become invisible and abort the beginFrame.
+ state.SetVisible(false);
+ state.BeginFrameAborted();
+
+ // We should now be back in the idle state as if we didn't start a frame at
+ // all.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Become visible again.
+ state.SetVisible(true);
+
+ // We should be beginning a frame now.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+
+ // Begin the frame.
+ state.UpdateState(state.NextAction());
+
+ // We should be starting the commit now.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+}
+
+TEST(SchedulerStateMachineTest, TestContextLostWhenCompletelyIdle) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ state.DidLoseOutputSurface();
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_RECREATION,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Once context recreation begins, nothing should happen.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Recreate the context.
+ state.DidRecreateOutputSurface();
+
+ // When the context is recreated, we should begin a commit.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+ state.UpdateState(state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestContextLostWhenIdleAndCommitRequestedWhileRecreating) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ state.DidLoseOutputSurface();
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_RECREATION,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Once context recreation begins, nothing should happen.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // While context is recreating, commits shouldn't begin.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Recreate the context
+ state.DidRecreateOutputSurface();
+
+ // When the context is recreated, we should begin a commit
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Once the context is recreated, whether we draw should be based on
+ // setCanDraw.
+ state.SetNeedsRedraw(true);
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.SetCanDraw(false);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.SetCanDraw(true);
+ state.DidLeaveVSync();
+}
+
+TEST(SchedulerStateMachineTest, TestContextLostWhileCommitInProgress) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Get a commit in flight.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Set damage and expect a draw.
+ state.SetNeedsRedraw(true);
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidLeaveVSync();
+
+ // Cause a lost context while the begin frame is in flight.
+ state.DidLoseOutputSurface();
+
+ // Ask for another draw. Expect nothing happens.
+ state.SetNeedsRedraw(true);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Finish the frame, and commit.
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Expect to be told to begin context recreation, independent of vsync state.
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_RECREATION,
+ state.NextAction());
+ state.DidLeaveVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_RECREATION,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestContextLostWhileCommitInProgressAndAnotherCommitRequested) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Get a commit in flight.
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Set damage and expect a draw.
+ state.SetNeedsRedraw(true);
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidLeaveVSync();
+
+ // Cause a lost context while the begin frame is in flight.
+ state.DidLoseOutputSurface();
+
+ // Ask for another draw and also set needs commit. Expect nothing happens.
+ state.SetNeedsRedraw(true);
+ state.SetNeedsCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+
+ // Finish the frame, and commit.
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_IF_POSSIBLE, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Expect to be told to begin context recreation, independent of vsync state
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_RECREATION,
+ state.NextAction());
+ state.DidLeaveVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_RECREATION,
+ state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestFinishAllRenderingWhileContextLost) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Cause a lost context lost.
+ state.DidLoseOutputSurface();
+
+ // Ask a forced redraw and verify it ocurrs.
+ state.SetNeedsForcedRedraw(true);
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.DidLeaveVSync();
+
+ // Clear the forced redraw bit.
+ state.SetNeedsForcedRedraw(false);
+
+ // Expect to be told to begin context recreation, independent of vsync state
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_OUTPUT_SURFACE_RECREATION,
+ state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ // Ask a forced redraw and verify it ocurrs.
+ state.SetNeedsForcedRedraw(true);
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.DidLeaveVSync();
+}
+
+TEST(SchedulerStateMachineTest, TestBeginFrameWhenInvisibleAndForceCommit) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(false);
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest,
+ TestBeginFrameWhenCanBeginFrameFalseAndForceCommit) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestBeginFrameWhenCommitInProgress) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(false);
+ state.SetCommitState(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS);
+ state.SetNeedsCommit();
+
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_DRAW,
+ state.CommitState());
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestBeginFrameWhenForcedCommitInProgress) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(false);
+ state.SetCommitState(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS);
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ state.CommitState());
+
+ // If we are waiting for forced draw then we know a begin frame is already in
+ // flight.
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestBeginFrameWhenContextLost) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ state.DidLoseOutputSurface();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_BEGIN_FRAME, state.NextAction());
+}
+
+TEST(SchedulerStateMachineTest, TestImmediateBeginFrame) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Schedule a forced frame, commit it, draw it.
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ state.UpdateState(state.NextAction());
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ state.CommitState());
+
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.SetNeedsForcedRedraw(true);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveVSync();
+
+ // Should be waiting for the normal begin frame.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState());
+}
+
+TEST(SchedulerStateMachineTest, TestImmediateBeginFrameDuringCommit) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ // Start a normal commit.
+ state.SetNeedsCommit();
+ state.UpdateState(state.NextAction());
+
+ // Schedule a forced frame, commit it, draw it.
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ state.UpdateState(state.NextAction());
+ state.BeginFrameComplete();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ state.CommitState());
+
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.SetNeedsForcedRedraw(true);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveVSync();
+
+ // Should be waiting for the normal begin frame.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState()) << state.ToString();
+}
+
+TEST(SchedulerStateMachineTest, ImmediateBeginFrameWhileInvisible) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(true);
+
+ state.SetNeedsCommit();
+ state.UpdateState(state.NextAction());
+
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ state.UpdateState(state.NextAction());
+ state.BeginFrameComplete();
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ state.CommitState());
+
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.SetNeedsForcedRedraw(true);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveVSync();
+
+ // Should be waiting for the normal begin frame.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_FRAME_IN_PROGRESS,
+ state.CommitState()) << state.ToString();
+
+ // Become invisible and abort the "normal" begin frame.
+ state.SetVisible(false);
+ state.BeginFrameAborted();
+
+ // Should be back in the idle state, but needing a commit.
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_IDLE, state.CommitState());
+ EXPECT_TRUE(state.NeedsCommit());
+}
+
+TEST(SchedulerStateMachineTest, ImmediateBeginFrameWhileCantDraw) {
+ SchedulerSettings default_scheduler_settings;
+ StateMachine state(default_scheduler_settings);
+ state.SetCanBeginFrame(true);
+ state.SetVisible(true);
+ state.SetCanDraw(false);
+
+ state.SetNeedsCommit();
+ state.UpdateState(state.NextAction());
+
+ state.SetNeedsCommit();
+ state.SetNeedsForcedCommit();
+ state.UpdateState(state.NextAction());
+ state.BeginFrameComplete();
+
+ EXPECT_EQ(SchedulerStateMachine::ACTION_COMMIT, state.NextAction());
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_READY_TO_COMMIT,
+ state.CommitState());
+ state.UpdateState(state.NextAction());
+
+ EXPECT_EQ(SchedulerStateMachine::COMMIT_STATE_WAITING_FOR_FIRST_FORCED_DRAW,
+ state.CommitState());
+
+ state.DidEnterVSync();
+ EXPECT_EQ(SchedulerStateMachine::ACTION_NONE, state.NextAction());
+ state.SetNeedsForcedRedraw(true);
+ EXPECT_EQ(SchedulerStateMachine::ACTION_DRAW_FORCED, state.NextAction());
+ state.UpdateState(state.NextAction());
+ state.DidDrawIfPossibleCompleted(true);
+ state.DidLeaveVSync();
+}
+
+} // namespace
+} // namespace cc
diff --git a/cc/scheduler/scheduler_unittest.cc b/cc/scheduler/scheduler_unittest.cc
new file mode 100644
index 0000000..689343e
--- /dev/null
+++ b/cc/scheduler/scheduler_unittest.cc
@@ -0,0 +1,520 @@
+// Copyright 2011 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 "cc/scheduler/scheduler.h"
+
+#include "base/logging.h"
+#include "cc/test/scheduler_test_common.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class FakeSchedulerClient : public SchedulerClient {
+ public:
+ FakeSchedulerClient() { Reset(); }
+ void Reset() {
+ actions_.clear();
+ draw_will_happen_ = true;
+ swap_will_happen_if_draw_happens_ = true;
+ num_draws_ = 0;
+ }
+
+ int num_draws() const { return num_draws_; }
+ int num_actions_() const { return static_cast<int>(actions_.size()); }
+ const char* Action(int i) const { return actions_[i]; }
+
+ bool HasAction(const char* action) const {
+ for (size_t i = 0; i < actions_.size(); i++)
+ if (!strcmp(actions_[i], action))
+ return true;
+ return false;
+ }
+
+ void SetDrawWillHappen(bool draw_will_happen) {
+ draw_will_happen_ = draw_will_happen;
+ }
+ void SetSwapWillHappenIfDrawHappens(bool swap_will_happen_if_draw_happens) {
+ swap_will_happen_if_draw_happens_ = swap_will_happen_if_draw_happens;
+ }
+
+ // Scheduler Implementation.
+ virtual void ScheduledActionBeginFrame() OVERRIDE {
+ actions_.push_back("ScheduledActionBeginFrame");
+ }
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapIfPossible() OVERRIDE {
+ actions_.push_back("ScheduledActionDrawAndSwapIfPossible");
+ num_draws_++;
+ return ScheduledActionDrawAndSwapResult(draw_will_happen_,
+ draw_will_happen_ &&
+ swap_will_happen_if_draw_happens_);
+ }
+ virtual ScheduledActionDrawAndSwapResult ScheduledActionDrawAndSwapForced()
+ OVERRIDE {
+ actions_.push_back("ScheduledActionDrawAndSwapForced");
+ return ScheduledActionDrawAndSwapResult(true,
+ swap_will_happen_if_draw_happens_);
+ }
+ virtual void ScheduledActionCommit() OVERRIDE {
+ actions_.push_back("ScheduledActionCommit");
+ }
+ virtual void ScheduledActionCheckForCompletedTileUploads() OVERRIDE {
+ actions_.push_back("ScheduledActionCheckForCompletedTileUploads");
+ }
+ virtual void ScheduledActionActivatePendingTreeIfNeeded() OVERRIDE {
+ actions_.push_back("ScheduledActionActivatePendingTreeIfNeeded");
+ }
+ virtual void ScheduledActionBeginContextRecreation() OVERRIDE {
+ actions_.push_back("ScheduledActionBeginContextRecreation");
+ }
+ virtual void ScheduledActionAcquireLayerTexturesForMainThread() OVERRIDE {
+ actions_.push_back("ScheduledActionAcquireLayerTexturesForMainThread");
+ }
+ virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {}
+
+ protected:
+ bool draw_will_happen_;
+ bool swap_will_happen_if_draw_happens_;
+ int num_draws_;
+ std::vector<const char*> actions_;
+};
+
+TEST(SchedulerTest, RequestCommit) {
+ FakeSchedulerClient client;
+ scoped_refptr<FakeTimeSource> time_source(new FakeTimeSource());
+ SchedulerSettings default_scheduler_settings;
+ scoped_ptr<Scheduler> scheduler =
+ Scheduler::Create(&client,
+ make_scoped_ptr(new FrameRateController(time_source)),
+ default_scheduler_settings);
+ scheduler->SetCanBeginFrame(true);
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ // SetNeedsCommit should begin the frame.
+ scheduler->SetNeedsCommit();
+ EXPECT_EQ(1, client.num_actions_());
+ EXPECT_STREQ("ScheduledActionBeginFrame", client.Action(0));
+ EXPECT_FALSE(time_source->active());
+ client.Reset();
+
+ // BeginFrameComplete should commit
+ scheduler->BeginFrameComplete();
+ EXPECT_EQ(1, client.num_actions_());
+ EXPECT_STREQ("ScheduledActionCommit", client.Action(0));
+ EXPECT_TRUE(time_source->active());
+ client.Reset();
+
+ // Tick should draw.
+ time_source->tick();
+ EXPECT_EQ(1, client.num_actions_());
+ EXPECT_STREQ("ScheduledActionDrawAndSwapIfPossible", client.Action(0));
+ EXPECT_FALSE(time_source->active());
+ client.Reset();
+
+ // Timer should be off.
+ EXPECT_FALSE(time_source->active());
+}
+
+TEST(SchedulerTest, RequestCommitAfterBeginFrame) {
+ FakeSchedulerClient client;
+ scoped_refptr<FakeTimeSource> time_source(new FakeTimeSource());
+ SchedulerSettings default_scheduler_settings;
+ scoped_ptr<Scheduler> scheduler =
+ Scheduler::Create(&client,
+ make_scoped_ptr(new FrameRateController(time_source)),
+ default_scheduler_settings);
+ scheduler->SetCanBeginFrame(true);
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ // SetNedsCommit should begin the frame.
+ scheduler->SetNeedsCommit();
+ EXPECT_EQ(1, client.num_actions_());
+ EXPECT_STREQ("ScheduledActionBeginFrame", client.Action(0));
+ client.Reset();
+
+ // Now SetNeedsCommit again. Calling here means we need a second frame.
+ scheduler->SetNeedsCommit();
+
+ // Since, another commit is needed, BeginFrameComplete should commit,
+ // then begin another frame.
+ scheduler->BeginFrameComplete();
+ EXPECT_EQ(1, client.num_actions_());
+ EXPECT_STREQ("ScheduledActionCommit", client.Action(0));
+ client.Reset();
+
+ // Tick should draw but then begin another frame.
+ time_source->tick();
+ EXPECT_FALSE(time_source->active());
+ EXPECT_EQ(2, client.num_actions_());
+ EXPECT_STREQ("ScheduledActionDrawAndSwapIfPossible", client.Action(0));
+ EXPECT_STREQ("ScheduledActionBeginFrame", client.Action(1));
+ client.Reset();
+}
+
+TEST(SchedulerTest, TextureAcquisitionCollision) {
+ FakeSchedulerClient client;
+ scoped_refptr<FakeTimeSource> time_source(new FakeTimeSource());
+ SchedulerSettings default_scheduler_settings;
+ scoped_ptr<Scheduler> scheduler =
+ Scheduler::Create(&client,
+ make_scoped_ptr(new FrameRateController(time_source)),
+ default_scheduler_settings);
+ scheduler->SetCanBeginFrame(true);
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ scheduler->SetNeedsCommit();
+ scheduler->SetMainThreadNeedsLayerTextures();
+ EXPECT_EQ(2, client.num_actions_());
+ EXPECT_STREQ("ScheduledActionBeginFrame", client.Action(0));
+ EXPECT_STREQ("ScheduledActionAcquireLayerTexturesForMainThread",
+ client.Action(1));
+ client.Reset();
+
+ // Compositor not scheduled to draw because textures are locked by main thread
+ EXPECT_FALSE(time_source->active());
+
+ // Trigger the commit
+ scheduler->BeginFrameComplete();
+ EXPECT_TRUE(time_source->active());
+ client.Reset();
+
+ // Between commit and draw, texture acquisition for main thread delayed,
+ // and main thread blocks.
+ scheduler->SetMainThreadNeedsLayerTextures();
+ EXPECT_EQ(0, client.num_actions_());
+ client.Reset();
+
+ // Once compositor draw complete, the delayed texture acquisition fires.
+ time_source->tick();
+ EXPECT_EQ(3, client.num_actions_());
+ EXPECT_STREQ("ScheduledActionDrawAndSwapIfPossible", client.Action(0));
+ EXPECT_STREQ("ScheduledActionAcquireLayerTexturesForMainThread",
+ client.Action(1));
+ EXPECT_STREQ("ScheduledActionBeginFrame", client.Action(2));
+ client.Reset();
+}
+
+TEST(SchedulerTest, VisibilitySwitchWithTextureAcquisition) {
+ FakeSchedulerClient client;
+ scoped_refptr<FakeTimeSource> time_source(new FakeTimeSource());
+ SchedulerSettings default_scheduler_settings;
+ scoped_ptr<Scheduler> scheduler =
+ Scheduler::Create(&client,
+ make_scoped_ptr(new FrameRateController(time_source)),
+ default_scheduler_settings);
+ scheduler->SetCanBeginFrame(true);
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ scheduler->SetNeedsCommit();
+ scheduler->BeginFrameComplete();
+ scheduler->SetMainThreadNeedsLayerTextures();
+ client.Reset();
+ // Verify that pending texture acquisition fires when visibility
+ // is lost in order to avoid a deadlock.
+ scheduler->SetVisible(false);
+ EXPECT_EQ(1, client.num_actions_());
+ EXPECT_STREQ("ScheduledActionAcquireLayerTexturesForMainThread",
+ client.Action(0));
+ client.Reset();
+
+ // Regaining visibility with textures acquired by main thread while
+ // compositor is waiting for first draw should result in a request
+ // for a new frame in order to escape a deadlock.
+ scheduler->SetVisible(true);
+ EXPECT_EQ(1, client.num_actions_());
+ EXPECT_STREQ("ScheduledActionBeginFrame", client.Action(0));
+ client.Reset();
+}
+
+class SchedulerClientThatsetNeedsDrawInsideDraw : public FakeSchedulerClient {
+ public:
+ SchedulerClientThatsetNeedsDrawInsideDraw() : scheduler_(NULL) {}
+
+ void SetScheduler(Scheduler* scheduler) { scheduler_ = scheduler; }
+
+ virtual void ScheduledActionBeginFrame() OVERRIDE {}
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapIfPossible() OVERRIDE {
+ // Only SetNeedsRedraw the first time this is called
+ if (!num_draws_)
+ scheduler_->SetNeedsRedraw();
+ return FakeSchedulerClient::ScheduledActionDrawAndSwapIfPossible();
+ }
+
+ virtual ScheduledActionDrawAndSwapResult ScheduledActionDrawAndSwapForced()
+ OVERRIDE {
+ NOTREACHED();
+ return ScheduledActionDrawAndSwapResult(true, true);
+ }
+
+ virtual void ScheduledActionCommit() OVERRIDE {}
+ virtual void ScheduledActionBeginContextRecreation() OVERRIDE {}
+ virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {}
+
+ protected:
+ Scheduler* scheduler_;
+};
+
+// Tests for two different situations:
+// 1. the scheduler dropping SetNeedsRedraw requests that happen inside
+// a ScheduledActionDrawAndSwap
+// 2. the scheduler drawing twice inside a single tick
+TEST(SchedulerTest, RequestRedrawInsideDraw) {
+ SchedulerClientThatsetNeedsDrawInsideDraw client;
+ scoped_refptr<FakeTimeSource> time_source(new FakeTimeSource());
+ SchedulerSettings default_scheduler_settings;
+ scoped_ptr<Scheduler> scheduler =
+ Scheduler::Create(&client,
+ make_scoped_ptr(new FrameRateController(time_source)),
+ default_scheduler_settings);
+ client.SetScheduler(scheduler.get());
+ scheduler->SetCanBeginFrame(true);
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(time_source->active());
+ EXPECT_EQ(0, client.num_draws());
+
+ time_source->tick();
+ EXPECT_EQ(1, client.num_draws());
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(time_source->active());
+
+ time_source->tick();
+ EXPECT_EQ(2, client.num_draws());
+ EXPECT_FALSE(scheduler->RedrawPending());
+ EXPECT_FALSE(time_source->active());
+}
+
+// Test that requesting redraw inside a failed draw doesn't lose the request.
+TEST(SchedulerTest, RequestRedrawInsideFailedDraw) {
+ SchedulerClientThatsetNeedsDrawInsideDraw client;
+ scoped_refptr<FakeTimeSource> time_source(new FakeTimeSource());
+ SchedulerSettings default_scheduler_settings;
+ scoped_ptr<Scheduler> scheduler =
+ Scheduler::Create(&client,
+ make_scoped_ptr(new FrameRateController(time_source)),
+ default_scheduler_settings);
+ client.SetScheduler(scheduler.get());
+ scheduler->SetCanBeginFrame(true);
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+ client.SetDrawWillHappen(false);
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(time_source->active());
+ EXPECT_EQ(0, client.num_draws());
+
+ // Fail the draw.
+ time_source->tick();
+ EXPECT_EQ(1, client.num_draws());
+
+ // We have a commit pending and the draw failed, and we didn't lose the redraw
+ // request.
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(time_source->active());
+
+ // Fail the draw again.
+ time_source->tick();
+ EXPECT_EQ(2, client.num_draws());
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(time_source->active());
+
+ // Draw successfully.
+ client.SetDrawWillHappen(true);
+ time_source->tick();
+ EXPECT_EQ(3, client.num_draws());
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_FALSE(scheduler->RedrawPending());
+ EXPECT_FALSE(time_source->active());
+}
+
+class SchedulerClientThatsetNeedsCommitInsideDraw : public FakeSchedulerClient {
+ public:
+ SchedulerClientThatsetNeedsCommitInsideDraw() : scheduler_(NULL) {}
+
+ void SetScheduler(Scheduler* scheduler) { scheduler_ = scheduler; }
+
+ virtual void ScheduledActionBeginFrame() OVERRIDE {}
+ virtual ScheduledActionDrawAndSwapResult
+ ScheduledActionDrawAndSwapIfPossible() OVERRIDE {
+ // Only SetNeedsCommit the first time this is called
+ if (!num_draws_)
+ scheduler_->SetNeedsCommit();
+ return FakeSchedulerClient::ScheduledActionDrawAndSwapIfPossible();
+ }
+
+ virtual ScheduledActionDrawAndSwapResult ScheduledActionDrawAndSwapForced()
+ OVERRIDE {
+ NOTREACHED();
+ return ScheduledActionDrawAndSwapResult(true, true);
+ }
+
+ virtual void ScheduledActionCommit() OVERRIDE {}
+ virtual void ScheduledActionBeginContextRecreation() OVERRIDE {}
+ virtual void DidAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE {}
+
+ protected:
+ Scheduler* scheduler_;
+};
+
+// Tests for the scheduler infinite-looping on SetNeedsCommit requests that
+// happen inside a ScheduledActionDrawAndSwap
+TEST(SchedulerTest, RequestCommitInsideDraw) {
+ SchedulerClientThatsetNeedsCommitInsideDraw client;
+ scoped_refptr<FakeTimeSource> time_source(new FakeTimeSource());
+ SchedulerSettings default_scheduler_settings;
+ scoped_ptr<Scheduler> scheduler =
+ Scheduler::Create(&client,
+ make_scoped_ptr(new FrameRateController(time_source)),
+ default_scheduler_settings);
+ client.SetScheduler(scheduler.get());
+ scheduler->SetCanBeginFrame(true);
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_EQ(0, client.num_draws());
+ EXPECT_TRUE(time_source->active());
+
+ time_source->tick();
+ EXPECT_FALSE(time_source->active());
+ EXPECT_EQ(1, client.num_draws());
+ EXPECT_TRUE(scheduler->CommitPending());
+ scheduler->BeginFrameComplete();
+
+ time_source->tick();
+ EXPECT_EQ(2, client.num_draws());
+ EXPECT_FALSE(time_source->active());
+ EXPECT_FALSE(scheduler->RedrawPending());
+}
+
+// Tests that when a draw fails then the pending commit should not be dropped.
+TEST(SchedulerTest, RequestCommitInsideFailedDraw) {
+ SchedulerClientThatsetNeedsDrawInsideDraw client;
+ scoped_refptr<FakeTimeSource> time_source(new FakeTimeSource());
+ SchedulerSettings default_scheduler_settings;
+ scoped_ptr<Scheduler> scheduler =
+ Scheduler::Create(&client,
+ make_scoped_ptr(new FrameRateController(time_source)),
+ default_scheduler_settings);
+ client.SetScheduler(scheduler.get());
+ scheduler->SetCanBeginFrame(true);
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+ client.SetDrawWillHappen(false);
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(time_source->active());
+ EXPECT_EQ(0, client.num_draws());
+
+ // Fail the draw.
+ time_source->tick();
+ EXPECT_EQ(1, client.num_draws());
+
+ // We have a commit pending and the draw failed, and we didn't lose the commit
+ // request.
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(time_source->active());
+
+ // Fail the draw again.
+ time_source->tick();
+ EXPECT_EQ(2, client.num_draws());
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(time_source->active());
+
+ // Draw successfully.
+ client.SetDrawWillHappen(true);
+ time_source->tick();
+ EXPECT_EQ(3, client.num_draws());
+ EXPECT_TRUE(scheduler->CommitPending());
+ EXPECT_FALSE(scheduler->RedrawPending());
+ EXPECT_FALSE(time_source->active());
+}
+
+TEST(SchedulerTest, NoBeginFrameWhenDrawFails) {
+ scoped_refptr<FakeTimeSource> time_source(new FakeTimeSource());
+ SchedulerClientThatsetNeedsCommitInsideDraw client;
+ scoped_ptr<FakeFrameRateController> controller(
+ new FakeFrameRateController(time_source));
+ FakeFrameRateController* controller_ptr = controller.get();
+ SchedulerSettings default_scheduler_settings;
+ scoped_ptr<Scheduler> scheduler =
+ Scheduler::Create(&client,
+ controller.PassAs<FrameRateController>(),
+ default_scheduler_settings);
+ client.SetScheduler(scheduler.get());
+ scheduler->SetCanBeginFrame(true);
+ scheduler->SetVisible(true);
+ scheduler->SetCanDraw(true);
+
+ EXPECT_EQ(0, controller_ptr->numFramesPending());
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(time_source->active());
+ EXPECT_EQ(0, client.num_draws());
+
+ // Draw successfully, this starts a new frame.
+ time_source->tick();
+ EXPECT_EQ(1, client.num_draws());
+ EXPECT_EQ(1, controller_ptr->numFramesPending());
+ scheduler->DidSwapBuffersComplete();
+ EXPECT_EQ(0, controller_ptr->numFramesPending());
+
+ scheduler->SetNeedsRedraw();
+ EXPECT_TRUE(scheduler->RedrawPending());
+ EXPECT_TRUE(time_source->active());
+
+ // Fail to draw, this should not start a frame.
+ client.SetDrawWillHappen(false);
+ time_source->tick();
+ EXPECT_EQ(2, client.num_draws());
+ EXPECT_EQ(0, controller_ptr->numFramesPending());
+}
+
+TEST(SchedulerTest, NoBeginFrameWhenSwapFailsDuringForcedCommit) {
+ scoped_refptr<FakeTimeSource> time_source(new FakeTimeSource());
+ FakeSchedulerClient client;
+ scoped_ptr<FakeFrameRateController> controller(
+ new FakeFrameRateController(time_source));
+ FakeFrameRateController* controller_ptr = controller.get();
+ SchedulerSettings default_scheduler_settings;
+ scoped_ptr<Scheduler> scheduler =
+ Scheduler::Create(&client,
+ controller.PassAs<FrameRateController>(),
+ default_scheduler_settings);
+
+ EXPECT_EQ(0, controller_ptr->numFramesPending());
+
+ // Tell the client that it will fail to swap.
+ client.SetDrawWillHappen(true);
+ client.SetSwapWillHappenIfDrawHappens(false);
+
+ // Get the compositor to do a ScheduledActionDrawAndSwapForced.
+ scheduler->SetNeedsRedraw();
+ scheduler->SetNeedsForcedRedraw();
+ EXPECT_TRUE(client.HasAction("ScheduledActionDrawAndSwapForced"));
+
+ // We should not have told the frame rate controller that we began a frame.
+ EXPECT_EQ(0, controller_ptr->numFramesPending());
+}
+
+} // namespace
+} // namespace cc
diff --git a/cc/scheduler/texture_uploader.cc b/cc/scheduler/texture_uploader.cc
new file mode 100644
index 0000000..b5d3422
--- /dev/null
+++ b/cc/scheduler/texture_uploader.cc
@@ -0,0 +1,351 @@
+// Copyright 2012 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 "cc/scheduler/texture_uploader.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/debug/alias.h"
+#include "base/debug/trace_event.h"
+#include "base/metrics/histogram.h"
+#include "cc/base/util.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/resources/resource.h"
+#include "gpu/GLES2/gl2extchromium.h"
+#include "third_party/WebKit/Source/Platform/chromium/public/WebGraphicsContext3D.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/vector2d.h"
+
+namespace {
+
+// How many previous uploads to use when predicting future throughput.
+static const size_t kUploadHistorySizeMax = 1000;
+static const size_t kUploadHistorySizeInitial = 100;
+
+// Global estimated number of textures per second to maintain estimates across
+// subsequent instances of TextureUploader.
+// More than one thread will not access this variable, so we do not need to
+// synchronize access.
+static const double kDefaultEstimatedTexturesPerSecond = 48.0 * 60.0;
+
+// Flush interval when performing texture uploads.
+static const int kTextureUploadFlushPeriod = 4;
+
+} // anonymous namespace
+
+namespace cc {
+
+TextureUploader::Query::Query(WebKit::WebGraphicsContext3D* context)
+ : context_(context),
+ query_id_(0),
+ value_(0),
+ has_value_(false),
+ is_non_blocking_(false) {
+ query_id_ = context_->createQueryEXT();
+}
+
+TextureUploader::Query::~Query() { context_->deleteQueryEXT(query_id_); }
+
+void TextureUploader::Query::Begin() {
+ has_value_ = false;
+ is_non_blocking_ = false;
+ context_->beginQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM, query_id_);
+}
+
+void TextureUploader::Query::End() {
+ context_->endQueryEXT(GL_COMMANDS_ISSUED_CHROMIUM);
+}
+
+bool TextureUploader::Query::IsPending() {
+ unsigned available = 1;
+ context_->getQueryObjectuivEXT(
+ query_id_, GL_QUERY_RESULT_AVAILABLE_EXT, &available);
+ return !available;
+}
+
+unsigned TextureUploader::Query::Value() {
+ if (!has_value_) {
+ context_->getQueryObjectuivEXT(query_id_, GL_QUERY_RESULT_EXT, &value_);
+ has_value_ = true;
+ }
+ return value_;
+}
+
+TextureUploader::TextureUploader(WebKit::WebGraphicsContext3D* context,
+ bool use_map_tex_sub_image,
+ bool useShallowFlush)
+ : context_(context),
+ num_blocking_texture_uploads_(0),
+ use_map_tex_sub_image_(use_map_tex_sub_image),
+ sub_image_size_(0),
+ use_shallow_flush_(useShallowFlush),
+ num_texture_uploads_since_last_flush_(0) {
+ for (size_t i = kUploadHistorySizeInitial; i > 0; i--)
+ textures_per_second_history_.insert(kDefaultEstimatedTexturesPerSecond);
+}
+
+TextureUploader::~TextureUploader() {}
+
+size_t TextureUploader::NumBlockingUploads() {
+ ProcessQueries();
+ return num_blocking_texture_uploads_;
+}
+
+void TextureUploader::MarkPendingUploadsAsNonBlocking() {
+ for (ScopedPtrDeque<Query>::iterator it = pending_queries_.begin();
+ it != pending_queries_.end();
+ ++it) {
+ if ((*it)->is_non_blocking())
+ continue;
+
+ num_blocking_texture_uploads_--;
+ (*it)->mark_as_non_blocking();
+ }
+
+ DCHECK(!num_blocking_texture_uploads_);
+}
+
+double TextureUploader::EstimatedTexturesPerSecond() {
+ ProcessQueries();
+
+ // Use the median as our estimate.
+ std::multiset<double>::iterator median = textures_per_second_history_.begin();
+ std::advance(median, textures_per_second_history_.size() / 2);
+ TRACE_COUNTER_ID1("cc", "EstimatedTexturesPerSecond", context_, *median);
+ return *median;
+}
+
+void TextureUploader::BeginQuery() {
+ if (available_queries_.empty())
+ available_queries_.push_back(Query::Create(context_));
+
+ available_queries_.front()->Begin();
+}
+
+void TextureUploader::EndQuery() {
+ available_queries_.front()->End();
+ pending_queries_.push_back(available_queries_.take_front());
+ num_blocking_texture_uploads_++;
+}
+
+void TextureUploader::Upload(const uint8* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format,
+ gfx::Size size) {
+ CHECK(image_rect.Contains(source_rect));
+
+ bool is_full_upload = dest_offset.IsZero() && source_rect.size() == size;
+
+ if (is_full_upload)
+ BeginQuery();
+
+ if (use_map_tex_sub_image_) {
+ UploadWithMapTexSubImage(
+ image, image_rect, source_rect, dest_offset, format);
+ } else {
+ UploadWithTexSubImage(image, image_rect, source_rect, dest_offset, format);
+ }
+
+ if (is_full_upload)
+ EndQuery();
+
+ num_texture_uploads_since_last_flush_++;
+ if (num_texture_uploads_since_last_flush_ >= kTextureUploadFlushPeriod)
+ Flush();
+}
+
+void TextureUploader::Flush() {
+ if (!num_texture_uploads_since_last_flush_)
+ return;
+
+ if (use_shallow_flush_)
+ context_->shallowFlushCHROMIUM();
+
+ num_texture_uploads_since_last_flush_ = 0;
+}
+
+void TextureUploader::ReleaseCachedQueries() {
+ ProcessQueries();
+ available_queries_.clear();
+}
+
+void TextureUploader::UploadWithTexSubImage(const uint8* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format) {
+ // Instrumentation to debug issue 156107
+ int source_rect_x = source_rect.x();
+ int source_rect_y = source_rect.y();
+ int source_rect_width = source_rect.width();
+ int source_rect_height = source_rect.height();
+ int image_rect_x = image_rect.x();
+ int image_rect_y = image_rect.y();
+ int image_rect_width = image_rect.width();
+ int image_rect_height = image_rect.height();
+ int dest_offset_x = dest_offset.x();
+ int dest_offset_y = dest_offset.y();
+ base::debug::Alias(&image);
+ base::debug::Alias(&source_rect_x);
+ base::debug::Alias(&source_rect_y);
+ base::debug::Alias(&source_rect_width);
+ base::debug::Alias(&source_rect_height);
+ base::debug::Alias(&image_rect_x);
+ base::debug::Alias(&image_rect_y);
+ base::debug::Alias(&image_rect_width);
+ base::debug::Alias(&image_rect_height);
+ base::debug::Alias(&dest_offset_x);
+ base::debug::Alias(&dest_offset_y);
+ TRACE_EVENT0("cc", "TextureUploader::uploadWithTexSubImage");
+
+ // Offset from image-rect to source-rect.
+ gfx::Vector2d offset(source_rect.origin() - image_rect.origin());
+
+ const uint8* pixel_source;
+ unsigned int bytes_per_pixel = Resource::BytesPerPixel(format);
+ // Use 4-byte row alignment (OpenGL default) for upload performance.
+ // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
+ unsigned int upload_image_stride =
+ RoundUp(bytes_per_pixel * source_rect.width(), 4u);
+
+ if (upload_image_stride == image_rect.width() * bytes_per_pixel &&
+ !offset.x()) {
+ pixel_source = &image[image_rect.width() * bytes_per_pixel * offset.y()];
+ } else {
+ size_t needed_size = upload_image_stride * source_rect.height();
+ if (sub_image_size_ < needed_size) {
+ sub_image_.reset(new uint8[needed_size]);
+ sub_image_size_ = needed_size;
+ }
+ // Strides not equal, so do a row-by-row memcpy from the
+ // paint results into a temp buffer for uploading.
+ for (int row = 0; row < source_rect.height(); ++row)
+ memcpy(&sub_image_[upload_image_stride * row],
+ &image[bytes_per_pixel *
+ (offset.x() + (offset.y() + row) * image_rect.width())],
+ source_rect.width() * bytes_per_pixel);
+
+ pixel_source = &sub_image_[0];
+ }
+
+ context_->texSubImage2D(GL_TEXTURE_2D,
+ 0,
+ dest_offset.x(),
+ dest_offset.y(),
+ source_rect.width(),
+ source_rect.height(),
+ format,
+ GL_UNSIGNED_BYTE,
+ pixel_source);
+}
+
+void TextureUploader::UploadWithMapTexSubImage(const uint8* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format) {
+ // Instrumentation to debug issue 156107
+ int source_rect_x = source_rect.x();
+ int source_rect_y = source_rect.y();
+ int source_rect_width = source_rect.width();
+ int source_rect_height = source_rect.height();
+ int image_rect_x = image_rect.x();
+ int image_rect_y = image_rect.y();
+ int image_rect_width = image_rect.width();
+ int image_rect_height = image_rect.height();
+ int dest_offset_x = dest_offset.x();
+ int dest_offset_y = dest_offset.y();
+ base::debug::Alias(&image);
+ base::debug::Alias(&source_rect_x);
+ base::debug::Alias(&source_rect_y);
+ base::debug::Alias(&source_rect_width);
+ base::debug::Alias(&source_rect_height);
+ base::debug::Alias(&image_rect_x);
+ base::debug::Alias(&image_rect_y);
+ base::debug::Alias(&image_rect_width);
+ base::debug::Alias(&image_rect_height);
+ base::debug::Alias(&dest_offset_x);
+ base::debug::Alias(&dest_offset_y);
+
+ TRACE_EVENT0("cc", "TextureUploader::uploadWithMapTexSubImage");
+
+ // Offset from image-rect to source-rect.
+ gfx::Vector2d offset(source_rect.origin() - image_rect.origin());
+
+ unsigned int bytes_per_pixel = Resource::BytesPerPixel(format);
+ // Use 4-byte row alignment (OpenGL default) for upload performance.
+ // Assuming that GL_UNPACK_ALIGNMENT has not changed from default.
+ unsigned int upload_image_stride =
+ RoundUp(bytes_per_pixel * source_rect.width(), 4u);
+
+ // Upload tile data via a mapped transfer buffer
+ uint8* pixel_dest = static_cast<uint8*>(
+ context_->mapTexSubImage2DCHROMIUM(GL_TEXTURE_2D,
+ 0,
+ dest_offset.x(),
+ dest_offset.y(),
+ source_rect.width(),
+ source_rect.height(),
+ format,
+ GL_UNSIGNED_BYTE,
+ GL_WRITE_ONLY));
+
+ if (!pixel_dest) {
+ UploadWithTexSubImage(image, image_rect, source_rect, dest_offset, format);
+ return;
+ }
+
+ if (upload_image_stride == image_rect.width() * bytes_per_pixel &&
+ !offset.x()) {
+ memcpy(pixel_dest,
+ &image[image_rect.width() * bytes_per_pixel * offset.y()],
+ source_rect.height() * image_rect.width() * bytes_per_pixel);
+ } else {
+ // Strides not equal, so do a row-by-row memcpy from the
+ // paint results into the pixelDest
+ for (int row = 0; row < source_rect.height(); ++row) {
+ memcpy(&pixel_dest[upload_image_stride * row],
+ &image[bytes_per_pixel *
+ (offset.x() + (offset.y() + row) * image_rect.width())],
+ source_rect.width() * bytes_per_pixel);
+ }
+ }
+
+ context_->unmapTexSubImage2DCHROMIUM(pixel_dest);
+}
+
+void TextureUploader::ProcessQueries() {
+ while (!pending_queries_.empty()) {
+ if (pending_queries_.front()->IsPending())
+ break;
+
+ unsigned us_elapsed = pending_queries_.front()->Value();
+ UMA_HISTOGRAM_CUSTOM_COUNTS(
+ "Renderer4.TextureGpuUploadTimeUS", us_elapsed, 0, 100000, 50);
+
+ // Clamp the queries to saner values in case the queries fail.
+ us_elapsed = std::max(1u, us_elapsed);
+ us_elapsed = std::min(15000u, us_elapsed);
+
+ if (!pending_queries_.front()->is_non_blocking())
+ num_blocking_texture_uploads_--;
+
+ // Remove the min and max value from our history and insert the new one.
+ double textures_per_second = 1.0 / (us_elapsed * 1e-6);
+ if (textures_per_second_history_.size() >= kUploadHistorySizeMax) {
+ textures_per_second_history_.erase(textures_per_second_history_.begin());
+ textures_per_second_history_.erase(--textures_per_second_history_.end());
+ }
+ textures_per_second_history_.insert(textures_per_second);
+
+ available_queries_.push_back(pending_queries_.take_front());
+ }
+}
+
+} // namespace cc
diff --git a/cc/scheduler/texture_uploader.h b/cc/scheduler/texture_uploader.h
new file mode 100644
index 0000000..46120c9
--- /dev/null
+++ b/cc/scheduler/texture_uploader.h
@@ -0,0 +1,125 @@
+// Copyright 2012 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 CC_SCHEDULER_TEXTURE_UPLOADER_H_
+#define CC_SCHEDULER_TEXTURE_UPLOADER_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "cc/base/cc_export.h"
+#include "cc/base/scoped_ptr_deque.h"
+#include "third_party/khronos/GLES2/gl2.h"
+
+namespace WebKit { class WebGraphicsContext3D; }
+
+namespace gfx {
+class Rect;
+class Size;
+class Vector2d;
+}
+
+namespace cc {
+
+class CC_EXPORT TextureUploader {
+ public:
+ static scoped_ptr<TextureUploader> Create(
+ WebKit::WebGraphicsContext3D* context,
+ bool use_map_tex_sub_image,
+ bool use_shallow_flush) {
+ return make_scoped_ptr(
+ new TextureUploader(context, use_map_tex_sub_image, use_shallow_flush));
+ }
+ ~TextureUploader();
+
+ size_t NumBlockingUploads();
+ void MarkPendingUploadsAsNonBlocking();
+ double EstimatedTexturesPerSecond();
+
+ // Let content_rect be a rectangle, and let content_rect be a sub-rectangle of
+ // content_rect, expressed in the same coordinate system as content_rect. Let
+ // image be a buffer for content_rect. This function will copy the region
+ // corresponding to sourceRect to destOffset in this sub-image.
+ void Upload(const uint8* image,
+ gfx::Rect content_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format,
+ gfx::Size size);
+
+ void Flush();
+ void ReleaseCachedQueries();
+
+ private:
+ class Query {
+ public:
+ static scoped_ptr<Query> Create(WebKit::WebGraphicsContext3D* context) {
+ return make_scoped_ptr(new Query(context));
+ }
+
+ virtual ~Query();
+
+ void Begin();
+ void End();
+ bool IsPending();
+ unsigned Value();
+ size_t TexturesUploaded();
+ void mark_as_non_blocking() {
+ is_non_blocking_ = true;
+ }
+ bool is_non_blocking() const {
+ return is_non_blocking_;
+ }
+
+ private:
+ explicit Query(WebKit::WebGraphicsContext3D* context);
+
+ WebKit::WebGraphicsContext3D* context_;
+ unsigned query_id_;
+ unsigned value_;
+ bool has_value_;
+ bool is_non_blocking_;
+
+ DISALLOW_COPY_AND_ASSIGN(Query);
+ };
+
+ TextureUploader(WebKit::WebGraphicsContext3D* context,
+ bool use_map_tex_sub_image,
+ bool use_shallow_flush);
+
+ void BeginQuery();
+ void EndQuery();
+ void ProcessQueries();
+
+ void UploadWithTexSubImage(const uint8* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format);
+ void UploadWithMapTexSubImage(const uint8* image,
+ gfx::Rect image_rect,
+ gfx::Rect source_rect,
+ gfx::Vector2d dest_offset,
+ GLenum format);
+
+ WebKit::WebGraphicsContext3D* context_;
+ ScopedPtrDeque<Query> pending_queries_;
+ ScopedPtrDeque<Query> available_queries_;
+ std::multiset<double> textures_per_second_history_;
+ size_t num_blocking_texture_uploads_;
+
+ bool use_map_tex_sub_image_;
+ size_t sub_image_size_;
+ scoped_array<uint8> sub_image_;
+
+ bool use_shallow_flush_;
+ size_t num_texture_uploads_since_last_flush_;
+
+ DISALLOW_COPY_AND_ASSIGN(TextureUploader);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_TEXTURE_UPLOADER_H_
diff --git a/cc/scheduler/texture_uploader_unittest.cc b/cc/scheduler/texture_uploader_unittest.cc
new file mode 100644
index 0000000..6c5c916
--- /dev/null
+++ b/cc/scheduler/texture_uploader_unittest.cc
@@ -0,0 +1,243 @@
+// Copyright 2012 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 "cc/scheduler/texture_uploader.h"
+
+#include "cc/base/util.h"
+#include "cc/resources/prioritized_resource.h"
+#include "cc/test/test_web_graphics_context_3d.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/khronos/GLES2/gl2.h"
+#include "third_party/khronos/GLES2/gl2ext.h"
+
+using namespace WebKit;
+
+namespace cc {
+namespace {
+
+class TestWebGraphicsContext3DTextureUpload : public TestWebGraphicsContext3D {
+ public:
+ TestWebGraphicsContext3DTextureUpload()
+ : result_available_(0),
+ unpack_alignment_(4) {}
+
+ virtual void pixelStorei(WGC3Denum pname, WGC3Dint param) OVERRIDE {
+ switch (pname) {
+ case GL_UNPACK_ALIGNMENT:
+ // Param should be a power of two <= 8.
+ EXPECT_EQ(0, param & (param - 1));
+ EXPECT_GE(8, param);
+ switch (param) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ unpack_alignment_ = param;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ virtual void getQueryObjectuivEXT(WebGLId, WGC3Denum type, WGC3Duint* value)
+ OVERRIDE {
+ switch (type) {
+ case GL_QUERY_RESULT_AVAILABLE_EXT:
+ *value = result_available_;
+ break;
+ default:
+ *value = 0;
+ break;
+ }
+ }
+
+ virtual void texSubImage2D(WGC3Denum target,
+ WGC3Dint level,
+ WGC3Dint xoffset,
+ WGC3Dint yoffset,
+ WGC3Dsizei width,
+ WGC3Dsizei height,
+ WGC3Denum format,
+ WGC3Denum type,
+ const void* pixels) OVERRIDE {
+ EXPECT_EQ(GL_TEXTURE_2D, target);
+ EXPECT_EQ(0, level);
+ EXPECT_LE(0, width);
+ EXPECT_LE(0, height);
+ EXPECT_LE(0, xoffset);
+ EXPECT_LE(0, yoffset);
+ EXPECT_LE(0, width);
+ EXPECT_LE(0, height);
+
+ // Check for allowed format/type combination.
+ unsigned int bytes_per_pixel = 0;
+ switch (format) {
+ case GL_ALPHA:
+ EXPECT_EQ(GL_UNSIGNED_BYTE, type);
+ bytes_per_pixel = 1;
+ break;
+ case GL_RGB:
+ EXPECT_NE(GL_UNSIGNED_SHORT_4_4_4_4, type);
+ EXPECT_NE(GL_UNSIGNED_SHORT_5_5_5_1, type);
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ bytes_per_pixel = 3;
+ break;
+ case GL_UNSIGNED_SHORT_5_6_5:
+ bytes_per_pixel = 2;
+ break;
+ }
+ break;
+ case GL_RGBA:
+ EXPECT_NE(GL_UNSIGNED_SHORT_5_6_5, type);
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ bytes_per_pixel = 4;
+ break;
+ case GL_UNSIGNED_SHORT_4_4_4_4:
+ bytes_per_pixel = 2;
+ break;
+ case GL_UNSIGNED_SHORT_5_5_5_1:
+ bytes_per_pixel = 2;
+ break;
+ }
+ break;
+ case GL_LUMINANCE:
+ EXPECT_EQ(GL_UNSIGNED_BYTE, type);
+ bytes_per_pixel = 1;
+ break;
+ case GL_LUMINANCE_ALPHA:
+ EXPECT_EQ(GL_UNSIGNED_BYTE, type);
+ bytes_per_pixel = 2;
+ break;
+ }
+
+ // If NULL, we aren't checking texture contents.
+ if (pixels == NULL)
+ return;
+
+ const uint8* bytes = static_cast<const uint8*>(pixels);
+ // We'll expect the first byte of every row to be 0x1, and the last byte to
+ // be 0x2.
+ const unsigned int stride =
+ RoundUp(bytes_per_pixel * width, unpack_alignment_);
+ for (WGC3Dsizei row = 0; row < height; ++row) {
+ const uint8* row_bytes =
+ bytes + (xoffset * bytes_per_pixel + (yoffset + row) * stride);
+ EXPECT_EQ(0x1, row_bytes[0]);
+ EXPECT_EQ(0x2, row_bytes[width * bytes_per_pixel - 1]);
+ }
+ }
+
+ void SetResultAvailable(unsigned result_available) {
+ result_available_ = result_available;
+ }
+
+ private:
+ unsigned unpack_alignment_;
+ unsigned result_available_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestWebGraphicsContext3DTextureUpload);
+};
+
+void UploadTexture(TextureUploader* uploader,
+ WGC3Denum format,
+ gfx::Size size,
+ const uint8* data) {
+ uploader->Upload(data,
+ gfx::Rect(gfx::Point(), size),
+ gfx::Rect(gfx::Point(), size),
+ gfx::Vector2d(),
+ format,
+ size);
+}
+
+TEST(TextureUploaderTest, NumBlockingUploads) {
+ scoped_ptr<TestWebGraphicsContext3DTextureUpload> fake_context(
+ new TestWebGraphicsContext3DTextureUpload);
+ scoped_ptr<TextureUploader> uploader =
+ TextureUploader::Create(fake_context.get(), false, false);
+
+ fake_context->SetResultAvailable(0);
+ EXPECT_EQ(0, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(1, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(2, uploader->NumBlockingUploads());
+
+ fake_context->SetResultAvailable(1);
+ EXPECT_EQ(0, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(0, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(0, uploader->NumBlockingUploads());
+}
+
+TEST(TextureUploaderTest, MarkPendingUploadsAsNonBlocking) {
+ scoped_ptr<TestWebGraphicsContext3DTextureUpload> fake_context(
+ new TestWebGraphicsContext3DTextureUpload);
+ scoped_ptr<TextureUploader> uploader =
+ TextureUploader::Create(fake_context.get(), false, false);
+
+ fake_context->SetResultAvailable(0);
+ EXPECT_EQ(0, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(2, uploader->NumBlockingUploads());
+
+ uploader->MarkPendingUploadsAsNonBlocking();
+ EXPECT_EQ(0, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ EXPECT_EQ(1, uploader->NumBlockingUploads());
+
+ fake_context->SetResultAvailable(1);
+ EXPECT_EQ(0, uploader->NumBlockingUploads());
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(), NULL);
+ uploader->MarkPendingUploadsAsNonBlocking();
+ EXPECT_EQ(0, uploader->NumBlockingUploads());
+}
+
+TEST(TextureUploaderTest, UploadContentsTest) {
+ scoped_ptr<TestWebGraphicsContext3DTextureUpload> fake_context(
+ new TestWebGraphicsContext3DTextureUpload);
+ scoped_ptr<TextureUploader> uploader =
+ TextureUploader::Create(fake_context.get(), false, false);
+ uint8 buffer[256 * 256 * 4];
+
+ // Upload a tightly packed 256x256 RGBA texture.
+ memset(buffer, 0, sizeof(buffer));
+ for (int i = 0; i < 256; ++i) {
+ // Mark the beginning and end of each row, for the test.
+ buffer[i * 4 * 256] = 0x1;
+ buffer[(i + 1) * 4 * 256 - 1] = 0x2;
+ }
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(256, 256), buffer);
+
+ // Upload a tightly packed 41x43 RGBA texture.
+ memset(buffer, 0, sizeof(buffer));
+ for (int i = 0; i < 43; ++i) {
+ // Mark the beginning and end of each row, for the test.
+ buffer[i * 4 * 41] = 0x1;
+ buffer[(i + 1) * 4 * 41 - 1] = 0x2;
+ }
+ UploadTexture(uploader.get(), GL_RGBA, gfx::Size(41, 43), buffer);
+
+ // Upload a tightly packed 82x86 LUMINANCE texture.
+ memset(buffer, 0, sizeof(buffer));
+ for (int i = 0; i < 86; ++i) {
+ // Mark the beginning and end of each row, for the test.
+ buffer[i * 1 * 82] = 0x1;
+ buffer[(i + 1) * 82 - 1] = 0x2;
+ }
+ UploadTexture(uploader.get(), GL_LUMINANCE, gfx::Size(82, 86), buffer);
+}
+
+} // namespace
+} // namespace cc
diff --git a/cc/scheduler/time_source.h b/cc/scheduler/time_source.h
new file mode 100644
index 0000000..0b47a61
--- /dev/null
+++ b/cc/scheduler/time_source.h
@@ -0,0 +1,46 @@
+// Copyright 2011 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 CC_SCHEDULER_TIME_SOURCE_H_
+#define CC_SCHEDULER_TIME_SOURCE_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/time.h"
+#include "cc/base/cc_export.h"
+
+namespace cc {
+
+class TimeSourceClient {
+ public:
+ virtual void onTimerTick() = 0;
+
+ protected:
+ virtual ~TimeSourceClient() {}
+};
+
+// An generic interface for getting a reliably-ticking timesource of
+// a specified rate.
+//
+// Be sure to call setActive(false) before releasing your reference to the
+// timer, or it will keep on ticking!
+class CC_EXPORT TimeSource : public base::RefCounted<TimeSource> {
+ public:
+ virtual void setClient(TimeSourceClient*) = 0;
+ virtual void setActive(bool) = 0;
+ virtual bool active() const = 0;
+ virtual void setTimebaseAndInterval(base::TimeTicks timebase,
+ base::TimeDelta interval) = 0;
+ virtual base::TimeTicks lastTickTime() = 0;
+ virtual base::TimeTicks nextTickTime() = 0;
+
+ protected:
+ virtual ~TimeSource() {}
+
+ private:
+ friend class base::RefCounted<TimeSource>;
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_TIME_SOURCE_H_
diff --git a/cc/scheduler/vsync_time_source.cc b/cc/scheduler/vsync_time_source.cc
new file mode 100644
index 0000000..166144f
--- /dev/null
+++ b/cc/scheduler/vsync_time_source.cc
@@ -0,0 +1,69 @@
+// Copyright 2013 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 "cc/scheduler/vsync_time_source.h"
+
+namespace cc {
+
+scoped_refptr<VSyncTimeSource> VSyncTimeSource::create(
+ VSyncProvider* vsync_provider) {
+ return make_scoped_refptr(new VSyncTimeSource(vsync_provider));
+}
+
+VSyncTimeSource::VSyncTimeSource(VSyncProvider* vsync_provider)
+ : vsync_provider_(vsync_provider)
+ , client_(0)
+ , active_(false)
+ , notification_requested_(false) {}
+
+VSyncTimeSource::~VSyncTimeSource() {}
+
+void VSyncTimeSource::setClient(TimeSourceClient* client) {
+ client_ = client;
+}
+
+void VSyncTimeSource::setActive(bool active) {
+ if (active_ == active)
+ return;
+ active_ = active;
+ // The notification will be lazily disabled in the callback to ensure
+ // we get notified of the frame immediately following a quick on-off-on
+ // transition.
+ if (active_ && !notification_requested_) {
+ notification_requested_ = true;
+ vsync_provider_->RequestVSyncNotification(this);
+ }
+}
+
+bool VSyncTimeSource::active() const {
+ return active_;
+}
+
+base::TimeTicks VSyncTimeSource::lastTickTime() {
+ return last_tick_time_;
+}
+
+base::TimeTicks VSyncTimeSource::nextTickTime() {
+ return active() ? last_tick_time_ + interval_ : base::TimeTicks();
+}
+
+void VSyncTimeSource::setTimebaseAndInterval(base::TimeTicks,
+ base::TimeDelta interval) {
+ interval_ = interval;
+}
+
+void VSyncTimeSource::DidVSync(base::TimeTicks frame_time) {
+ last_tick_time_ = frame_time;
+ if (!active_) {
+ if (notification_requested_) {
+ notification_requested_ = false;
+ vsync_provider_->RequestVSyncNotification(NULL);
+ }
+ return;
+ }
+ if (client_)
+ client_->onTimerTick();
+}
+
+} // namespace cc
diff --git a/cc/scheduler/vsync_time_source.h b/cc/scheduler/vsync_time_source.h
new file mode 100644
index 0000000..8d0659c
--- /dev/null
+++ b/cc/scheduler/vsync_time_source.h
@@ -0,0 +1,67 @@
+// Copyright 2013 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 CC_SCHEDULER_VSYNC_TIME_SOURCE_H_
+#define CC_SCHEDULER_VSYNC_TIME_SOURCE_H_
+
+#include "cc/base/cc_export.h"
+#include "cc/scheduler/time_source.h"
+
+namespace cc {
+
+class CC_EXPORT VSyncClient {
+ public:
+ virtual void DidVSync(base::TimeTicks frame_time) = 0;
+
+ protected:
+ virtual ~VSyncClient() {}
+};
+
+class VSyncProvider {
+ public:
+ // Request to be notified of future vsync events. The notifications will be
+ // delivered until they are disabled by calling this function with a null
+ // client.
+ virtual void RequestVSyncNotification(VSyncClient* client) = 0;
+
+ protected:
+ virtual ~VSyncProvider() {}
+};
+
+// This timer implements a time source that is explicitly triggered by an
+// external vsync signal.
+class CC_EXPORT VSyncTimeSource : public TimeSource, public VSyncClient {
+ public:
+ static scoped_refptr<VSyncTimeSource> create(VSyncProvider* vsync_provider);
+
+ // TimeSource implementation
+ virtual void setClient(TimeSourceClient* client) OVERRIDE;
+ virtual void setTimebaseAndInterval(base::TimeTicks timebase,
+ base::TimeDelta interval) OVERRIDE;
+ virtual void setActive(bool active) OVERRIDE;
+ virtual bool active() const OVERRIDE;
+ virtual base::TimeTicks lastTickTime() OVERRIDE;
+ virtual base::TimeTicks nextTickTime() OVERRIDE;
+
+ // VSyncClient implementation
+ virtual void DidVSync(base::TimeTicks frame_time) OVERRIDE;
+
+ protected:
+ explicit VSyncTimeSource(VSyncProvider* vsync_provider);
+ virtual ~VSyncTimeSource();
+
+ base::TimeTicks last_tick_time_;
+ base::TimeDelta interval_;
+ bool active_;
+ bool notification_requested_;
+
+ VSyncProvider* vsync_provider_;
+ TimeSourceClient* client_;
+
+ DISALLOW_COPY_AND_ASSIGN(VSyncTimeSource);
+};
+
+} // namespace cc
+
+#endif // CC_SCHEDULER_VSYNC_TIME_SOURCE_H_
diff --git a/cc/scheduler/vsync_time_source_unittest.cc b/cc/scheduler/vsync_time_source_unittest.cc
new file mode 100644
index 0000000..ea9c79d
--- /dev/null
+++ b/cc/scheduler/vsync_time_source_unittest.cc
@@ -0,0 +1,103 @@
+// Copyright 2013 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 "cc/scheduler/vsync_time_source.h"
+
+#include "cc/test/scheduler_test_common.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace cc {
+namespace {
+
+class FakeVSyncProvider : public VSyncProvider {
+ public:
+ FakeVSyncProvider()
+ : client_(NULL) {}
+
+ // VSyncProvider implementation.
+ virtual void RequestVSyncNotification(VSyncClient* client) OVERRIDE {
+ client_ = client;
+ }
+
+ bool IsVSyncNotificationEnabled() const { return client_ != NULL; }
+
+ void Trigger(base::TimeTicks frame_time) {
+ if (client_)
+ client_->DidVSync(frame_time);
+ }
+
+ private:
+ VSyncClient* client_;
+};
+
+class VSyncTimeSourceTest : public testing::Test {
+ public:
+ VSyncTimeSourceTest()
+ : timer_(VSyncTimeSource::create(&provider_)) {
+ timer_->setClient(&client_);
+ }
+
+ protected:
+ FakeTimeSourceClient client_;
+ FakeVSyncProvider provider_;
+ scoped_refptr<VSyncTimeSource> timer_;
+};
+
+TEST_F(VSyncTimeSourceTest, TaskPostedAndTickCalled)
+{
+ EXPECT_FALSE(provider_.IsVSyncNotificationEnabled());
+
+ timer_->setActive(true);
+ EXPECT_TRUE(provider_.IsVSyncNotificationEnabled());
+
+ base::TimeTicks frame_time = base::TimeTicks::Now();
+ provider_.Trigger(frame_time);
+ EXPECT_TRUE(client_.tickCalled());
+ EXPECT_EQ(timer_->lastTickTime(), frame_time);
+}
+
+TEST_F(VSyncTimeSourceTest, NotificationDisabledLazily)
+{
+ base::TimeTicks frame_time = base::TimeTicks::Now();
+
+ // Enable timer and trigger sync once.
+ timer_->setActive(true);
+ EXPECT_TRUE(provider_.IsVSyncNotificationEnabled());
+ provider_.Trigger(frame_time);
+ EXPECT_TRUE(client_.tickCalled());
+
+ // Disabling the timer should not disable vsync notification immediately.
+ client_.reset();
+ timer_->setActive(false);
+ EXPECT_TRUE(provider_.IsVSyncNotificationEnabled());
+
+ // At the next vsync the notification is disabled, but the timer isn't ticked.
+ provider_.Trigger(frame_time);
+ EXPECT_FALSE(provider_.IsVSyncNotificationEnabled());
+ EXPECT_FALSE(client_.tickCalled());
+
+ // The notification should not be disabled multiple times.
+ provider_.RequestVSyncNotification(timer_.get());
+ provider_.Trigger(frame_time);
+ EXPECT_TRUE(provider_.IsVSyncNotificationEnabled());
+ EXPECT_FALSE(client_.tickCalled());
+}
+
+TEST_F(VSyncTimeSourceTest, ValidNextTickTime)
+{
+ base::TimeTicks frame_time = base::TimeTicks::Now();
+ base::TimeDelta interval = base::TimeDelta::FromSeconds(1);
+
+ ASSERT_EQ(timer_->nextTickTime(), base::TimeTicks());
+
+ timer_->setActive(true);
+ provider_.Trigger(frame_time);
+ ASSERT_EQ(timer_->nextTickTime(), frame_time);
+
+ timer_->setTimebaseAndInterval(frame_time, interval);
+ ASSERT_EQ(timer_->nextTickTime(), frame_time + interval);
+}
+
+} // namespace
+} // namespace cc