[Chromium] Schedule texture uploads based on hard-coded timer and vsync.
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 16 Aug 2012 19:42:21 +0000 (19:42 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 16 Aug 2012 19:42:21 +0000 (19:42 +0000)
https://bugs.webkit.org/show_bug.cgi?id=84281

Patch by David Reveman <reveman@chromium.org> on 2012-08-16
Reviewed by James Robinson.

Source/WebCore:

Improve interaction between vsync and texture uploads by performing
uploads in smaller batches and use a hard-coded timer to emulate
upload completion. This greatly reduces the chance of the compositor
missing a vsync due to being busy with texture uploads.

The CCScheduler client is now given a time limit when told to update
more resources. This time limit is passed to an instance of the
CCTextureUpdateController class, which is responsible for performing
texture updates until the limit is reached.

Unit tests: CCSchedulerTest.RequestCommit
            CCTextureUpdateControllerTest.UpdateMoreTextures
            CCTextureUpdateControllerTest.HasMoreUpdates

* platform/graphics/chromium/cc/CCFrameRateController.cpp:
(WebCore::CCFrameRateController::nextTickTime):
(WebCore):
* platform/graphics/chromium/cc/CCFrameRateController.h:
(CCFrameRateController):
* platform/graphics/chromium/cc/CCScheduler.cpp:
(WebCore::CCScheduler::processScheduledActions):
* platform/graphics/chromium/cc/CCScheduler.h:
(CCSchedulerClient):
* platform/graphics/chromium/cc/CCTextureUpdateController.cpp:
(WebCore::CCTextureUpdateController::maxPartialTextureUpdates):
(WebCore::CCTextureUpdateController::CCTextureUpdateController):
(WebCore::CCTextureUpdateController::updateMoreTextures):
(WebCore):
(WebCore::CCTextureUpdateController::onTimerFired):
(WebCore::CCTextureUpdateController::monotonicTimeNow):
(WebCore::CCTextureUpdateController::updateMoreTexturesTime):
(WebCore::CCTextureUpdateController::updateMoreTexturesSize):
(WebCore::CCTextureUpdateController::updateMoreTexturesIfEnoughTimeRemaining):
(WebCore::CCTextureUpdateController::updateMoreTexturesNow):
* platform/graphics/chromium/cc/CCTextureUpdateController.h:
(WebCore::CCTextureUpdateController::create):
(CCTextureUpdateController):
* platform/graphics/chromium/cc/CCThreadProxy.cpp:
(WebCore::CCThreadProxy::beginFrameCompleteOnImplThread):
(WebCore::CCThreadProxy::scheduledActionUpdateMoreResources):
* platform/graphics/chromium/cc/CCThreadProxy.h:

Source/WebKit/chromium:

* tests/CCSchedulerTest.cpp:
(WebKitTests::TEST):
* tests/CCSchedulerTestCommon.h:
(WebKitTests::FakeCCTimeSource::FakeCCTimeSource):
(WebKitTests::FakeCCTimeSource::setNextTickTime):
(FakeCCTimeSource):
* tests/CCTextureUpdateControllerTest.cpp:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@125800 268f45cc-cd09-0410-ab3c-d52691b4dbfc

13 files changed:
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/chromium/cc/CCFrameRateController.cpp
Source/WebCore/platform/graphics/chromium/cc/CCFrameRateController.h
Source/WebCore/platform/graphics/chromium/cc/CCScheduler.cpp
Source/WebCore/platform/graphics/chromium/cc/CCScheduler.h
Source/WebCore/platform/graphics/chromium/cc/CCTextureUpdateController.cpp
Source/WebCore/platform/graphics/chromium/cc/CCTextureUpdateController.h
Source/WebCore/platform/graphics/chromium/cc/CCThreadProxy.cpp
Source/WebCore/platform/graphics/chromium/cc/CCThreadProxy.h
Source/WebKit/chromium/ChangeLog
Source/WebKit/chromium/tests/CCSchedulerTest.cpp
Source/WebKit/chromium/tests/CCSchedulerTestCommon.h
Source/WebKit/chromium/tests/CCTextureUpdateControllerTest.cpp

index 7677088..65718e0 100644 (file)
@@ -1,3 +1,52 @@
+2012-08-16  David Reveman  <reveman@chromium.org>
+
+        [Chromium] Schedule texture uploads based on hard-coded timer and vsync.
+        https://bugs.webkit.org/show_bug.cgi?id=84281
+
+        Reviewed by James Robinson.
+
+        Improve interaction between vsync and texture uploads by performing
+        uploads in smaller batches and use a hard-coded timer to emulate
+        upload completion. This greatly reduces the chance of the compositor
+        missing a vsync due to being busy with texture uploads.
+
+        The CCScheduler client is now given a time limit when told to update
+        more resources. This time limit is passed to an instance of the
+        CCTextureUpdateController class, which is responsible for performing
+        texture updates until the limit is reached.
+
+        Unit tests: CCSchedulerTest.RequestCommit
+                    CCTextureUpdateControllerTest.UpdateMoreTextures
+                    CCTextureUpdateControllerTest.HasMoreUpdates
+
+        * platform/graphics/chromium/cc/CCFrameRateController.cpp:
+        (WebCore::CCFrameRateController::nextTickTime):
+        (WebCore):
+        * platform/graphics/chromium/cc/CCFrameRateController.h:
+        (CCFrameRateController):
+        * platform/graphics/chromium/cc/CCScheduler.cpp:
+        (WebCore::CCScheduler::processScheduledActions):
+        * platform/graphics/chromium/cc/CCScheduler.h:
+        (CCSchedulerClient):
+        * platform/graphics/chromium/cc/CCTextureUpdateController.cpp:
+        (WebCore::CCTextureUpdateController::maxPartialTextureUpdates):
+        (WebCore::CCTextureUpdateController::CCTextureUpdateController):
+        (WebCore::CCTextureUpdateController::updateMoreTextures):
+        (WebCore):
+        (WebCore::CCTextureUpdateController::onTimerFired):
+        (WebCore::CCTextureUpdateController::monotonicTimeNow):
+        (WebCore::CCTextureUpdateController::updateMoreTexturesTime):
+        (WebCore::CCTextureUpdateController::updateMoreTexturesSize):
+        (WebCore::CCTextureUpdateController::updateMoreTexturesIfEnoughTimeRemaining):
+        (WebCore::CCTextureUpdateController::updateMoreTexturesNow):
+        * platform/graphics/chromium/cc/CCTextureUpdateController.h:
+        (WebCore::CCTextureUpdateController::create):
+        (CCTextureUpdateController):
+        * platform/graphics/chromium/cc/CCThreadProxy.cpp:
+        (WebCore::CCThreadProxy::beginFrameCompleteOnImplThread):
+        (WebCore::CCThreadProxy::scheduledActionUpdateMoreResources):
+        * platform/graphics/chromium/cc/CCThreadProxy.h:
+
 2012-08-16  Dana Jansens  <danakj@chromium.org>
 
         [chromium] Impl scrolling crashes when the renderer's initialization failed
index f19b39e..6e7d765 100644 (file)
@@ -29,6 +29,7 @@
 #include "TraceEvent.h"
 #include "cc/CCDelayBasedTimeSource.h"
 #include "cc/CCTimeSource.h"
+#include <wtf/CurrentTime.h>
 
 namespace WebCore {
 
@@ -147,4 +148,12 @@ void CCFrameRateController::didAbortAllPendingFrames()
     m_numFramesPending = 0;
 }
 
+double CCFrameRateController::nextTickTime()
+{
+    if (m_isTimeSourceThrottling)
+        return m_timeSource->nextTickTime();
+
+    return monotonicallyIncreasingTime();
+}
+
 }
index 69c9037..fc2d6ca 100644 (file)
@@ -27,7 +27,6 @@
 
 #include "cc/CCTimer.h"
 
-#include <wtf/CurrentTime.h>
 #include <wtf/Deque.h>
 #include <wtf/OwnPtr.h>
 #include <wtf/PassOwnPtr.h>
@@ -68,6 +67,7 @@ public:
     void didFinishFrame();
     void didAbortAllPendingFrames();
     void setMaxFramesPending(int); // 0 for unlimited.
+    double nextTickTime();
 
     void setTimebaseAndInterval(double timebase, double intervalSeconds);
 
index 2c1b505..bb23728 100644 (file)
@@ -173,16 +173,11 @@ void CCScheduler::processScheduledActions()
             m_client->scheduledActionBeginFrame();
             break;
         case CCSchedulerStateMachine::ACTION_BEGIN_UPDATE_MORE_RESOURCES:
-            m_client->scheduledActionUpdateMoreResources();
-            if (!m_client->hasMoreResourceUpdates()) {
-                // If we were just told to update resources, but there are no
-                // more pending, then tell the state machine that the
-                // beginUpdateMoreResources completed. If more are pending,
-                // then we will ack the update at the next draw.
-                m_updateMoreResourcesPending = false;
-                m_stateMachine.beginUpdateMoreResourcesComplete(false);
-            } else
+            if (m_client->hasMoreResourceUpdates()) {
+                m_client->scheduledActionUpdateMoreResources(m_frameRateController->nextTickTime());
                 m_updateMoreResourcesPending = true;
+            } else
+                m_stateMachine.beginUpdateMoreResourcesComplete(false);
             break;
         case CCSchedulerStateMachine::ACTION_COMMIT:
             m_client->scheduledActionCommit();
index a7d8b03..ef81014 100644 (file)
@@ -58,7 +58,7 @@ public:
     virtual void scheduledActionBeginFrame() = 0;
     virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapIfPossible() = 0;
     virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapForced() = 0;
-    virtual void scheduledActionUpdateMoreResources() = 0;
+    virtual void scheduledActionUpdateMoreResources(double monotonicTimeLimit) = 0;
     virtual void scheduledActionCommit() = 0;
     virtual void scheduledActionBeginContextRecreation() = 0;
     virtual void scheduledActionAcquireLayerTexturesForMainThread() = 0;
index dde8949..7148621 100644 (file)
 
 namespace {
 
-// Number of textures to update with each call to updateMoreTextures().
-static const size_t textureUpdatesPerFrame = 48;
+// Number of textures to update with each call to updateMoreTexturesIfEnoughTimeRemaining().
+static const size_t textureUpdatesPerTick = 12;
+
+// Measured in seconds.
+static const double textureUpdateTickRate = 0.004;
 
 // Flush interval when performing texture uploads.
 static const int textureUploadFlushPeriod = 4;
@@ -46,7 +49,7 @@ namespace WebCore {
 
 size_t CCTextureUpdateController::maxPartialTextureUpdates()
 {
-    return textureUpdatesPerFrame;
+    return textureUpdatesPerTick;
 }
 
 void CCTextureUpdateController::updateTextures(CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader, CCTextureUpdateQueue* queue, size_t count)
@@ -110,11 +113,14 @@ void CCTextureUpdateController::updateTextures(CCResourceProvider* resourceProvi
         copier->flush();
 }
 
-CCTextureUpdateController::CCTextureUpdateController(PassOwnPtr<CCTextureUpdateQueue> queue, CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader)
-    : m_queue(queue)
+CCTextureUpdateController::CCTextureUpdateController(CCThread* thread, PassOwnPtr<CCTextureUpdateQueue> queue, CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader)
+    : m_timer(adoptPtr(new CCTimer(thread, this)))
+    , m_queue(queue)
     , m_resourceProvider(resourceProvider)
     , m_copier(copier)
     , m_uploader(uploader)
+    , m_monotonicTimeLimit(0)
+    , m_firstUpdateAttempt(true)
 {
 }
 
@@ -127,17 +133,57 @@ bool CCTextureUpdateController::hasMoreUpdates() const
     return m_queue->hasMoreUpdates();
 }
 
-void CCTextureUpdateController::updateMoreTextures()
+void CCTextureUpdateController::updateMoreTextures(double monotonicTimeLimit)
 {
+    m_monotonicTimeLimit = monotonicTimeLimit;
+
     if (!m_queue->hasMoreUpdates())
         return;
 
-    updateTextures(m_resourceProvider, m_copier, m_uploader, m_queue.get(), updateMoreTexturesSize());
+    // Call updateMoreTexturesNow() directly unless it's the first update
+    // attempt. This ensures that we empty the update queue in a finite
+    // amount of time.
+    if (m_firstUpdateAttempt) {
+        updateMoreTexturesIfEnoughTimeRemaining();
+        m_firstUpdateAttempt = false;
+    } else
+        updateMoreTexturesNow();
+}
+
+void CCTextureUpdateController::onTimerFired()
+{
+    if (!m_queue->hasMoreUpdates())
+        return;
+
+    updateMoreTexturesIfEnoughTimeRemaining();
+}
+
+double CCTextureUpdateController::monotonicTimeNow() const
+{
+    return monotonicallyIncreasingTime();
+}
+
+double CCTextureUpdateController::updateMoreTexturesTime() const
+{
+    return textureUpdateTickRate;
 }
 
 size_t CCTextureUpdateController::updateMoreTexturesSize() const
 {
-    return textureUpdatesPerFrame;
+    return textureUpdatesPerTick;
+}
+
+void CCTextureUpdateController::updateMoreTexturesIfEnoughTimeRemaining()
+{
+    bool hasTimeRemaining = monotonicTimeNow() < m_monotonicTimeLimit - updateMoreTexturesTime();
+    if (hasTimeRemaining)
+        updateMoreTexturesNow();
+}
+
+void CCTextureUpdateController::updateMoreTexturesNow()
+{
+    m_timer->startOneShot(updateMoreTexturesTime());
+    updateTextures(m_resourceProvider, m_copier, m_uploader, m_queue.get(), updateMoreTexturesSize());
 }
 
 }
index d6cda59..e33f095 100644 (file)
@@ -27,6 +27,7 @@
 #define CCTextureUpdateController_h
 
 #include "cc/CCTextureUpdateQueue.h"
+#include "cc/CCTimer.h"
 #include <wtf/Noncopyable.h>
 #include <wtf/OwnPtr.h>
 
@@ -35,12 +36,12 @@ namespace WebCore {
 class TextureCopier;
 class TextureUploader;
 
-class CCTextureUpdateController {
+class CCTextureUpdateController : public CCTimerClient {
     WTF_MAKE_NONCOPYABLE(CCTextureUpdateController);
 public:
-    static PassOwnPtr<CCTextureUpdateController> create(PassOwnPtr<CCTextureUpdateQueue> queue, CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader)
+    static PassOwnPtr<CCTextureUpdateController> create(CCThread* thread, PassOwnPtr<CCTextureUpdateQueue> queue, CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader)
     {
-        return adoptPtr(new CCTextureUpdateController(queue, resourceProvider, copier, uploader));
+        return adoptPtr(new CCTextureUpdateController(thread, queue, resourceProvider, copier, uploader));
     }
     static size_t maxPartialTextureUpdates();
     static void updateTextures(CCResourceProvider*, TextureCopier*, TextureUploader*, CCTextureUpdateQueue*, size_t count);
@@ -48,19 +49,30 @@ public:
     virtual ~CCTextureUpdateController();
 
     bool hasMoreUpdates() const;
-    void updateMoreTextures();
+    void updateMoreTextures(double monotonicTimeLimit);
+
+    // CCTimerClient implementation.
+    virtual void onTimerFired() OVERRIDE;
 
     // Virtual for testing.
+    virtual double monotonicTimeNow() const;
+    virtual double updateMoreTexturesTime() const;
     virtual size_t updateMoreTexturesSize() const;
 
 protected:
-    CCTextureUpdateController(PassOwnPtr<CCTextureUpdateQueue>, CCResourceProvider*, TextureCopier*, TextureUploader*);
+    CCTextureUpdateController(CCThread*, PassOwnPtr<CCTextureUpdateQueue>, CCResourceProvider*, TextureCopier*, TextureUploader*);
+
+    void updateMoreTexturesIfEnoughTimeRemaining();
+    void updateMoreTexturesNow();
 
+    OwnPtr<CCTimer> m_timer;
     OwnPtr<CCTextureUpdateQueue> m_queue;
     bool m_contentsTexturesPurged;
     CCResourceProvider* m_resourceProvider;
     TextureCopier* m_copier;
     TextureUploader* m_uploader;
+    double m_monotonicTimeLimit;
+    bool m_firstUpdateAttempt;
 };
 
 }
index 7d5d4b6..ed0a8cd 100644 (file)
@@ -577,7 +577,7 @@ void CCThreadProxy::beginFrameCompleteOnImplThread(CCCompletionEvent* completion
     } else
         m_resetContentsTexturesPurgedAfterCommitOnImplThread = true;
 
-    m_currentTextureUpdateControllerOnImplThread = CCTextureUpdateController::create(queue, m_layerTreeHostImpl->resourceProvider(), m_layerTreeHostImpl->layerRenderer()->textureCopier(), m_layerTreeHostImpl->layerRenderer()->textureUploader());
+    m_currentTextureUpdateControllerOnImplThread = CCTextureUpdateController::create(CCProxy::implThread(), queue, m_layerTreeHostImpl->resourceProvider(), m_layerTreeHostImpl->layerRenderer()->textureCopier(), m_layerTreeHostImpl->layerRenderer()->textureUploader());
     m_commitCompletionEventOnImplThread = completion;
 
     m_schedulerOnImplThread->beginFrameComplete();
@@ -608,11 +608,11 @@ bool CCThreadProxy::canDraw()
     return m_layerTreeHostImpl->canDraw();
 }
 
-void CCThreadProxy::scheduledActionUpdateMoreResources()
+void CCThreadProxy::scheduledActionUpdateMoreResources(double monotonicTimeLimit)
 {
     TRACE_EVENT0("cc", "CCThreadProxy::scheduledActionUpdateMoreResources");
     ASSERT(m_currentTextureUpdateControllerOnImplThread);
-    m_currentTextureUpdateControllerOnImplThread->updateMoreTextures();
+    m_currentTextureUpdateControllerOnImplThread->updateMoreTextures(monotonicTimeLimit);
 }
 
 void CCThreadProxy::scheduledActionCommit()
index ddb4d90..12e606d 100644 (file)
@@ -88,7 +88,7 @@ public:
     virtual void scheduledActionBeginFrame() OVERRIDE;
     virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapIfPossible() OVERRIDE;
     virtual CCScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapForced() OVERRIDE;
-    virtual void scheduledActionUpdateMoreResources() OVERRIDE;
+    virtual void scheduledActionUpdateMoreResources(double monotonicTimeLimit) OVERRIDE;
     virtual void scheduledActionCommit() OVERRIDE;
     virtual void scheduledActionBeginContextRecreation() OVERRIDE;
     virtual void scheduledActionAcquireLayerTexturesForMainThread() OVERRIDE;
index a589ae5..6f9ee11 100644 (file)
@@ -1,3 +1,18 @@
+2012-08-16  David Reveman  <reveman@chromium.org>
+
+        [Chromium] Schedule texture uploads based on hard-coded timer and vsync.
+        https://bugs.webkit.org/show_bug.cgi?id=84281
+
+        Reviewed by James Robinson.
+
+        * tests/CCSchedulerTest.cpp:
+        (WebKitTests::TEST):
+        * tests/CCSchedulerTestCommon.h:
+        (WebKitTests::FakeCCTimeSource::FakeCCTimeSource):
+        (WebKitTests::FakeCCTimeSource::setNextTickTime):
+        (FakeCCTimeSource):
+        * tests/CCTextureUpdateControllerTest.cpp:
+
 2012-08-16  Dana Jansens  <danakj@chromium.org>
 
         [chromium] Impl scrolling crashes when the renderer's initialization failed
index 4125088..848a586 100644 (file)
@@ -81,7 +81,7 @@ public:
         return CCScheduledActionDrawAndSwapResult(true, m_swapWillHappenIfDrawHappens);
     }
 
-    virtual void scheduledActionUpdateMoreResources() OVERRIDE { m_actions.push_back("scheduledActionUpdateMoreResources"); }
+    virtual void scheduledActionUpdateMoreResources(double) OVERRIDE { m_actions.push_back("scheduledActionUpdateMoreResources"); }
     virtual void scheduledActionCommit() OVERRIDE { m_actions.push_back("scheduledActionCommit"); }
     virtual void scheduledActionBeginContextRecreation() OVERRIDE { m_actions.push_back("scheduledActionBeginContextRecreation"); }
     virtual void scheduledActionAcquireLayerTexturesForMainThread() OVERRIDE { m_actions.push_back("scheduledActionAcquireLayerTexturesForMainThread"); }
@@ -114,12 +114,10 @@ TEST(CCSchedulerTest, RequestCommit)
     client.reset();
 
     // Since, hasMoreResourceUpdates is set to false,
-    // beginFrameComplete should updateMoreResources, then
-    // commit
+    // beginFrameComplete should commit
     scheduler->beginFrameComplete();
-    EXPECT_EQ(2, client.numActions());
-    EXPECT_STREQ("scheduledActionUpdateMoreResources", client.action(0));
-    EXPECT_STREQ("scheduledActionCommit", client.action(1));
+    EXPECT_EQ(1, client.numActions());
+    EXPECT_STREQ("scheduledActionCommit", client.action(0));
     EXPECT_TRUE(timeSource->active());
     client.reset();
 
@@ -152,12 +150,10 @@ TEST(CCSchedulerTest, RequestCommitAfterBeginFrame)
     scheduler->setNeedsCommit();
 
     // Since, hasMoreResourceUpdates is set to false, and another commit is
-    // needed, beginFrameComplete should updateMoreResources, then commit, then
-    // begin another frame.
+    // needed, beginFrameComplete should commit, then begin another frame.
     scheduler->beginFrameComplete();
-    EXPECT_EQ(2, client.numActions());
-    EXPECT_STREQ("scheduledActionUpdateMoreResources", client.action(0));
-    EXPECT_STREQ("scheduledActionCommit", client.action(1));
+    EXPECT_EQ(1, client.numActions());
+    EXPECT_STREQ("scheduledActionCommit", client.action(0));
     client.reset();
 
     // Tick should draw but then begin another frame.
@@ -257,7 +253,7 @@ public:
         return CCScheduledActionDrawAndSwapResult(true, true);
     }
 
-    virtual void scheduledActionUpdateMoreResources() OVERRIDE { }
+    virtual void scheduledActionUpdateMoreResources(double) OVERRIDE { }
     virtual void scheduledActionCommit() OVERRIDE { }
     virtual void scheduledActionBeginContextRecreation() OVERRIDE { }
 
@@ -357,7 +353,7 @@ public:
         return CCScheduledActionDrawAndSwapResult(true, true);
     }
 
-    virtual void scheduledActionUpdateMoreResources() OVERRIDE { }
+    virtual void scheduledActionUpdateMoreResources(double) OVERRIDE { }
     virtual void scheduledActionCommit() OVERRIDE { }
     virtual void scheduledActionBeginContextRecreation() OVERRIDE { }
 
index beb1655..d9dd5e5 100644 (file)
@@ -96,6 +96,7 @@ class FakeCCTimeSource : public WebCore::CCTimeSource {
 public:
     FakeCCTimeSource()
         : m_active(false)
+        , m_nextTickTime(0)
         , m_client(0) { }
 
     virtual ~FakeCCTimeSource() { }
@@ -114,8 +115,11 @@ public:
             m_client->onTimerTick();
     }
 
+    void setNextTickTime(double nextTickTime) { m_nextTickTime = nextTickTime; }
+
 protected:
     bool m_active;
+    double m_nextTickTime;
     WebCore::CCTimeSourceClient* m_client;
 };
 
index a0dc2ec..695832f 100644 (file)
@@ -542,4 +542,148 @@ TEST_F(CCTextureUpdateControllerTest, TripleUpdateFinalUpdateAllPartial)
     EXPECT_EQ(kFullUploads + kPartialUploads, m_numTotalUploads);
 }
 
+class FakeCCTextureUpdateController : public WebCore::CCTextureUpdateController {
+public:
+    static PassOwnPtr<FakeCCTextureUpdateController> create(WebCore::CCThread* thread, PassOwnPtr<CCTextureUpdateQueue> queue, CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader)
+    {
+        return adoptPtr(new FakeCCTextureUpdateController(thread, queue, resourceProvider, copier, uploader));
+    }
+
+    void setMonotonicTimeNow(double time) { m_monotonicTimeNow = time; }
+    virtual double monotonicTimeNow() const OVERRIDE { return m_monotonicTimeNow; }
+    void setUpdateMoreTexturesTime(double time) { m_updateMoreTexturesTime = time; }
+    virtual double updateMoreTexturesTime() const OVERRIDE { return m_updateMoreTexturesTime; }
+    void setUpdateMoreTexturesSize(size_t size) { m_updateMoreTexturesSize = size; }
+    virtual size_t updateMoreTexturesSize() const OVERRIDE { return m_updateMoreTexturesSize; }
+
+protected:
+    FakeCCTextureUpdateController(WebCore::CCThread* thread, PassOwnPtr<CCTextureUpdateQueue> queue, CCResourceProvider* resourceProvider, TextureCopier* copier, TextureUploader* uploader)
+        : WebCore::CCTextureUpdateController(thread, queue, resourceProvider, copier, uploader)
+        , m_monotonicTimeNow(0)
+        , m_updateMoreTexturesTime(0)
+        , m_updateMoreTexturesSize(0) { }
+
+    double m_monotonicTimeNow;
+    double m_updateMoreTexturesTime;
+    size_t m_updateMoreTexturesSize;
+};
+
+TEST_F(CCTextureUpdateControllerTest, UpdateMoreTextures)
+{
+    FakeCCThread thread;
+
+    setMaxUploadCountPerUpdate(1);
+    appendFullUploadsToUpdateQueue(3);
+    appendPartialUploadsToUpdateQueue(0);
+
+    DebugScopedSetImplThread implThread;
+    OwnPtr<FakeCCTextureUpdateController> controller(FakeCCTextureUpdateController::create(&thread, m_queue.release(), m_resourceProvider.get(), &m_copier, &m_uploader));
+
+    controller->setMonotonicTimeNow(0);
+    controller->setUpdateMoreTexturesTime(0.1);
+    controller->setUpdateMoreTexturesSize(1);
+    // Not enough time for any updates.
+    controller->updateMoreTextures(0.09);
+    EXPECT_FALSE(thread.hasPendingTask());
+    EXPECT_EQ(0, m_numBeginUploads);
+    EXPECT_EQ(0, m_numEndUploads);
+
+    thread.reset();
+    controller->setMonotonicTimeNow(0);
+    controller->setUpdateMoreTexturesTime(0.1);
+    controller->setUpdateMoreTexturesSize(1);
+    // Only enough time for 1 update.
+    controller->updateMoreTextures(0.12);
+    EXPECT_TRUE(thread.hasPendingTask());
+    controller->setMonotonicTimeNow(thread.pendingDelayMs() / 1000.0);
+    thread.runPendingTask();
+    EXPECT_EQ(1, m_numBeginUploads);
+    EXPECT_EQ(1, m_numEndUploads);
+    EXPECT_EQ(1, m_numTotalUploads);
+
+    thread.reset();
+    controller->setMonotonicTimeNow(0);
+    controller->setUpdateMoreTexturesTime(0.1);
+    controller->setUpdateMoreTexturesSize(1);
+    // Enough time for 2 updates.
+    controller->updateMoreTextures(0.22);
+    EXPECT_TRUE(thread.hasPendingTask());
+    controller->setMonotonicTimeNow(controller->monotonicTimeNow() + thread.pendingDelayMs() / 1000.0);
+    thread.runPendingTask();
+    EXPECT_EQ(3, m_numBeginUploads);
+    EXPECT_EQ(3, m_numEndUploads);
+    EXPECT_EQ(3, m_numTotalUploads);
+}
+
+TEST_F(CCTextureUpdateControllerTest, NoMoreUpdates)
+{
+    FakeCCThread thread;
+
+    setMaxUploadCountPerUpdate(1);
+    appendFullUploadsToUpdateQueue(2);
+    appendPartialUploadsToUpdateQueue(0);
+
+    DebugScopedSetImplThread implThread;
+    OwnPtr<FakeCCTextureUpdateController> controller(FakeCCTextureUpdateController::create(&thread, m_queue.release(), m_resourceProvider.get(), &m_copier, &m_uploader));
+
+    controller->setMonotonicTimeNow(0);
+    controller->setUpdateMoreTexturesTime(0.1);
+    controller->setUpdateMoreTexturesSize(1);
+    // Enough time for 3 updates but only 2 necessary.
+    controller->updateMoreTextures(0.31);
+    EXPECT_TRUE(thread.hasPendingTask());
+    controller->setMonotonicTimeNow(controller->monotonicTimeNow() + thread.pendingDelayMs() / 1000.0);
+    thread.runPendingTask();
+    EXPECT_TRUE(thread.hasPendingTask());
+    controller->setMonotonicTimeNow(controller->monotonicTimeNow() + thread.pendingDelayMs() / 1000.0);
+    thread.runPendingTask();
+    EXPECT_EQ(2, m_numBeginUploads);
+    EXPECT_EQ(2, m_numEndUploads);
+    EXPECT_EQ(2, m_numTotalUploads);
+
+    thread.reset();
+    controller->setMonotonicTimeNow(0);
+    controller->setUpdateMoreTexturesTime(0.1);
+    controller->setUpdateMoreTexturesSize(1);
+    // Enough time for updates but no more updates left.
+    controller->updateMoreTextures(0.31);
+    EXPECT_FALSE(thread.hasPendingTask());
+    EXPECT_EQ(2, m_numBeginUploads);
+    EXPECT_EQ(2, m_numEndUploads);
+    EXPECT_EQ(2, m_numTotalUploads);
+}
+
+TEST_F(CCTextureUpdateControllerTest, UpdatesCompleteInFiniteTime)
+{
+    FakeCCThread thread;
+
+    setMaxUploadCountPerUpdate(1);
+    appendFullUploadsToUpdateQueue(2);
+    appendPartialUploadsToUpdateQueue(0);
+
+    DebugScopedSetImplThread implThread;
+    OwnPtr<FakeCCTextureUpdateController> controller(FakeCCTextureUpdateController::create(&thread, m_queue.release(), m_resourceProvider.get(), &m_copier, &m_uploader));
+
+    controller->setMonotonicTimeNow(0);
+    controller->setUpdateMoreTexturesTime(0.5);
+    controller->setUpdateMoreTexturesSize(1);
+
+    for (int i = 0; i < 100; i++) {
+        if (!controller->hasMoreUpdates())
+            break;
+
+        // Not enough time for any updates.
+        controller->updateMoreTextures(0.4);
+
+        if (thread.hasPendingTask()) {
+            controller->setMonotonicTimeNow(controller->monotonicTimeNow() + thread.pendingDelayMs() / 1000.0);
+            thread.runPendingTask();
+        }
+    }
+
+    EXPECT_EQ(2, m_numBeginUploads);
+    EXPECT_EQ(2, m_numEndUploads);
+    EXPECT_EQ(2, m_numTotalUploads);
+}
+
 } // namespace