Diff
Modified: trunk/Source/WebCore/ChangeLog (124897 => 124898)
--- trunk/Source/WebCore/ChangeLog 2012-08-07 18:17:31 UTC (rev 124897)
+++ trunk/Source/WebCore/ChangeLog 2012-08-07 18:29:49 UTC (rev 124898)
@@ -1,3 +1,27 @@
+2012-08-07 Brian Anderson <[email protected]>
+
+ Add CCDelayBasedTimeSource::setTimebaseAndInterval
+ https://bugs.webkit.org/show_bug.cgi?id=92825
+
+ Reviewed by James Robinson.
+
+ Allows CCDelayBaseTimeSource to have it's timebase and interval updated
+ on the fly. Accounts for double ticking due to jitter or resetting the
+ timer multiple times quickly.
+
+ CCDelayBasedTimeSourceTest updated with two tests for proper handling
+ of jittery timebase/interval source and immediate handling of
+ significant timebase/interval changes.
+
+ * platform/graphics/chromium/cc/CCDelayBasedTimeSource.cpp:
+ (WebCore::CCDelayBasedTimeSource::CCDelayBasedTimeSource):
+ (WebCore::CCDelayBasedTimeSource::nextTickTime):
+ (WebCore):
+ (WebCore::CCDelayBasedTimeSource::onTimerFired):
+ (WebCore::CCDelayBasedTimeSource::setTimebaseAndInterval):
+ (WebCore::CCDelayBasedTimeSource::postNextTickTask):
+ * platform/graphics/chromium/cc/CCDelayBasedTimeSource.h:
+
2012-07-23 Alexandru Chiculita <[email protected]>
[CSS Shaders] Reuse precompiled shaders across elements
Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCDelayBasedTimeSource.cpp (124897 => 124898)
--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCDelayBasedTimeSource.cpp 2012-08-07 18:17:31 UTC (rev 124897)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCDelayBasedTimeSource.cpp 2012-08-07 18:29:49 UTC (rev 124898)
@@ -26,11 +26,28 @@
#include "cc/CCDelayBasedTimeSource.h"
+#include <algorithm>
#include <wtf/CurrentTime.h>
#include <wtf/MathExtras.h>
namespace WebCore {
+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;
+
+}
+
+
PassRefPtr<CCDelayBasedTimeSource> CCDelayBasedTimeSource::create(double interval, CCThread* thread)
{
return adoptRef(new CCDelayBasedTimeSource(interval, thread));
@@ -39,8 +56,9 @@
CCDelayBasedTimeSource::CCDelayBasedTimeSource(double intervalSeconds, CCThread* thread)
: m_client(0)
, m_hasTickTarget(false)
- , m_intervalSeconds(intervalSeconds)
- , m_tickTarget(0)
+ , m_lastTickTime(0)
+ , m_currentParameters(intervalSeconds, 0)
+ , m_nextParameters(intervalSeconds, 0)
, m_state(STATE_INACTIVE)
, m_timer(thread, this)
{
@@ -72,15 +90,25 @@
postNextTickTask(now);
}
+double CCDelayBasedTimeSource::lastTickTime()
+{
+ return m_lastTickTime;
+}
+
+double CCDelayBasedTimeSource::nextTickTime()
+{
+ return active() ? m_currentParameters.tickTarget : 0.0;
+}
+
void CCDelayBasedTimeSource::onTimerFired()
{
ASSERT(m_state != STATE_INACTIVE);
double now = monotonicallyIncreasingTime();
+ m_lastTickTime = now;
if (m_state == STATE_STARTING) {
- m_hasTickTarget = true;
- m_tickTarget = now;
+ setTimebaseAndInterval(now, m_currentParameters.interval);
m_state = STATE_ACTIVE;
}
@@ -91,6 +119,42 @@
m_client->onTimerTick();
}
+void CCDelayBasedTimeSource::setTimebaseAndInterval(double timebase, double intervalSeconds)
+{
+ m_nextParameters.interval = intervalSeconds;
+ 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(intervalSeconds - m_currentParameters.interval);
+ double intervalChange = intervalDelta / intervalSeconds;
+ 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);
+ double phaseChange = fmod(targetDelta, intervalSeconds) / intervalSeconds;
+ if (phaseChange > phaseChangeThreshold && phaseChange < (1.0 - phaseChangeThreshold)) {
+ setActive(false);
+ setActive(true);
+ return;
+ }
+}
+
double CCDelayBasedTimeSource::monotonicallyIncreasingTime() const
{
return WTF::monotonicallyIncreasingTime();
@@ -143,20 +207,25 @@
// Note, that in the above discussion, times are expressed in milliseconds, but in the code, seconds are used.
void CCDelayBasedTimeSource::postNextTickTask(double now)
{
- int numIntervalsElapsed = static_cast<int>(floor((now - m_tickTarget) / m_intervalSeconds));
- double lastEffectiveTick = m_tickTarget + m_intervalSeconds * numIntervalsElapsed;
- double newTickTarget = lastEffectiveTick + m_intervalSeconds;
+ double newInterval = m_nextParameters.interval;
+ double intervalsElapsed = floor((now - m_nextParameters.tickTarget) / newInterval);
+ double lastEffectiveTick = m_nextParameters.tickTarget + newInterval * intervalsElapsed;
+ double newTickTarget = lastEffectiveTick + newInterval;
+ ASSERT(newTickTarget > now);
- long long delayMs = static_cast<long long>((newTickTarget - now) * 1000.0);
- if (!delayMs) {
- newTickTarget = newTickTarget + m_intervalSeconds;
- delayMs = static_cast<long long>((newTickTarget - now) * 1000.0);
- }
+ // 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 * doubleTickThreshold)
+ newTickTarget += newInterval;
// Post another task *before* the tick and update state
- ASSERT(newTickTarget > now);
- m_timer.startOneShot(delayMs / 1000.0);
- m_tickTarget = newTickTarget;
+ double delay = newTickTarget - now;
+ ASSERT(delay <= newInterval * (1.0 + doubleTickThreshold));
+ m_timer.startOneShot(delay);
+
+ m_nextParameters.tickTarget = newTickTarget;
+ m_currentParameters = m_nextParameters;
}
}
Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCDelayBasedTimeSource.h (124897 => 124898)
--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCDelayBasedTimeSource.h 2012-08-07 18:17:31 UTC (rev 124897)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCDelayBasedTimeSource.h 2012-08-07 18:29:49 UTC (rev 124898)
@@ -44,9 +44,16 @@
virtual void setClient(CCTimeSourceClient* client) OVERRIDE { m_client = client; }
+ virtual void setTimebaseAndInterval(double timebase, double intervalSeconds) OVERRIDE;
+
virtual void setActive(bool) OVERRIDE;
virtual bool active() const OVERRIDE { return m_state != STATE_INACTIVE; }
+ // Get the last and next tick times.
+ // If not active, nextTickTime will return 0.
+ virtual double lastTickTime();
+ virtual double nextTickTime();
+
// CCTimerClient implementation.
virtual void onTimerFired() OVERRIDE;
@@ -62,10 +69,26 @@
STATE_STARTING,
STATE_ACTIVE,
};
+
+ struct Parameters {
+ Parameters(double interval, double tickTarget)
+ : interval(interval), tickTarget(tickTarget)
+ { }
+ double interval;
+ double tickTarget;
+ };
+
CCTimeSourceClient* m_client;
bool m_hasTickTarget;
- double m_intervalSeconds;
- double m_tickTarget;
+ double 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;
CCThread* m_thread;
CCTimer m_timer;
Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCFrameRateController.cpp (124897 => 124898)
--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCFrameRateController.cpp 2012-08-07 18:17:31 UTC (rev 124897)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCFrameRateController.cpp 2012-08-07 18:29:49 UTC (rev 124898)
@@ -98,6 +98,11 @@
m_maxFramesPending = maxFramesPending;
}
+void CCFrameRateController::setTimebaseAndInterval(double timebase, double intervalSeconds)
+{
+ m_timeSource->setTimebaseAndInterval(timebase, intervalSeconds);
+}
+
void CCFrameRateController::onTimerTick()
{
ASSERT(m_active);
Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCFrameRateController.h (124897 => 124898)
--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCFrameRateController.h 2012-08-07 18:17:31 UTC (rev 124897)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCFrameRateController.h 2012-08-07 18:29:49 UTC (rev 124898)
@@ -58,8 +58,6 @@
void setActive(bool);
- void setMaxPendingFrames(int);
-
// Use the following methods to adjust target frame rate.
//
// Multiple frames can be in-progress, but for every didBeginFrame, a
@@ -71,6 +69,8 @@
void didAbortAllPendingFrames();
void setMaxFramesPending(int); // 0 for unlimited.
+ void setTimebaseAndInterval(double timebase, double intervalSeconds);
+
protected:
friend class CCFrameRateControllerTimeSourceAdapter;
void onTimerTick();
Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCScheduler.cpp (124897 => 124898)
--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCScheduler.cpp 2012-08-07 18:17:31 UTC (rev 124897)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCScheduler.cpp 2012-08-07 18:29:49 UTC (rev 124898)
@@ -126,6 +126,11 @@
processScheduledActions();
}
+void CCScheduler::setTimebaseAndInterval(double timebase, double intervalSeconds)
+{
+ m_frameRateController->setTimebaseAndInterval(timebase, intervalSeconds);
+}
+
void CCScheduler::vsyncTick()
{
if (m_updateMoreResourcesPending) {
Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCScheduler.h (124897 => 124898)
--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCScheduler.h 2012-08-07 18:17:31 UTC (rev 124897)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCScheduler.h 2012-08-07 18:29:49 UTC (rev 124898)
@@ -105,6 +105,8 @@
bool commitPending() const { return m_stateMachine.commitPending(); }
bool redrawPending() const { return m_stateMachine.redrawPending(); }
+ void setTimebaseAndInterval(double timebase, double intervalSeconds);
+
// CCFrameRateControllerClient implementation
virtual void vsyncTick() OVERRIDE;
Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCTimeSource.h (124897 => 124898)
--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCTimeSource.h 2012-08-07 18:17:31 UTC (rev 124897)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCTimeSource.h 2012-08-07 18:29:49 UTC (rev 124898)
@@ -50,6 +50,9 @@
virtual void setClient(CCTimeSourceClient*) = 0;
virtual void setActive(bool) = 0;
virtual bool active() const = 0;
+ virtual void setTimebaseAndInterval(double timebase, double intervalSeconds) = 0;
+ virtual double lastTickTime() = 0;
+ virtual double nextTickTime() = 0;
};
}
Modified: trunk/Source/WebKit/chromium/tests/CCDelayBasedTimeSourceTest.cpp (124897 => 124898)
--- trunk/Source/WebKit/chromium/tests/CCDelayBasedTimeSourceTest.cpp 2012-08-07 18:17:31 UTC (rev 124897)
+++ trunk/Source/WebKit/chromium/tests/CCDelayBasedTimeSourceTest.cpp 2012-08-07 18:29:49 UTC (rev 124898)
@@ -199,7 +199,121 @@
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(CCDelayBasedTimeSource, SaneHandlingOfJitteryTimebase)
+{
+ FakeCCThread thread;
+ FakeCCTimeSourceClient client;
+ double interval = 1.0 / 60.0;
+ RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::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->setTimebaseAndInterval(interval + 0.001, interval);
+ timer->setMonotonicallyIncreasingTime(interval);
+ thread.runPendingTask();
+
+ // Without double tick prevention, pendingDelayMs would be 1.
+ EXPECT_EQ(17, thread.pendingDelayMs());
+
+ // Jitter timebase ~1ms early
+ timer->setTimebaseAndInterval(interval * 2 - 0.001, interval);
+ timer->setMonotonicallyIncreasingTime(interval * 2);
+ thread.runPendingTask();
+
+ EXPECT_EQ(15, thread.pendingDelayMs());
+}
+
+TEST(CCDelayBasedTimeSource, HanldlesSignificantTimebaseChangesImmediately)
+{
+ FakeCCThread thread;
+ FakeCCTimeSourceClient client;
+ double interval = 1.0 / 60.0;
+ RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::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->setMonotonicallyIncreasingTime(interval);
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ client.reset();
+ thread.runPendingTaskOnOverwrite(true);
+ timer->setTimebaseAndInterval(interval + 0.0070001, 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->setMonotonicallyIncreasingTime(interval + 0.0070001);
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ client.reset();
+ thread.runPendingTaskOnOverwrite(true);
+ timer->setTimebaseAndInterval(interval, interval);
+ thread.runPendingTaskOnOverwrite(false);
+
+ EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled.
+ EXPECT_EQ(16-7, thread.pendingDelayMs());
+}
+
+TEST(CCDelayBasedTimeSource, HanldlesSignificantIntervalChangesImmediately)
+{
+ FakeCCThread thread;
+ FakeCCTimeSourceClient client;
+ double interval = 1.0 / 60.0;
+ RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::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->setMonotonicallyIncreasingTime(interval);
+ thread.runPendingTask();
+
+ EXPECT_EQ(16, thread.pendingDelayMs());
+
+ client.reset();
+ thread.runPendingTaskOnOverwrite(true);
+ timer->setTimebaseAndInterval(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->setMonotonicallyIncreasingTime(interval * 3);
+ thread.runPendingTask();
+
+ EXPECT_EQ(33, thread.pendingDelayMs());
+
+ client.reset();
+ thread.runPendingTaskOnOverwrite(true);
+ timer->setTimebaseAndInterval(interval * 3, interval);
+ thread.runPendingTaskOnOverwrite(false);
+
+ EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled.
+ EXPECT_EQ(16, thread.pendingDelayMs());
+}
+
TEST(CCDelayBasedTimeSourceTest, AchievesTargetRateWithNoNoise)
{
int numIterations = 1000;
Modified: trunk/Source/WebKit/chromium/tests/CCSchedulerTestCommon.h (124897 => 124898)
--- trunk/Source/WebKit/chromium/tests/CCSchedulerTestCommon.h 2012-08-07 18:17:31 UTC (rev 124897)
+++ trunk/Source/WebKit/chromium/tests/CCSchedulerTestCommon.h 2012-08-07 18:29:49 UTC (rev 124898)
@@ -52,8 +52,14 @@
{
m_pendingTaskDelay = 0;
m_pendingTask.clear();
+ m_runPendingTaskOnOverwrite = false;
}
+ void runPendingTaskOnOverwrite(bool enable)
+ {
+ m_runPendingTaskOnOverwrite = enable;
+ }
+
bool hasPendingTask() const { return m_pendingTask; }
void runPendingTask()
{
@@ -71,6 +77,9 @@
virtual void postTask(PassOwnPtr<Task>) { ASSERT_NOT_REACHED(); }
virtual void postDelayedTask(PassOwnPtr<Task> task, long long delay)
{
+ if (m_runPendingTaskOnOverwrite && hasPendingTask())
+ runPendingTask();
+
EXPECT_TRUE(!hasPendingTask());
m_pendingTask = task;
m_pendingTaskDelay = delay;
@@ -80,6 +89,7 @@
protected:
OwnPtr<Task> m_pendingTask;
long long m_pendingTaskDelay;
+ bool m_runPendingTaskOnOverwrite;
};
class FakeCCTimeSource : public WebCore::CCTimeSource {
@@ -93,6 +103,9 @@
virtual void setClient(WebCore::CCTimeSourceClient* client) OVERRIDE { m_client = client; }
virtual void setActive(bool b) OVERRIDE { m_active = b; }
virtual bool active() const OVERRIDE { return m_active; }
+ virtual void setTimebaseAndInterval(double timebase, double interval) OVERRIDE { }
+ virtual double lastTickTime() OVERRIDE { return 0; }
+ virtual double nextTickTime() OVERRIDE { return 0; }
void tick()
{