Title: [124898] trunk/Source
Revision
124898
Author
[email protected]
Date
2012-08-07 11:29:49 -0700 (Tue, 07 Aug 2012)

Log Message

Add CCDelayBasedTimeSource::setTimebaseAndInterval
https://bugs.webkit.org/show_bug.cgi?id=92825

Patch by Brian Anderson <[email protected]> on 2012-08-07
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:

Modified Paths

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()
     {
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to