Title: [126760] trunk/Source
Revision
126760
Author
[email protected]
Date
2012-08-27 07:27:50 -0700 (Mon, 27 Aug 2012)

Log Message

[Chromium] Implementing a global limit on memory consumed by deferred 2D canvases
https://bugs.webkit.org/show_bug.cgi?id=94386

Patch by Justin Novosad <[email protected]> on 2012-08-27
Reviewed by Stephen White.

Source/WebCore:

Before this change, there was no global bound on memory that could be
consumed for the purpose of recording 2d canvas deferred draw commands.
There was only a per canvas limit. It is possible for canvases to get
dereferenced without flushing pending draw commands, which may lock
resources until the canvas element is garbage collected. This makes
it possible to grow memory consumption indefinitely by hitting reload
successively on some canvas-intensive web pages.

The solution implemented in this change consists in maintaining a
global registry of deferred canvas layers and tracking the sum of
memory allocated by all canvases for the purpose of storing deferred
draw commands. When memory consumption reaches the allowed limit,
caches are cleared and draw operations are flushed on one or several
canvases, starting with the least recently used, until memory
consumption is below a target level.

New tests: webkit_unit_tests DeferredLayerManagerTest*

* WebCore.gypi:
* platform/graphics/chromium/Canvas2DLayerBridge.cpp:
(WebCore::Canvas2DLayerBridge::Canvas2DLayerBridge):
(WebCore::Canvas2DLayerBridge::~Canvas2DLayerBridge):
(WebCore::Canvas2DLayerBridge::storageAllocatedForRecordingChanged):
Overloaded from SkDeferredCanvas::NotificationClient. Called when
there is a change in the number of bytes allocated by the deferred
canvas.
(WebCore):
(WebCore::Canvas2DLayerBridge::flushedDrawCommands):
Overloaded from SkDeferredCanvas::NotificationClient. Called when
pending draw commands are flush by the deferred canvas.
(WebCore::Canvas2DLayerBridge::freeMemoryIfPossible):
Called by the Canvas2DLayerManager to request the release of unlocked
memory resources (caches). Request is relayed to SkDeferredCanvas.
returns number of bytes freed.
(WebCore::Canvas2DLayerBridge::flush):
Requests that pending draw commands be flushed immediately from the
deferred canvas queue. Request is relayed to SkDeferredCanvas.
(WebCore::Canvas2DLayerBridge::contextAcquired):
* platform/graphics/chromium/Canvas2DLayerBridge.h:
(Canvas2DLayerBridge):
(WebCore::Canvas2DLayerBridge::bytesAllocated):
(WebCore::Canvas2DLayerBridge::updateBytesAllocated):
* platform/graphics/chromium/Canvas2DLayerManager.cpp: Added.
(WebCore):
(WebCore::Canvas2DLayerManager::Canvas2DLayerManager):
(WebCore::Canvas2DLayerManager::~Canvas2DLayerManager):
(WebCore::Canvas2DLayerManager::init):
Sets the global limit for the maximum total number of bytes allocated
by deferred layers.
(WebCore::Canvas2DLayerManager::get):
Singleton accessor
(WebCore::Canvas2DLayerManager::layerDidDraw):
Called by Canvas2DLayerBridge to indicate that the layer was drawn to.
(WebCore::Canvas2DLayerManager::addLayerToList):
(WebCore::Canvas2DLayerManager::layerAllocatedStorageChanged):
Called by Canvas2DLayerBridge to indicate thet the memory consumed by
the layer has changed.
(WebCore::Canvas2DLayerManager::layerDidFlush):
Called by Canvas2DLayerBridge to indicated that the pending draw
commands of the layer were flushed.
(WebCore::Canvas2DLayerManager::layerToBeDestroyed):
Called by Canvas2DLayerBridge upon destruction.
(WebCore::Canvas2DLayerManager::freeMemoryIfNecessary):
Called internally. Check that current memory consumption is below
the tolerated limit.  If check fails, intiates resource releases.
(WebCore::Canvas2DLayerManager::removeLayerFromList):
(WebCore::Canvas2DLayerManager::isInList):
(WebCore::Canvas2DLayerManager::updateBytesAllocated):
* platform/graphics/chromium/Canvas2DLayerManager.h: Added.
(WebCore):
(Canvas2DLayerManager):

Source/WebKit/chromium:

Adding unit tests for WebCore::Canvas2DLayerManager

* WebKit.gypi:

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (126759 => 126760)


--- trunk/Source/WebCore/ChangeLog	2012-08-27 14:20:46 UTC (rev 126759)
+++ trunk/Source/WebCore/ChangeLog	2012-08-27 14:27:50 UTC (rev 126760)
@@ -1,3 +1,82 @@
+2012-08-27  Justin Novosad  <[email protected]>
+
+        [Chromium] Implementing a global limit on memory consumed by deferred 2D canvases
+        https://bugs.webkit.org/show_bug.cgi?id=94386
+
+        Reviewed by Stephen White.
+
+        Before this change, there was no global bound on memory that could be
+        consumed for the purpose of recording 2d canvas deferred draw commands.
+        There was only a per canvas limit. It is possible for canvases to get
+        dereferenced without flushing pending draw commands, which may lock
+        resources until the canvas element is garbage collected. This makes
+        it possible to grow memory consumption indefinitely by hitting reload
+        successively on some canvas-intensive web pages.
+
+        The solution implemented in this change consists in maintaining a
+        global registry of deferred canvas layers and tracking the sum of
+        memory allocated by all canvases for the purpose of storing deferred
+        draw commands. When memory consumption reaches the allowed limit,
+        caches are cleared and draw operations are flushed on one or several
+        canvases, starting with the least recently used, until memory
+        consumption is below a target level.
+
+        New tests: webkit_unit_tests DeferredLayerManagerTest*
+
+        * WebCore.gypi:
+        * platform/graphics/chromium/Canvas2DLayerBridge.cpp:
+        (WebCore::Canvas2DLayerBridge::Canvas2DLayerBridge):
+        (WebCore::Canvas2DLayerBridge::~Canvas2DLayerBridge):
+        (WebCore::Canvas2DLayerBridge::storageAllocatedForRecordingChanged):
+        Overloaded from SkDeferredCanvas::NotificationClient. Called when
+        there is a change in the number of bytes allocated by the deferred
+        canvas.
+        (WebCore):
+        (WebCore::Canvas2DLayerBridge::flushedDrawCommands):
+        Overloaded from SkDeferredCanvas::NotificationClient. Called when
+        pending draw commands are flush by the deferred canvas.
+        (WebCore::Canvas2DLayerBridge::freeMemoryIfPossible):
+        Called by the Canvas2DLayerManager to request the release of unlocked
+        memory resources (caches). Request is relayed to SkDeferredCanvas.
+        returns number of bytes freed.
+        (WebCore::Canvas2DLayerBridge::flush):
+        Requests that pending draw commands be flushed immediately from the
+        deferred canvas queue. Request is relayed to SkDeferredCanvas.
+        (WebCore::Canvas2DLayerBridge::contextAcquired):
+        * platform/graphics/chromium/Canvas2DLayerBridge.h:
+        (Canvas2DLayerBridge):
+        (WebCore::Canvas2DLayerBridge::bytesAllocated):
+        (WebCore::Canvas2DLayerBridge::updateBytesAllocated):
+        * platform/graphics/chromium/Canvas2DLayerManager.cpp: Added.
+        (WebCore):
+        (WebCore::Canvas2DLayerManager::Canvas2DLayerManager):
+        (WebCore::Canvas2DLayerManager::~Canvas2DLayerManager):
+        (WebCore::Canvas2DLayerManager::init):
+        Sets the global limit for the maximum total number of bytes allocated
+        by deferred layers.
+        (WebCore::Canvas2DLayerManager::get):
+        Singleton accessor
+        (WebCore::Canvas2DLayerManager::layerDidDraw):
+        Called by Canvas2DLayerBridge to indicate that the layer was drawn to.
+        (WebCore::Canvas2DLayerManager::addLayerToList):
+        (WebCore::Canvas2DLayerManager::layerAllocatedStorageChanged):
+        Called by Canvas2DLayerBridge to indicate thet the memory consumed by
+        the layer has changed.
+        (WebCore::Canvas2DLayerManager::layerDidFlush):
+        Called by Canvas2DLayerBridge to indicated that the pending draw
+        commands of the layer were flushed.
+        (WebCore::Canvas2DLayerManager::layerToBeDestroyed):
+        Called by Canvas2DLayerBridge upon destruction.
+        (WebCore::Canvas2DLayerManager::freeMemoryIfNecessary):
+        Called internally. Check that current memory consumption is below
+        the tolerated limit.  If check fails, intiates resource releases.
+        (WebCore::Canvas2DLayerManager::removeLayerFromList):
+        (WebCore::Canvas2DLayerManager::isInList):
+        (WebCore::Canvas2DLayerManager::updateBytesAllocated):
+        * platform/graphics/chromium/Canvas2DLayerManager.h: Added.
+        (WebCore):
+        (Canvas2DLayerManager):
+
 2012-08-27  Zeno Albisser  <[email protected]>
 
         Implement GraphicsSurface::paintToTextureMapper.

Modified: trunk/Source/WebCore/WebCore.gypi (126759 => 126760)


--- trunk/Source/WebCore/WebCore.gypi	2012-08-27 14:20:46 UTC (rev 126759)
+++ trunk/Source/WebCore/WebCore.gypi	2012-08-27 14:27:50 UTC (rev 126760)
@@ -3653,6 +3653,8 @@
             'platform/graphics/chromium/AnimationTranslationUtil.h',
             'platform/graphics/chromium/Canvas2DLayerBridge.cpp',
             'platform/graphics/chromium/Canvas2DLayerBridge.h',
+            'platform/graphics/chromium/Canvas2DLayerManager.cpp',
+            'platform/graphics/chromium/Canvas2DLayerManager.h',
             'platform/graphics/chromium/CompositorHUDFontAtlas.cpp',
             'platform/graphics/chromium/CompositorHUDFontAtlas.h',
             'platform/graphics/chromium/CrossProcessFontLoading.h',

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


--- trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerBridge.cpp	2012-08-27 14:20:46 UTC (rev 126759)
+++ trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerBridge.cpp	2012-08-27 14:27:50 UTC (rev 126760)
@@ -28,6 +28,7 @@
 #include "Canvas2DLayerBridge.h"
 
 #include "CCRendererGL.h" // For the GLC() macro.
+#include "Canvas2DLayerManager.h"
 #include "GrContext.h"
 #include "GraphicsContext3D.h"
 #include "GraphicsContext3DPrivate.h"
@@ -53,6 +54,9 @@
     , m_size(size)
     , m_canvas(0)
     , m_context(context)
+    , m_bytesAllocated(0)
+    , m_next(0)
+    , m_prev(0)
 {
     if (m_useDoubleBuffering) {
         m_context->makeContextCurrent();
@@ -76,6 +80,7 @@
 
 Canvas2DLayerBridge::~Canvas2DLayerBridge()
 {
+    Canvas2DLayerManager::get().layerToBeDestroyed(this);
     if (SkDeferredCanvas* deferred = deferredCanvas())
         deferred->setNotificationClient(0);
     m_layer->setTextureId(0);
@@ -101,6 +106,34 @@
     m_context->makeContextCurrent();
 }
 
+void Canvas2DLayerBridge::storageAllocatedForRecordingChanged(size_t bytesAllocated)
+{
+    ASSERT(m_deferralMode == Deferred);
+    intptr_t delta = (intptr_t)bytesAllocated - (intptr_t)m_bytesAllocated;
+    m_bytesAllocated = bytesAllocated;
+    Canvas2DLayerManager::get().layerAllocatedStorageChanged(this, delta);
+}
+
+void Canvas2DLayerBridge::flushedDrawCommands()
+{
+    storageAllocatedForRecordingChanged(deferredCanvas()->storageAllocatedForRecording());
+}
+
+size_t Canvas2DLayerBridge::freeMemoryIfPossible(size_t bytesToFree)
+{
+    ASSERT(deferredCanvas());
+    size_t bytesFreed = deferredCanvas()->freeMemoryIfPossible(bytesToFree);
+    Canvas2DLayerManager::get().layerAllocatedStorageChanged(this, -((intptr_t)bytesFreed));
+    m_bytesAllocated -= bytesFreed;
+    return bytesFreed;
+}
+
+void Canvas2DLayerBridge::flush()
+{
+    ASSERT(deferredCanvas());
+    m_canvas->flush();
+}
+
 SkCanvas* Canvas2DLayerBridge::skCanvas(SkDevice* device)
 {
     ASSERT(!m_canvas);
@@ -154,6 +187,8 @@
 {
     if (m_deferralMode == NonDeferred && !m_useDoubleBuffering)
         m_layer->willModifyTexture();
+    else if (m_deferralMode == Deferred)
+        Canvas2DLayerManager::get().layerDidDraw(this);
 }
 
 unsigned Canvas2DLayerBridge::backBufferTexture()

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


--- trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerBridge.h	2012-08-27 14:20:46 UTC (rev 126759)
+++ trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerBridge.h	2012-08-27 14:27:50 UTC (rev 126760)
@@ -32,6 +32,7 @@
 #include "SkDeferredCanvas.h"
 #include <public/WebExternalTextureLayer.h>
 #include <public/WebExternalTextureLayerClient.h>
+#include <wtf/DoublyLinkedList.h>
 #include <wtf/PassOwnPtr.h>
 #include <wtf/RefPtr.h>
 
@@ -43,7 +44,7 @@
 
 class LayerChromium;
 
-class Canvas2DLayerBridge : public WebKit::WebExternalTextureLayerClient, public SkDeferredCanvas::NotificationClient {
+class Canvas2DLayerBridge : public WebKit::WebExternalTextureLayerClient, public SkDeferredCanvas::NotificationClient, public DoublyLinkedListNode<Canvas2DLayerBridge> {
     WTF_MAKE_NONCOPYABLE(Canvas2DLayerBridge);
 public:
     static PassOwnPtr<Canvas2DLayerBridge> create(PassRefPtr<GraphicsContext3D> context, const IntSize& size, DeferralMode deferralMode, unsigned textureId)
@@ -58,15 +59,22 @@
     virtual WebKit::WebGraphicsContext3D* context() OVERRIDE;
 
     // SkDeferredCanvas::NotificationClient implementation
-    virtual void prepareForDraw();
+    virtual void prepareForDraw() OVERRIDE;
+    virtual void storageAllocatedForRecordingChanged(size_t) OVERRIDE;
+    virtual void flushedDrawCommands() OVERRIDE;
 
+    // Methods used by Canvas2DLayerManager
+    virtual size_t freeMemoryIfPossible(size_t); // virtual for mocking
+    virtual void flush(); // virtual for mocking
+    size_t bytesAllocated() const {return m_bytesAllocated;}
+
     SkCanvas* skCanvas(SkDevice*);
     WebKit::WebLayer* layer();
     void contextAcquired();
 
     unsigned backBufferTexture();
 
-private:
+protected:
     Canvas2DLayerBridge(PassRefPtr<GraphicsContext3D>, const IntSize&, DeferralMode, unsigned textureId);
     SkDeferredCanvas* deferredCanvas();
 
@@ -78,6 +86,11 @@
     SkCanvas* m_canvas;
     OwnPtr<WebKit::WebExternalTextureLayer> m_layer;
     RefPtr<GraphicsContext3D> m_context;
+    size_t m_bytesAllocated;
+
+    friend class WTF::DoublyLinkedListNode<Canvas2DLayerBridge>;
+    Canvas2DLayerBridge* m_next;
+    Canvas2DLayerBridge* m_prev;
 };
 
 }

Added: trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.cpp (0 => 126760)


--- trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.cpp	                        (rev 0)
+++ trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.cpp	2012-08-27 14:27:50 UTC (rev 126760)
@@ -0,0 +1,136 @@
+/*
+Copyright (C) 2012 Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1.  Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+2.  Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+#include "config.h"
+
+#include "Canvas2DLayerManager.h"
+
+#include <wtf/StdLibExtras.h>
+
+namespace {
+enum {
+    DefaultMaxBytesAllocated = 64*1024*1024,
+    DefaultTargetBytesAllocated = 16*1024*1024,
+};
+}
+
+namespace WebCore {
+
+Canvas2DLayerManager::Canvas2DLayerManager()
+    : m_bytesAllocated(0)
+    , m_maxBytesAllocated(DefaultMaxBytesAllocated)
+    , m_targetBytesAllocated(DefaultTargetBytesAllocated)
+{
+}
+
+Canvas2DLayerManager::~Canvas2DLayerManager()
+{
+    ASSERT(!m_bytesAllocated);
+    ASSERT(!m_layerList.head());
+}
+
+void Canvas2DLayerManager::init(size_t maxBytesAllocated, size_t targetBytesAllocated)
+{
+    ASSERT(maxBytesAllocated >= targetBytesAllocated);
+    m_maxBytesAllocated = maxBytesAllocated;
+    m_targetBytesAllocated = targetBytesAllocated;
+}
+
+Canvas2DLayerManager& Canvas2DLayerManager::get()
+{
+    DEFINE_STATIC_LOCAL(Canvas2DLayerManager, manager, ());
+    return manager;
+}
+
+void Canvas2DLayerManager::layerDidDraw(Canvas2DLayerBridge* layer)
+{
+    if (isInList(layer)) {
+        if (layer != m_layerList.head()) {
+            m_layerList.remove(layer);
+            m_layerList.push(layer); // Set as MRU
+        }
+    } else
+        addLayerToList(layer); 
+}
+
+void Canvas2DLayerManager::addLayerToList(Canvas2DLayerBridge* layer)
+{
+    ASSERT(!isInList(layer));
+    m_bytesAllocated += layer->bytesAllocated();
+    m_layerList.push(layer); // Set as MRU
+}
+
+void Canvas2DLayerManager::layerAllocatedStorageChanged(Canvas2DLayerBridge* layer, intptr_t deltaBytes)
+{
+    if (!isInList(layer))
+        addLayerToList(layer);
+    else {
+        ASSERT((intptr_t)m_bytesAllocated + deltaBytes >= 0); 
+        m_bytesAllocated = (intptr_t)m_bytesAllocated + deltaBytes;
+    }
+    freeMemoryIfNecessary();
+}
+
+void Canvas2DLayerManager::layerToBeDestroyed(Canvas2DLayerBridge* layer)
+{
+    if (isInList(layer))
+        removeLayerFromList(layer);
+}
+
+void Canvas2DLayerManager::freeMemoryIfNecessary() 
+{
+    if (m_bytesAllocated > m_maxBytesAllocated) {
+        // Pass 1: Free memory from caches
+        Canvas2DLayerBridge* layer = m_layerList.tail(); // LRU
+        while (m_bytesAllocated > m_targetBytesAllocated && layer) {
+            layer->freeMemoryIfPossible(m_bytesAllocated - m_targetBytesAllocated);
+            layer = layer->prev();
+        }
+
+        // Pass 2: Flush canvases
+        Canvas2DLayerBridge* leastRecentlyUsedLayer = m_layerList.tail();
+        while (m_bytesAllocated > m_targetBytesAllocated && leastRecentlyUsedLayer) {
+            leastRecentlyUsedLayer->flush();
+            leastRecentlyUsedLayer->freeMemoryIfPossible(~0);
+            removeLayerFromList(leastRecentlyUsedLayer);
+            leastRecentlyUsedLayer = m_layerList.tail();
+        }
+    }
+}
+
+void Canvas2DLayerManager::removeLayerFromList(Canvas2DLayerBridge* layer)
+{
+    ASSERT(isInList(layer));
+    m_bytesAllocated -= layer->bytesAllocated();
+    m_layerList.remove(layer);
+    layer->setNext(0);
+    layer->setPrev(0);
+}
+
+bool Canvas2DLayerManager::isInList(Canvas2DLayerBridge* layer)
+{
+    return layer->prev() || m_layerList.head() == layer;
+}
+
+}
+

Added: trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.h (0 => 126760)


--- trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.h	                        (rev 0)
+++ trunk/Source/WebCore/platform/graphics/chromium/Canvas2DLayerManager.h	2012-08-27 14:27:50 UTC (rev 126760)
@@ -0,0 +1,63 @@
+/*
+Copyright (C) 2012 Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1.  Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+2.  Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in the
+    documentation and/or other materials provided with the distribution.
+    
+THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef Canvas2DLayerManager_h
+#define Canvas2DLayerManager_h
+
+#include "Canvas2DLayerBridge.h"
+
+class Canvas2DLayerManagerTest;
+
+namespace WebCore {
+
+class Canvas2DLayerManager {
+public:
+    static Canvas2DLayerManager& get();
+    void init(size_t maxBytesAllocated, size_t targetBytesAllocated);
+    virtual ~Canvas2DLayerManager();
+
+    void layerAllocatedStorageChanged(Canvas2DLayerBridge*, intptr_t deltaBytes);
+    void layerDidDraw(Canvas2DLayerBridge*);
+    void layerToBeDestroyed(Canvas2DLayerBridge*);
+private:
+    Canvas2DLayerManager();
+
+    // internal methods
+    void freeMemoryIfNecessary();
+    bool isInList(Canvas2DLayerBridge*);
+    void addLayerToList(Canvas2DLayerBridge*);
+    void removeLayerFromList(Canvas2DLayerBridge*);
+
+    size_t m_bytesAllocated;
+    size_t m_maxBytesAllocated;
+    size_t m_targetBytesAllocated;
+    DoublyLinkedList<Canvas2DLayerBridge> m_layerList;
+
+    friend class ::Canvas2DLayerManagerTest; // for unit testing
+};
+
+}
+
+#endif
+

Modified: trunk/Source/WebKit/chromium/ChangeLog (126759 => 126760)


--- trunk/Source/WebKit/chromium/ChangeLog	2012-08-27 14:20:46 UTC (rev 126759)
+++ trunk/Source/WebKit/chromium/ChangeLog	2012-08-27 14:27:50 UTC (rev 126760)
@@ -1,3 +1,14 @@
+2012-08-27  Justin Novosad  <[email protected]>
+
+        [Chromium] Implementing a global limit on memory consumed by deferred 2D canvases
+        https://bugs.webkit.org/show_bug.cgi?id=94386
+
+        Reviewed by Stephen White.
+
+        Adding unit tests for WebCore::Canvas2DLayerManager
+
+        * WebKit.gypi:
+
 2012-08-27  Sheriff Bot  <[email protected]>
 
         Unreviewed.  Rolled DEPS.

Modified: trunk/Source/WebKit/chromium/WebKit.gypi (126759 => 126760)


--- trunk/Source/WebKit/chromium/WebKit.gypi	2012-08-27 14:20:46 UTC (rev 126759)
+++ trunk/Source/WebKit/chromium/WebKit.gypi	2012-08-27 14:27:50 UTC (rev 126760)
@@ -56,6 +56,7 @@
             'tests/ArenaTestHelpers.h',
             'tests/AssociatedURLLoaderTest.cpp',
             'tests/Canvas2DLayerBridgeTest.cpp',
+            'tests/Canvas2DLayerManagerTest.cpp',
             'tests/CCActiveAnimationTest.cpp',
             'tests/CCAnimationTestCommon.cpp',
             'tests/CCAnimationTestCommon.h',

Added: trunk/Source/WebKit/chromium/tests/Canvas2DLayerManagerTest.cpp (0 => 126760)


--- trunk/Source/WebKit/chromium/tests/Canvas2DLayerManagerTest.cpp	                        (rev 0)
+++ trunk/Source/WebKit/chromium/tests/Canvas2DLayerManagerTest.cpp	2012-08-27 14:27:50 UTC (rev 126760)
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include "Canvas2DLayerManager.h"
+
+#include "FakeWebGraphicsContext3D.h"
+#include "GraphicsContext3DPrivate.h"
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace WebCore;
+using testing::InSequence;
+using testing::Return;
+using testing::Test;
+
+
+class FakeCanvas2DLayerBridge : public Canvas2DLayerBridge {
+public:
+    FakeCanvas2DLayerBridge() 
+        : Canvas2DLayerBridge(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new WebKit::FakeWebGraphicsContext3D)), IntSize(1, 1), Deferred, 0)
+        , m_freeableBytes(0)
+        , m_freeMemoryIfPossibleCount(0)
+        , m_flushCount(0)
+    {
+    }
+
+    void fakeFreeableBytes(size_t size)
+    {
+        m_freeableBytes = size;
+    }
+
+    virtual size_t freeMemoryIfPossible(size_t size) OVERRIDE
+    {
+        m_freeMemoryIfPossibleCount++;
+        size_t bytesFreed = size < m_freeableBytes ? size : m_freeableBytes;
+        m_freeableBytes -= bytesFreed;
+        Canvas2DLayerManager::get().layerAllocatedStorageChanged(this, -((intptr_t)bytesFreed));
+        m_bytesAllocated -= bytesFreed;
+        return bytesFreed;
+    }
+
+    virtual void flush() OVERRIDE
+    {
+        m_flushCount++;
+    }
+
+public:
+    size_t m_freeableBytes;
+    int m_freeMemoryIfPossibleCount;
+    int m_flushCount;
+};
+
+class Canvas2DLayerManagerTest : public Test {
+protected:
+    void storageAllocationTrackingTest()
+    {
+        Canvas2DLayerManager& manager = Canvas2DLayerManager::get();
+        manager.init(10, 10);
+        {
+            FakeCanvas2DLayerBridge layer1;
+            EXPECT_EQ((size_t)0, manager.m_bytesAllocated);
+            layer1.storageAllocatedForRecordingChanged(1);
+            EXPECT_EQ((size_t)1, manager.m_bytesAllocated);
+            // Test allocation increase
+            layer1.storageAllocatedForRecordingChanged(2);
+            EXPECT_EQ((size_t)2, manager.m_bytesAllocated);
+            // Test allocation decrease
+            layer1.storageAllocatedForRecordingChanged(1);
+            EXPECT_EQ((size_t)1, manager.m_bytesAllocated);
+            {
+                FakeCanvas2DLayerBridge layer2;
+                EXPECT_EQ((size_t)1, manager.m_bytesAllocated);
+                // verify multi-layer allocation tracking
+                layer2.storageAllocatedForRecordingChanged(2);
+                EXPECT_EQ((size_t)3, manager.m_bytesAllocated);
+            }
+            // Verify tracking after destruction
+            EXPECT_EQ((size_t)1, manager.m_bytesAllocated);
+        }
+    }
+
+    void evictionTest()
+    {
+        Canvas2DLayerManager& manager = Canvas2DLayerManager::get();
+        manager.init(10, 5);
+        FakeCanvas2DLayerBridge layer;
+        layer.fakeFreeableBytes(10);
+        layer.storageAllocatedForRecordingChanged(8); // under the max
+        EXPECT_EQ(0, layer.m_freeMemoryIfPossibleCount);
+        layer.storageAllocatedForRecordingChanged(12); // over the max
+        EXPECT_EQ(1, layer.m_freeMemoryIfPossibleCount);
+        EXPECT_EQ((size_t)3, layer.m_freeableBytes);
+        EXPECT_EQ(0, layer.m_flushCount); // eviction succeeded without triggering a flush
+        EXPECT_EQ((size_t)5, layer.bytesAllocated());
+    }
+
+    void flushEvictionTest()
+    {
+        Canvas2DLayerManager& manager = Canvas2DLayerManager::get();
+        manager.init(10, 5);
+        FakeCanvas2DLayerBridge layer;
+        layer.fakeFreeableBytes(1); // Not enough freeable bytes, will cause aggressive eviction by flushing
+        layer.storageAllocatedForRecordingChanged(8); // under the max
+        EXPECT_EQ(0, layer.m_freeMemoryIfPossibleCount);
+        layer.storageAllocatedForRecordingChanged(12); // over the max
+        EXPECT_EQ(2, layer.m_freeMemoryIfPossibleCount); // Two tries, one before flush, one after flush
+        EXPECT_EQ((size_t)0, layer.m_freeableBytes);
+        EXPECT_EQ(1, layer.m_flushCount); // flush was attempted
+        EXPECT_EQ((size_t)11, layer.bytesAllocated()); // flush drops the layer from manager's tracking list
+        EXPECT_FALSE(manager.isInList(&layer));
+    }
+};
+
+namespace {
+
+TEST_F(Canvas2DLayerManagerTest, testStorageAllocationTracking)
+{
+    storageAllocationTrackingTest();
+}
+
+TEST_F(Canvas2DLayerManagerTest, testEviction)
+{
+    evictionTest();
+}
+
+TEST_F(Canvas2DLayerManagerTest, testFlushEviction)
+{
+    flushEvictionTest();
+}
+
+} // namespace
+
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to