diff options
author | jamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-18 08:36:31 +0000 |
---|---|---|
committer | jamesr@chromium.org <jamesr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-18 08:36:31 +0000 |
commit | be4655a91f88479e913499a8587e47453dda308f (patch) | |
tree | c337ce7463df87ab3f50ec80964b5893ad6bbaef /cc/scheduler | |
parent | e12dd0e802b2a80112cb40e01fabcc5c0475f05b (diff) | |
download | chromium_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')
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 |