Title: [130994] trunk/Source
Revision
130994
Author
commit-qu...@webkit.org
Date
2012-10-10 19:05:15 -0700 (Wed, 10 Oct 2012)

Log Message

[Chromium] Smoother animation for non-RAF 2D canvas animations
https://bugs.webkit.org/show_bug.cgi?id=97918

Patch by Justin Novosad <ju...@chromium.org> on 2012-10-10
Reviewed by Stephen White.

Source/WebCore:

Reduces animation jank by preventing the accumulation of multiple
frames of backlog in the deferred canvas rendering queue. When the
animation is more than one full frame ahead of the compositor, an
immediate flush is triggered.  This takes into account overdraw
elimination by the skip-on-clear optimization that is built-in to
SkDeferredCanvas, to allow non-RAF animations that clear the canvas at
each frame to run without any rate limiting. This change also
incidentally improves frame rate in many cases by providing more
granular batching of GPU API calls, resulting in better pipelining
through the command buffer.

Test: webkit_unit_test Canvas2DLayerManagerTest.testDeferredFrame

* platform/graphics/chromium/Canvas2DLayerBridge.cpp:
(WebCore::Canvas2DLayerBridge::Canvas2DLayerBridge):
(WebCore::Canvas2DLayerBridge::limitPendingFrames):
Called at the end of a task (usually a scheduled script action) that
invoked 2d canvas rendering context methods. The end of the task
marks the completion of a displayable frame. This method will trigger
a flush if it detects that the layer has pending draw commands that
are more that one frame old.
(WebCore):
(WebCore::Canvas2DLayerBridge::flushedDrawCommands):
(WebCore::Canvas2DLayerBridge::didFlushPendingCommands):
(WebCore::Canvas2DLayerBridge::skippedPendingDrawCommands):
(WebCore::Canvas2DLayerBridge::flush):
(WebCore::Canvas2DLayerBridge::contextAcquired):
* platform/graphics/chromium/Canvas2DLayerBridge.h:
(Canvas2DLayerBridge):
* platform/graphics/chromium/Canvas2DLayerManager.cpp:
(WebCore::Canvas2DLayerManager::~Canvas2DLayerManager):
(WebCore::Canvas2DLayerManager::willProcessTask):
(WebCore):
(WebCore::Canvas2DLayerManager::didProcessTask):
(WebCore::Canvas2DLayerManager::layerDidDraw):
* platform/graphics/chromium/Canvas2DLayerManager.h:
(Canvas2DLayerManager):

Source/WebKit/chromium:

New unit test to verify the automatic flushing of deferred 2d canvas
layers when scripted animations are more than one full frame ahead
of the compositor. Also verifies that automatic flushing does not
kick-in when the skip on clear optimization fires

* DEPS:
* tests/Canvas2DLayerManagerTest.cpp:
(FakeCanvas2DLayerBridge::fakeSkipPendingDrawCommands):
To fake the skip on clear optimization
(FakeCanvas2DLayerBridge):
(Canvas2DLayerManagerTest):
(Canvas2DLayerManagerTest::doDeferredFrameTestTask):
Method that simulates a _javascript_ scheduled action that draws to
a 2D canvas layer
(DeferredFrameTestTask):
Task object to simulate a scheduled action. This is so the unit
test can cover the TaskObserver behavior of Canvas2DLayerManager.
(Canvas2DLayerManagerTest::DeferredFrameTestTask::DeferredFrameTestTask):
(Canvas2DLayerManagerTest::deferredFrameTest):
The unit test implementation, which uses the WebThread run loop to
post tasks that touch the canvas layer.

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (130993 => 130994)


--- trunk/Source/WebCore/ChangeLog	2012-10-11 01:52:04 UTC (rev 130993)
+++ trunk/Source/WebCore/ChangeLog	2012-10-11 02:05:15 UTC (rev 130994)
@@ -1,3 +1,48 @@
+2012-10-10  Justin Novosad  <ju...@chromium.org>
+
+        [Chromium] Smoother animation for non-RAF 2D canvas animations
+        https://bugs.webkit.org/show_bug.cgi?id=97918
+
+        Reviewed by Stephen White.
+
+        Reduces animation jank by preventing the accumulation of multiple
+        frames of backlog in the deferred canvas rendering queue. When the
+        animation is more than one full frame ahead of the compositor, an
+        immediate flush is triggered.  This takes into account overdraw
+        elimination by the skip-on-clear optimization that is built-in to
+        SkDeferredCanvas, to allow non-RAF animations that clear the canvas at
+        each frame to run without any rate limiting. This change also
+        incidentally improves frame rate in many cases by providing more
+        granular batching of GPU API calls, resulting in better pipelining
+        through the command buffer.
+
+        Test: webkit_unit_test Canvas2DLayerManagerTest.testDeferredFrame
+
+        * platform/graphics/chromium/Canvas2DLayerBridge.cpp:
+        (WebCore::Canvas2DLayerBridge::Canvas2DLayerBridge):
+        (WebCore::Canvas2DLayerBridge::limitPendingFrames):
+        Called at the end of a task (usually a scheduled script action) that
+        invoked 2d canvas rendering context methods. The end of the task
+        marks the completion of a displayable frame. This method will trigger
+        a flush if it detects that the layer has pending draw commands that
+        are more that one frame old.
+        (WebCore):
+        (WebCore::Canvas2DLayerBridge::flushedDrawCommands):
+        (WebCore::Canvas2DLayerBridge::didFlushPendingCommands):
+        (WebCore::Canvas2DLayerBridge::skippedPendingDrawCommands):
+        (WebCore::Canvas2DLayerBridge::flush):
+        (WebCore::Canvas2DLayerBridge::contextAcquired):
+        * platform/graphics/chromium/Canvas2DLayerBridge.h:
+        (Canvas2DLayerBridge):
+        * platform/graphics/chromium/Canvas2DLayerManager.cpp:
+        (WebCore::Canvas2DLayerManager::~Canvas2DLayerManager):
+        (WebCore::Canvas2DLayerManager::willProcessTask):
+        (WebCore):
+        (WebCore::Canvas2DLayerManager::didProcessTask):
+        (WebCore::Canvas2DLayerManager::layerDidDraw):
+        * platform/graphics/chromium/Canvas2DLayerManager.h:
+        (Canvas2DLayerManager):
+
 2012-10-10  Beth Dakin  <bda...@apple.com>
 
         https://bugs.webkit.org/show_bug.cgi?id=98968

Modified: trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerBridge.cpp (130993 => 130994)


--- trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerBridge.cpp	2012-10-11 01:52:04 UTC (rev 130993)
+++ trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerBridge.cpp	2012-10-11 02:05:15 UTC (rev 130994)
@@ -51,6 +51,8 @@
     , m_canvas(0)
     , m_context(context)
     , m_bytesAllocated(0)
+    , m_didRecordDrawCommand(false)
+    , m_framesPending(0)
     , m_next(0)
     , m_prev(0)
 {
@@ -106,6 +108,16 @@
     return 0;
 }
 
+void Canvas2DLayerBridge::limitPendingFrames()
+{
+    if (m_didRecordDrawCommand) {
+        m_framesPending++;
+        m_didRecordDrawCommand = false;
+        if (m_framesPending > 1)
+            flush();
+    }
+}
+
 void Canvas2DLayerBridge::prepareForDraw()
 {
     ASSERT(deferredCanvas());
@@ -122,11 +134,22 @@
     Canvas2DLayerManager::get().layerAllocatedStorageChanged(this, delta);
 }
 
+size_t Canvas2DLayerBridge::storageAllocatedForRecording()
+{
+    return deferredCanvas()->storageAllocatedForRecording();
+}
+
 void Canvas2DLayerBridge::flushedDrawCommands()
 {
-    storageAllocatedForRecordingChanged(deferredCanvas()->storageAllocatedForRecording());
+    storageAllocatedForRecordingChanged(storageAllocatedForRecording());
+    m_framesPending = 0;
 }
 
+void Canvas2DLayerBridge::skippedPendingDrawCommands()
+{
+    flushedDrawCommands();
+}
+
 size_t Canvas2DLayerBridge::freeMemoryIfPossible(size_t bytesToFree)
 {
     ASSERT(deferredCanvas());
@@ -140,7 +163,8 @@
 void Canvas2DLayerBridge::flush()
 {
     ASSERT(deferredCanvas());
-    m_canvas->flush();
+    if (deferredCanvas()->hasPendingCommands())
+        m_canvas->flush();
 }
 
 SkCanvas* Canvas2DLayerBridge::skCanvas(SkDevice* device)
@@ -196,8 +220,10 @@
 {
     if (m_deferralMode == NonDeferred && !m_useDoubleBuffering)
         m_layer->willModifyTexture();
-    else if (m_deferralMode == Deferred)
+    else if (m_deferralMode == Deferred) {
         Canvas2DLayerManager::get().layerDidDraw(this);
+        m_didRecordDrawCommand = true;
+    }
 }
 
 unsigned Canvas2DLayerBridge::backBufferTexture()

Modified: trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerBridge.h (130993 => 130994)


--- trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerBridge.h	2012-10-11 01:52:04 UTC (rev 130993)
+++ trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerBridge.h	2012-10-11 02:05:15 UTC (rev 130994)
@@ -62,11 +62,14 @@
     virtual void prepareForDraw() OVERRIDE;
     virtual void storageAllocatedForRecordingChanged(size_t) OVERRIDE;
     virtual void flushedDrawCommands() OVERRIDE;
+    virtual void skippedPendingDrawCommands() OVERRIDE;
 
     // Methods used by Canvas2DLayerManager
     virtual size_t freeMemoryIfPossible(size_t); // virtual for mocking
     virtual void flush(); // virtual for mocking
+    virtual size_t storageAllocatedForRecording(); // virtual for faking
     size_t bytesAllocated() const {return m_bytesAllocated;}
+    void limitPendingFrames();
 
     SkCanvas* skCanvas(SkDevice*);
     WebKit::WebLayer* layer();
@@ -87,6 +90,8 @@
     OwnPtr<WebKit::WebExternalTextureLayer> m_layer;
     RefPtr<GraphicsContext3D> m_context;
     size_t m_bytesAllocated;
+    bool m_didRecordDrawCommand;
+    int m_framesPending;
 
     friend class WTF::DoublyLinkedListNode<Canvas2DLayerBridge>;
     Canvas2DLayerBridge* m_next;

Modified: trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.cpp (130993 => 130994)


--- trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.cpp	2012-10-11 01:52:04 UTC (rev 130993)
+++ trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.cpp	2012-10-11 02:05:15 UTC (rev 130994)
@@ -25,8 +25,11 @@
 
 #include "Canvas2DLayerManager.h"
 
+#include <public/Platform.h>
 #include <wtf/StdLibExtras.h>
 
+using WebKit::WebThread;
+
 namespace {
 enum {
     DefaultMaxBytesAllocated = 64*1024*1024,
@@ -40,6 +43,7 @@
     : m_bytesAllocated(0)
     , m_maxBytesAllocated(DefaultMaxBytesAllocated)
     , m_targetBytesAllocated(DefaultTargetBytesAllocated)
+    , m_taskObserverActive(false)
 {
 }
 
@@ -47,6 +51,7 @@
 {
     ASSERT(!m_bytesAllocated);
     ASSERT(!m_layerList.head());
+    ASSERT(!m_taskObserverActive);
 }
 
 void Canvas2DLayerManager::init(size_t maxBytesAllocated, size_t targetBytesAllocated)
@@ -62,6 +67,22 @@
     return manager;
 }
 
+void Canvas2DLayerManager::willProcessTask()
+{
+    // Observer is registered during a task and deregistered upon task completion.
+    ASSERT_NOT_REACHED();
+}
+
+void Canvas2DLayerManager::didProcessTask()
+{
+    // Called after the script action for the current frame has been processed.
+    ASSERT(m_taskObserverActive);
+    WebKit::Platform::current()->currentThread()->removeTaskObserver(this);
+    m_taskObserverActive = false;
+    for (Canvas2DLayerBridge* layer = m_layerList.head(); layer; layer = layer->next())
+        layer->limitPendingFrames();
+}
+
 void Canvas2DLayerManager::layerDidDraw(Canvas2DLayerBridge* layer)
 {
     if (isInList(layer)) {
@@ -70,7 +91,13 @@
             m_layerList.push(layer); // Set as MRU
         }
     } else
-        addLayerToList(layer); 
+        addLayerToList(layer);
+
+    if (!m_taskObserverActive) {
+        m_taskObserverActive = true;
+        // Schedule a call to didProcessTask() after completion of the current script task.
+        WebKit::Platform::current()->currentThread()->addTaskObserver(this);
+    }
 }
 
 void Canvas2DLayerManager::addLayerToList(Canvas2DLayerBridge* layer)

Modified: trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.h (130993 => 130994)


--- trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.h	2012-10-11 01:52:04 UTC (rev 130993)
+++ trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.h	2012-10-11 02:05:15 UTC (rev 130994)
@@ -26,12 +26,13 @@
 #define Canvas2DLayerManager_h
 
 #include "Canvas2DLayerBridge.h"
+#include <public/WebThread.h>
 
 class Canvas2DLayerManagerTest;
 
 namespace WebCore {
 
-class Canvas2DLayerManager {
+class Canvas2DLayerManager : public WebKit::WebThread::TaskObserver {
 public:
     static Canvas2DLayerManager& get();
     void init(size_t maxBytesAllocated, size_t targetBytesAllocated);
@@ -48,10 +49,13 @@
     bool isInList(Canvas2DLayerBridge*);
     void addLayerToList(Canvas2DLayerBridge*);
     void removeLayerFromList(Canvas2DLayerBridge*);
+    virtual void willProcessTask() OVERRIDE;
+    virtual void didProcessTask() OVERRIDE;
 
     size_t m_bytesAllocated;
     size_t m_maxBytesAllocated;
     size_t m_targetBytesAllocated;
+    bool m_taskObserverActive;
     DoublyLinkedList<Canvas2DLayerBridge> m_layerList;
 
     friend class ::Canvas2DLayerManagerTest; // for unit testing

Modified: trunk/Source/WebKit/chromium/ChangeLog (130993 => 130994)


--- trunk/Source/WebKit/chromium/ChangeLog	2012-10-11 01:52:04 UTC (rev 130993)
+++ trunk/Source/WebKit/chromium/ChangeLog	2012-10-11 02:05:15 UTC (rev 130994)
@@ -1,3 +1,32 @@
+2012-10-10  Justin Novosad  <ju...@chromium.org>
+
+        [Chromium] Smoother animation for non-RAF 2D canvas animations
+        https://bugs.webkit.org/show_bug.cgi?id=97918
+
+        Reviewed by Stephen White.
+
+        New unit test to verify the automatic flushing of deferred 2d canvas
+        layers when scripted animations are more than one full frame ahead 
+        of the compositor. Also verifies that automatic flushing does not
+        kick-in when the skip on clear optimization fires
+
+        * DEPS:
+        * tests/Canvas2DLayerManagerTest.cpp:
+        (FakeCanvas2DLayerBridge::fakeSkipPendingDrawCommands):
+        To fake the skip on clear optimization
+        (FakeCanvas2DLayerBridge):
+        (Canvas2DLayerManagerTest):
+        (Canvas2DLayerManagerTest::doDeferredFrameTestTask):
+        Method that simulates a _javascript_ scheduled action that draws to
+        a 2D canvas layer
+        (DeferredFrameTestTask):
+        Task object to simulate a scheduled action. This is so the unit
+        test can cover the TaskObserver behavior of Canvas2DLayerManager.
+        (Canvas2DLayerManagerTest::DeferredFrameTestTask::DeferredFrameTestTask):
+        (Canvas2DLayerManagerTest::deferredFrameTest):
+        The unit test implementation, which uses the WebThread run loop to
+        post tasks that touch the canvas layer.
+
 2012-10-10  Leandro Gracia Gil  <leandrogra...@chromium.org>
 
         [Chromium] Update selection on long press to match normal delimiters.

Modified: trunk/Source/WebKit/chromium/tests/Canvas2DLayerManagerTest.cpp (130993 => 130994)


--- trunk/Source/WebKit/chromium/tests/Canvas2DLayerManagerTest.cpp	2012-10-11 01:52:04 UTC (rev 130993)
+++ trunk/Source/WebKit/chromium/tests/Canvas2DLayerManagerTest.cpp	2012-10-11 02:05:15 UTC (rev 130994)
@@ -30,6 +30,8 @@
 #include "GraphicsContext3DPrivate.h"
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+#include <public/Platform.h>
+#include <public/WebThread.h>
 
 using namespace WebCore;
 using testing::InSequence;
@@ -47,6 +49,15 @@
     {
     }
 
+    virtual size_t storageAllocatedForRecording() OVERRIDE
+    {
+        // Because the fake layer has no canvas to query, just
+        // return status quo. Allocation changes that would normally be
+        // initiated by the canvas can be faked by invoking
+        // storageAllocatedForRecordingChanged directly from the test code.
+        return m_bytesAllocated;
+    }
+
     void fakeFreeableBytes(size_t size)
     {
         m_freeableBytes = size;
@@ -65,6 +76,7 @@
 
     virtual void flush() OVERRIDE
     {
+        flushedDrawCommands();
         m_flushCount++;
     }
 
@@ -133,6 +145,83 @@
         EXPECT_EQ((size_t)11, layer.bytesAllocated()); // flush drops the layer from manager's tracking list
         EXPECT_FALSE(manager.isInList(&layer));
     }
+
+    void doDeferredFrameTestTask(FakeCanvas2DLayerBridge* layer, bool skipCommands)
+    {
+        EXPECT_FALSE(Canvas2DLayerManager::get().m_taskObserverActive);
+        layer->contextAcquired();
+        layer->storageAllocatedForRecordingChanged(1);
+        EXPECT_TRUE(Canvas2DLayerManager::get().m_taskObserverActive);
+        if (skipCommands) {
+            layer->contextAcquired();
+            layer->storageAllocatedForRecordingChanged(0);
+            layer->skippedPendingDrawCommands();
+        }
+        WebKit::Platform::current()->currentThread()->exitRunLoop();
+    }
+
+    class DeferredFrameTestTask : public WebKit::WebThread::Task {
+    public:
+        DeferredFrameTestTask(Canvas2DLayerManagerTest* test, FakeCanvas2DLayerBridge* layer, bool skipCommands)
+        {
+            m_test = test;
+            m_layer = layer;
+            m_skipCommands = skipCommands;
+        }
+
+        virtual void run() OVERRIDE
+        {
+            m_test->doDeferredFrameTestTask(m_layer, m_skipCommands);
+        }
+    private:
+        Canvas2DLayerManagerTest* m_test;
+        FakeCanvas2DLayerBridge* m_layer;
+        bool m_skipCommands;
+    };
+
+    void deferredFrameTest()
+    {
+        Canvas2DLayerManager::get().init(10, 10);
+        FakeCanvas2DLayerBridge fakeLayer;
+        WebKit::Platform::current()->currentThread()->postTask(new DeferredFrameTestTask(this, &fakeLayer, true));
+        WebKit::Platform::current()->currentThread()->enterRunLoop();
+        // Verify that didProcessTask was called upon completion
+        EXPECT_FALSE(Canvas2DLayerManager::get().m_taskObserverActive);
+        // Verify that no flush was performed because frame is fresh
+        EXPECT_EQ(0, fakeLayer.m_flushCount);
+
+        // Verify that no flushes are triggered as long as frame are fresh
+        WebKit::Platform::current()->currentThread()->postTask(new DeferredFrameTestTask(this, &fakeLayer, true));
+        WebKit::Platform::current()->currentThread()->enterRunLoop();
+        EXPECT_FALSE(Canvas2DLayerManager::get().m_taskObserverActive);
+        EXPECT_EQ(0, fakeLayer.m_flushCount);
+
+        WebKit::Platform::current()->currentThread()->postTask(new DeferredFrameTestTask(this, &fakeLayer, true));
+        WebKit::Platform::current()->currentThread()->enterRunLoop();
+        EXPECT_FALSE(Canvas2DLayerManager::get().m_taskObserverActive);
+        EXPECT_EQ(0, fakeLayer.m_flushCount);
+
+        // Verify that a flush is triggered every two frames when they are stale.
+        WebKit::Platform::current()->currentThread()->postTask(new DeferredFrameTestTask(this, &fakeLayer, false));
+        WebKit::Platform::current()->currentThread()->enterRunLoop();
+        EXPECT_FALSE(Canvas2DLayerManager::get().m_taskObserverActive);
+        EXPECT_EQ(1, fakeLayer.m_flushCount);
+
+        WebKit::Platform::current()->currentThread()->postTask(new DeferredFrameTestTask(this, &fakeLayer, false));
+        WebKit::Platform::current()->currentThread()->enterRunLoop();
+        EXPECT_FALSE(Canvas2DLayerManager::get().m_taskObserverActive);
+        EXPECT_EQ(1, fakeLayer.m_flushCount);
+
+        WebKit::Platform::current()->currentThread()->postTask(new DeferredFrameTestTask(this, &fakeLayer, false));
+        WebKit::Platform::current()->currentThread()->enterRunLoop();
+        EXPECT_FALSE(Canvas2DLayerManager::get().m_taskObserverActive);
+        EXPECT_EQ(2, fakeLayer.m_flushCount);
+
+        WebKit::Platform::current()->currentThread()->postTask(new DeferredFrameTestTask(this, &fakeLayer, false));
+        WebKit::Platform::current()->currentThread()->enterRunLoop();
+        EXPECT_FALSE(Canvas2DLayerManager::get().m_taskObserverActive);
+        EXPECT_EQ(2, fakeLayer.m_flushCount);
+    }
 };
 
 namespace {
@@ -152,5 +241,10 @@
     flushEvictionTest();
 }
 
+TEST_F(Canvas2DLayerManagerTest, testDeferredFrame)
+{
+    deferredFrameTest();
+}
+
 } // namespace
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to