Avoid unnecessary TextureManager::reduceMemoryToLimit().
authorwangxianzhu@chromium.org <wangxianzhu@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 9 Jan 2012 20:15:33 +0000 (20:15 +0000)
committerwangxianzhu@chromium.org <wangxianzhu@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 9 Jan 2012 20:15:33 +0000 (20:15 +0000)
https://bugs.webkit.org/show_bug.cgi?id=75632

Source/WebCore:

Unnecessary TextureManager::reduceMemoryToLimit() will cause some tile
textures that are required soon to be unnecessarily removed/replaced,
and degrade performance, sometimes significantly.

For example, CCLayerTreeHost::setViewport will be called during
scrolling. The original code would call TextureManager::reduceMemoryToLimit(),
causing some textures unnecessarily discarded and then recreated
repeatedly during scrolling.

It's also unnecessary to call TextureManager::reduceMemoryToLimit()
from TextureManager::setPreferredMemoryLimitBytes() because the limit
is not a hard limit. The callers should call reduceMemoryToLimit()
explicitly if it wants it when setting the preferred memory limit.

Reviewed by James Robinson.

Tests: webkit_unit_tests --gtest_filter=TextureManagerTest.*:CCLayerTreeHostTestSetViewportSize.*

* platform/graphics/chromium/LayerRendererChromium.cpp:
(WebCore::LayerRendererChromium::finishDrawingFrame): Call reduceMemoryToLimit() explicitly
* platform/graphics/chromium/TextureManager.cpp:
(WebCore::TextureManager::setPreferredMemoryLimitBytes): Removed call to reduceMemoryToLimit().
* platform/graphics/chromium/cc/CCLayerTreeHost.cpp:
(WebCore::CCLayerTreeHost::finishCommitOnImplThread):
(WebCore::CCLayerTreeHost::setViewportSize): Changed name from setViewport(). Check change of viewportSize.
* platform/graphics/chromium/cc/CCLayerTreeHost.h:
* platform/graphics/chromium/cc/CCLayerTreeHostImpl.cpp:
(WebCore::CCLayerTreeHostImpl::setViewportSize): Changed name from setViewport()
* platform/graphics/chromium/cc/CCLayerTreeHostImpl.h:

Source/WebKit/chromium:

Main part of the change is in Source/WebCore.

Reviewed by James Robinson.

Tests: webkit_unit_tests --gtest_filter=TextureManagerTest.*:CCLayerTreeHostTestSetViewportSize.*

* WebKit.gypi:
* src/WebLayerTreeView.cpp:
(WebKit::WebLayerTreeView::setViewportSize):
* src/WebViewImpl.cpp:
(WebKit::WebViewImpl::updateLayerTreeViewport):
* tests/CCLayerTreeHostImplTest.cpp:
(WebKit::TEST_F):
* tests/CCLayerTreeHostTest.cpp:
(WTF::MockLayerTreeHost::create):
(WTF::CCLayerTreeHostTestOpacityChange::beginTest):
(WTF::CCLayerTreeHostTestSetViewportSize::CCLayerTreeHostTestSetViewportSize):
(WTF::CCLayerTreeHostTestSetViewportSize::beginTest):
(WTF::CCLayerTreeHostTestSetViewportSize::afterTest):
(WTF::TEST_F):
* tests/TextureManagerTest.cpp: Added.
(WTF::FakeTextureAllocator::createTexture):
(WTF::FakeTextureAllocator::deleteTexture):
(WTF::texturesMemorySize):
(WTF::createTextureManager):
(WTF::requestTexture):
(WTF::TEST):

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

15 files changed:
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/chromium/LayerRendererChromium.cpp
Source/WebCore/platform/graphics/chromium/TextureManager.cpp
Source/WebCore/platform/graphics/chromium/TextureManager.h
Source/WebCore/platform/graphics/chromium/cc/CCLayerTreeHost.cpp
Source/WebCore/platform/graphics/chromium/cc/CCLayerTreeHost.h
Source/WebCore/platform/graphics/chromium/cc/CCLayerTreeHostImpl.cpp
Source/WebCore/platform/graphics/chromium/cc/CCLayerTreeHostImpl.h
Source/WebKit/chromium/ChangeLog
Source/WebKit/chromium/WebKit.gypi
Source/WebKit/chromium/src/WebLayerTreeView.cpp
Source/WebKit/chromium/src/WebViewImpl.cpp
Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp
Source/WebKit/chromium/tests/CCLayerTreeHostTest.cpp
Source/WebKit/chromium/tests/TextureManagerTest.cpp [new file with mode: 0644]

index eeaedec..1cec8eb 100644 (file)
@@ -1,3 +1,38 @@
+2012-01-09  Xianzhu Wang  <wangxianzhu@chromium.org>
+
+        Avoid unnecessary TextureManager::reduceMemoryToLimit().
+        https://bugs.webkit.org/show_bug.cgi?id=75632
+
+        Unnecessary TextureManager::reduceMemoryToLimit() will cause some tile
+        textures that are required soon to be unnecessarily removed/replaced,
+        and degrade performance, sometimes significantly.
+
+        For example, CCLayerTreeHost::setViewport will be called during
+        scrolling. The original code would call TextureManager::reduceMemoryToLimit(),
+        causing some textures unnecessarily discarded and then recreated
+        repeatedly during scrolling.
+
+        It's also unnecessary to call TextureManager::reduceMemoryToLimit()
+        from TextureManager::setPreferredMemoryLimitBytes() because the limit
+        is not a hard limit. The callers should call reduceMemoryToLimit()
+        explicitly if it wants it when setting the preferred memory limit.
+
+        Reviewed by James Robinson.
+
+        Tests: webkit_unit_tests --gtest_filter=TextureManagerTest.*:CCLayerTreeHostTestSetViewportSize.*
+
+        * platform/graphics/chromium/LayerRendererChromium.cpp:
+        (WebCore::LayerRendererChromium::finishDrawingFrame): Call reduceMemoryToLimit() explicitly
+        * platform/graphics/chromium/TextureManager.cpp:
+        (WebCore::TextureManager::setPreferredMemoryLimitBytes): Removed call to reduceMemoryToLimit().
+        * platform/graphics/chromium/cc/CCLayerTreeHost.cpp:
+        (WebCore::CCLayerTreeHost::finishCommitOnImplThread):
+        (WebCore::CCLayerTreeHost::setViewportSize): Changed name from setViewport(). Check change of viewportSize.
+        * platform/graphics/chromium/cc/CCLayerTreeHost.h:
+        * platform/graphics/chromium/cc/CCLayerTreeHostImpl.cpp:
+        (WebCore::CCLayerTreeHostImpl::setViewportSize): Changed name from setViewport()
+        * platform/graphics/chromium/cc/CCLayerTreeHostImpl.h:
+
 2012-01-09  Sami Kyostila  <skyostil@chromium.org>
 
         [Chromium] JPEG RGB swizzling order should match platform pixel format
index daeb97f..9823433 100644 (file)
@@ -679,11 +679,9 @@ void LayerRendererChromium::finishDrawingFrame()
 
     size_t contentsMemoryUseBytes = m_contentsTextureAllocator->currentMemoryUseBytes();
     size_t reclaimLimit = TextureManager::reclaimLimitBytes(viewportSize());
-    if (reclaimLimit > contentsMemoryUseBytes)
-        m_renderSurfaceTextureManager->setPreferredMemoryLimitBytes(reclaimLimit - contentsMemoryUseBytes);
-    else
-        m_renderSurfaceTextureManager->setPreferredMemoryLimitBytes(0);
-
+    size_t preferredLimit = reclaimLimit > contentsMemoryUseBytes ? reclaimLimit - contentsMemoryUseBytes : 0;
+    m_renderSurfaceTextureManager->setPreferredMemoryLimitBytes(preferredLimit);
+    m_renderSurfaceTextureManager->reduceMemoryToLimit(preferredLimit);
     m_renderSurfaceTextureManager->deleteEvictedTextures(m_renderSurfaceTextureAllocator.get());
 
     if (settings().compositeOffscreen)
index 30207be..516cea9 100644 (file)
@@ -112,7 +112,6 @@ void TextureManager::setMaxMemoryLimitBytes(size_t memoryLimitBytes)
 
 void TextureManager::setPreferredMemoryLimitBytes(size_t memoryLimitBytes)
 {
-    reduceMemoryToLimit(memoryLimitBytes);
     m_preferredMemoryLimitBytes = memoryLimitBytes;
 }
 
index c6c21ad..a66e9d5 100644 (file)
@@ -64,7 +64,9 @@ public:
     static size_t memoryUseBytes(const IntSize&, GC3Denum format);
 
     void setMaxMemoryLimitBytes(size_t);
+    size_t maxMemoryLimitBytes() { return m_maxMemoryLimitBytes; }
     void setPreferredMemoryLimitBytes(size_t);
+    size_t preferredMemoryLimitBytes() { return m_preferredMemoryLimitBytes; }
 
     TextureToken getToken();
     void releaseToken(TextureToken);
index 379d160..f8574ed 100644 (file)
@@ -156,7 +156,7 @@ void CCLayerTreeHost::finishCommitOnImplThread(CCLayerTreeHostImpl* hostImpl)
 
     hostImpl->setSourceFrameNumber(frameNumber());
     hostImpl->setHaveWheelEventHandlers(m_haveWheelEventHandlers);
-    hostImpl->setViewport(viewportSize());
+    hostImpl->setViewportSize(viewportSize());
     hostImpl->setPageScaleFactorAndLimits(pageScale(), m_minPageScale, m_maxPageScale);
 
     m_frameNumber++;
@@ -247,8 +247,11 @@ void CCLayerTreeHost::setRootLayer(PassRefPtr<LayerChromium> rootLayer)
     setNeedsCommit();
 }
 
-void CCLayerTreeHost::setViewport(const IntSize& viewportSize)
+void CCLayerTreeHost::setViewportSize(const IntSize& viewportSize)
 {
+    if (viewportSize == m_viewportSize)
+        return;
+
     contentsTextureManager()->setMaxMemoryLimitBytes(TextureManager::highLimitBytes(viewportSize));
     contentsTextureManager()->setPreferredMemoryLimitBytes(TextureManager::reclaimLimitBytes(viewportSize));
     m_viewportSize = viewportSize;
index 4f293ec..2f989bc 100644 (file)
@@ -169,7 +169,7 @@ public:
 
     const CCSettings& settings() const { return m_settings; }
 
-    void setViewport(const IntSize& viewportSize);
+    void setViewportSize(const IntSize&);
 
     const IntSize& viewportSize() const { return m_viewportSize; }
 
index b5b776a..02186ca 100644 (file)
@@ -361,7 +361,7 @@ bool CCLayerTreeHostImpl::initializeLayerRenderer(PassRefPtr<GraphicsContext3D>
     return m_layerRenderer;
 }
 
-void CCLayerTreeHostImpl::setViewport(const IntSize& viewportSize)
+void CCLayerTreeHostImpl::setViewportSize(const IntSize& viewportSize)
 {
     if (viewportSize == m_viewportSize)
         return;
index 4897e8e..e4d006d 100644 (file)
@@ -107,7 +107,7 @@ public:
     int sourceFrameNumber() const { return m_sourceFrameNumber; }
     void setSourceFrameNumber(int frameNumber) { m_sourceFrameNumber = frameNumber; }
 
-    void setViewport(const IntSize& viewportSize);
+    void setViewportSize(const IntSize&);
     const IntSize& viewportSize() const { return m_viewportSize; }
 
     void setPageScaleFactorAndLimits(float pageScale, float minPageScale, float maxPageScale);
index eefc2f9..9b38456 100644 (file)
@@ -1,3 +1,36 @@
+2012-01-09  Xianzhu Wang  <wangxianzhu@chromium.org>
+
+        Avoid unnecessary TextureManager::reduceMemoryToLimit().
+        https://bugs.webkit.org/show_bug.cgi?id=75632
+
+        Main part of the change is in Source/WebCore.
+
+        Reviewed by James Robinson.
+
+        Tests: webkit_unit_tests --gtest_filter=TextureManagerTest.*:CCLayerTreeHostTestSetViewportSize.*
+
+        * WebKit.gypi:
+        * src/WebLayerTreeView.cpp:
+        (WebKit::WebLayerTreeView::setViewportSize):
+        * src/WebViewImpl.cpp:
+        (WebKit::WebViewImpl::updateLayerTreeViewport):
+        * tests/CCLayerTreeHostImplTest.cpp:
+        (WebKit::TEST_F):
+        * tests/CCLayerTreeHostTest.cpp:
+        (WTF::MockLayerTreeHost::create):
+        (WTF::CCLayerTreeHostTestOpacityChange::beginTest):
+        (WTF::CCLayerTreeHostTestSetViewportSize::CCLayerTreeHostTestSetViewportSize):
+        (WTF::CCLayerTreeHostTestSetViewportSize::beginTest):
+        (WTF::CCLayerTreeHostTestSetViewportSize::afterTest):
+        (WTF::TEST_F):
+        * tests/TextureManagerTest.cpp: Added.
+        (WTF::FakeTextureAllocator::createTexture):
+        (WTF::FakeTextureAllocator::deleteTexture):
+        (WTF::texturesMemorySize):
+        (WTF::createTextureManager):
+        (WTF::requestTexture):
+        (WTF::TEST):
+
 2012-01-09  Sheriff Bot  <webkit.review.bot@gmail.com>
 
         Unreviewed, rolling out r104418.
index cf9c41e..9b0e9ce 100644 (file)
@@ -96,6 +96,7 @@
             'tests/PODIntervalTreeTest.cpp',
             'tests/PODRedBlackTreeTest.cpp',
             'tests/RenderTableCellTest.cpp',
+            'tests/TextureManagerTest.cpp',
             'tests/TiledLayerChromiumTest.cpp',
             'tests/TilingDataTest.cpp',
             'tests/TreeSynchronizerTest.cpp',
index 44ac905..b288b56 100644 (file)
@@ -81,7 +81,7 @@ void WebLayerTreeView::composite()
 
 void WebLayerTreeView::setViewportSize(const WebSize& viewportSize)
 {
-    m_private->setViewport(viewportSize);
+    m_private->setViewportSize(viewportSize);
 }
 
 WebSize WebLayerTreeView::viewportSize() const
index b9355fc..860580a 100644 (file)
@@ -3089,7 +3089,7 @@ void WebViewImpl::updateLayerTreeViewport()
         layerAdjustX = -view->contentsSize().width() + view->visibleContentRect(false).width();
     }
     m_nonCompositedContentHost->setViewport(visibleRect.size(), view->contentsSize(), scroll, pageScaleFactor(), layerAdjustX);
-    m_layerTreeHost->setViewport(visibleRect.size());
+    m_layerTreeHost->setViewportSize(visibleRect.size());
     m_layerTreeHost->setPageScale(pageScaleFactor());
 }
 
index f5b8db2..b2f11a3 100644 (file)
@@ -223,7 +223,7 @@ TEST_F(CCLayerTreeHostImplTest, FAILS_blendingOffWhenDrawingOpaqueLayers)
     GraphicsContext3D::Attributes attrs;
     RefPtr<GraphicsContext3D> context = GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new BlendStateTrackerContext()), attrs, 0, GraphicsContext3D::RenderDirectlyToHostWindow, GraphicsContext3DPrivate::ForUseOnThisThread);
     m_hostImpl->initializeLayerRenderer(context);
-    m_hostImpl->setViewport(IntSize(10, 10));
+    m_hostImpl->setViewportSize(IntSize(10, 10));
 
     RefPtr<CCLayerImpl> root = CCLayerImpl::create(0);
     root->setAnchorPoint(FloatPoint(0, 0));
@@ -322,7 +322,7 @@ TEST_F(CCLayerTreeHostImplTest, reshapeNotCalledUntilDraw)
     ReshapeTrackerContext* reshapeTracker = new ReshapeTrackerContext();
     RefPtr<GraphicsContext3D> context = GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(reshapeTracker), attrs, 0, GraphicsContext3D::RenderDirectlyToHostWindow, GraphicsContext3DPrivate::ForUseOnThisThread);
     m_hostImpl->initializeLayerRenderer(context);
-    m_hostImpl->setViewport(IntSize(10, 10));
+    m_hostImpl->setViewportSize(IntSize(10, 10));
 
     RefPtr<CCLayerImpl> root = adoptRef(new FakeDrawableCCLayerImpl(1));
     root->setAnchorPoint(FloatPoint(0, 0));
@@ -372,7 +372,7 @@ TEST_F(CCLayerTreeHostImplTest, partialSwapReceivesDamageRect)
     settings.partialSwapEnabled = true;
     OwnPtr<CCLayerTreeHostImpl> layerTreeHostImpl = CCLayerTreeHostImpl::create(settings, this);
     layerTreeHostImpl->initializeLayerRenderer(context);
-    layerTreeHostImpl->setViewport(IntSize(500, 500));
+    layerTreeHostImpl->setViewportSize(IntSize(500, 500));
 
     RefPtr<CCLayerImpl> root = adoptRef(new FakeDrawableCCLayerImpl(1));
     RefPtr<CCLayerImpl> child = adoptRef(new FakeDrawableCCLayerImpl(2));
@@ -413,7 +413,7 @@ TEST_F(CCLayerTreeHostImplTest, partialSwapReceivesDamageRect)
     // Make sure that partial swap is constrained to the viewport dimensions
     // expected damage rect: IntRect(IntPoint::zero(), IntSize(500, 500));
     // expected swap rect: flipped damage rect, but also clamped to viewport
-    layerTreeHostImpl->setViewport(IntSize(10, 10));
+    layerTreeHostImpl->setViewportSize(IntSize(10, 10));
     root->setOpacity(0.7); // this will damage everything
     layerTreeHostImpl->drawLayers();
     layerTreeHostImpl->swapBuffers();
index e6ce8b0..bfa1b08 100644 (file)
@@ -110,7 +110,7 @@ public:
         layerTreeHost->setRootLayer(rootLayer);
 
         // LayerTreeHostImpl won't draw if it has 1x1 viewport.
-        layerTreeHost->setViewport(IntSize(1, 1));
+        layerTreeHost->setViewportSize(IntSize(1, 1));
 
         return layerTreeHost.release();
     }
@@ -988,7 +988,7 @@ public:
     virtual void beginTest()
     {
         m_layerTreeHost->setRootLayer(m_updateCheckLayer);
-        m_layerTreeHost->setViewport(IntSize(10, 10));
+        m_layerTreeHost->setViewportSize(IntSize(10, 10));
 
         postSetNeedsCommitToMainThread();
     }
@@ -1025,4 +1025,47 @@ TEST_F(CCLayerTreeHostTestOpacityChange, runMultiThread)
     runTest(true);
 }
 
+class CCLayerTreeHostTestSetViewportSize : public CCLayerTreeHostTest {
+public:
+
+    CCLayerTreeHostTestSetViewportSize()
+        : m_numCommits(0)
+        , m_numDraws(0)
+    {
+    }
+
+    virtual void beginTest()
+    {
+        IntSize viewportSize(10, 10);
+        layerTreeHost()->setViewportSize(viewportSize);
+        EXPECT_EQ(viewportSize, layerTreeHost()->viewportSize());
+        EXPECT_EQ(TextureManager::highLimitBytes(viewportSize), layerTreeHost()->contentsTextureManager()->maxMemoryLimitBytes());
+        EXPECT_EQ(TextureManager::reclaimLimitBytes(viewportSize), layerTreeHost()->contentsTextureManager()->preferredMemoryLimitBytes());
+
+        // setViewportSize() should not call TextureManager::setMaxMemoryLimitBytes() or TextureManager::setPreferredMemoryLimitBytes()
+        // if the viewport size is not changed.
+        IntSize fakeSize(5, 5);
+        layerTreeHost()->contentsTextureManager()->setMaxMemoryLimitBytes(TextureManager::highLimitBytes(fakeSize));
+        layerTreeHost()->contentsTextureManager()->setPreferredMemoryLimitBytes(TextureManager::reclaimLimitBytes(fakeSize));
+        layerTreeHost()->setViewportSize(viewportSize);
+        EXPECT_EQ(TextureManager::highLimitBytes(fakeSize), layerTreeHost()->contentsTextureManager()->maxMemoryLimitBytes());
+        EXPECT_EQ(TextureManager::reclaimLimitBytes(fakeSize), layerTreeHost()->contentsTextureManager()->preferredMemoryLimitBytes());
+
+        endTest();
+    }
+
+    virtual void afterTest()
+    {
+    }
+
+private:
+    int m_numCommits;
+    int m_numDraws;
+};
+
+TEST_F(CCLayerTreeHostTestSetViewportSize, runSingleThread)
+{
+    runTest(false);
+}
+
 } // namespace
diff --git a/Source/WebKit/chromium/tests/TextureManagerTest.cpp b/Source/WebKit/chromium/tests/TextureManagerTest.cpp
new file mode 100644 (file)
index 0000000..7c088da
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * 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 "TextureManager.h"
+
+#include <gtest/gtest.h>
+
+using namespace WebCore;
+using namespace WTF;
+
+namespace {
+
+class FakeTextureAllocator : public TextureAllocator {
+public:
+    virtual unsigned createTexture(const IntSize&, GC3Denum) { return 1; }
+    virtual void deleteTexture(unsigned, const IntSize&, GC3Denum) { }
+};
+
+FakeTextureAllocator fakeTextureAllocator;
+const IntSize textureSize(256, 256);
+const GC3Denum textureFormat = GraphicsContext3D::RGBA;
+
+size_t texturesMemorySize(size_t textureCount)
+{
+    return TextureManager::memoryUseBytes(textureSize, textureFormat) * textureCount;
+}
+
+PassOwnPtr<TextureManager> createTextureManager(size_t maxTextures, size_t preferredTextures)
+{
+    return TextureManager::create(texturesMemorySize(maxTextures), texturesMemorySize(preferredTextures), 1024);
+}
+
+bool requestTexture(TextureManager* manager, TextureToken token)
+{
+    unsigned textureId;
+    bool result = manager->requestTexture(token, textureSize, textureFormat, textureId);
+    if (result)
+        manager->allocateTexture(&fakeTextureAllocator, token);
+    return result;
+}
+
+TEST(TextureManagerTest, requestTextureInPreferredLimit)
+{
+    const size_t preferredTextures = 8;
+    OwnPtr<TextureManager> textureManager = createTextureManager(preferredTextures * 2, preferredTextures);
+    TextureToken tokens[preferredTextures];
+    for (size_t i = 0; i < preferredTextures; ++i) {
+        tokens[i] = textureManager->getToken();
+        if (i)
+            EXPECT_GT(tokens[i], tokens[i - 1]);
+        EXPECT_TRUE(requestTexture(textureManager.get(), tokens[i]));
+        EXPECT_TRUE(textureManager->hasTexture(tokens[i]));
+        EXPECT_TRUE(textureManager->isProtected(tokens[i]));
+    }
+
+    for (size_t i = 0; i < preferredTextures; ++i)
+        EXPECT_TRUE(textureManager->hasTexture(tokens[i]));
+
+    EXPECT_EQ(texturesMemorySize(preferredTextures), textureManager->currentMemoryUseBytes());
+}
+
+TEST(TextureManagerTest, requestTextureExceedingPreferredLimit)
+{
+    const size_t maxTextures = 8;
+    const size_t preferredTextures = 4;
+    OwnPtr<TextureManager> textureManager = createTextureManager(maxTextures, preferredTextures);
+    TextureToken tokens[maxTextures];
+    for (size_t i = 0; i < preferredTextures; ++i) {
+        tokens[i] = textureManager->getToken();
+        EXPECT_TRUE(requestTexture(textureManager.get(), tokens[i]));
+        EXPECT_TRUE(textureManager->hasTexture(tokens[i]));
+    }
+
+    textureManager->unprotectTexture(tokens[0]);
+    textureManager->unprotectTexture(tokens[2]);
+
+    for (size_t i = preferredTextures; i < maxTextures; ++i) {
+        tokens[i] = textureManager->getToken();
+        EXPECT_TRUE(requestTexture(textureManager.get(), tokens[i]));
+        EXPECT_TRUE(textureManager->hasTexture(tokens[i]));
+        textureManager->unprotectTexture(tokens[i]);
+    }
+
+    EXPECT_FALSE(textureManager->hasTexture(tokens[0]));
+    EXPECT_TRUE(textureManager->hasTexture(tokens[1]));
+    EXPECT_TRUE(textureManager->isProtected(tokens[1]));
+    EXPECT_FALSE(textureManager->hasTexture(tokens[2]));
+    EXPECT_TRUE(textureManager->hasTexture(tokens[3]));
+    EXPECT_TRUE(textureManager->isProtected(tokens[3]));
+
+    EXPECT_EQ(texturesMemorySize(preferredTextures), textureManager->currentMemoryUseBytes());
+}
+
+TEST(TextureManagerTest, requestTextureExceedingMaxLimit)
+{
+    const size_t maxTextures = 8;
+    const size_t preferredTextures = 4;
+    OwnPtr<TextureManager> textureManager = createTextureManager(maxTextures, preferredTextures);
+    TextureToken tokens[maxTextures];
+    for (size_t i = 0; i < maxTextures; ++i) {
+        tokens[i] = textureManager->getToken();
+        EXPECT_TRUE(requestTexture(textureManager.get(), tokens[i]));
+        EXPECT_TRUE(textureManager->hasTexture(tokens[i]));
+    }
+
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->currentMemoryUseBytes());
+
+    for (size_t i = 0; i < maxTextures; ++i) {
+        TextureToken token = textureManager->getToken();
+        EXPECT_FALSE(requestTexture(textureManager.get(), token));
+        EXPECT_FALSE(textureManager->hasTexture(token));
+    }
+
+    EXPECT_EQ(textureManager->currentMemoryUseBytes(), texturesMemorySize(maxTextures));
+
+    textureManager->unprotectTexture(tokens[1]);
+    textureManager->unprotectTexture(tokens[3]);
+    EXPECT_TRUE(requestTexture(textureManager.get(), textureManager->getToken()));
+    EXPECT_TRUE(requestTexture(textureManager.get(), textureManager->getToken()));
+    EXPECT_FALSE(requestTexture(textureManager.get(), textureManager->getToken()));
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->currentMemoryUseBytes());
+    EXPECT_FALSE(textureManager->hasTexture(tokens[1]));
+    EXPECT_FALSE(textureManager->hasTexture(tokens[3]));
+}
+
+TEST(TextureManagerTest, reduceMemoryToLimit)
+{
+    const size_t maxTextures = 8;
+    const size_t preferredTextures = 4;
+    OwnPtr<TextureManager> textureManager = createTextureManager(maxTextures, preferredTextures);
+    TextureToken tokens[maxTextures];
+    for (size_t i = 0; i < maxTextures; ++i) {
+        tokens[i] = textureManager->getToken();
+        EXPECT_TRUE(requestTexture(textureManager.get(), tokens[i]));
+    }
+
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->currentMemoryUseBytes());
+    textureManager->reduceMemoryToLimit(texturesMemorySize(maxTextures));
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->currentMemoryUseBytes());
+    textureManager->reduceMemoryToLimit(texturesMemorySize(preferredTextures));
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->currentMemoryUseBytes());
+
+    const size_t unprotectedTextures = preferredTextures + 1;
+    for (size_t i = 0; i < preferredTextures + 1; ++i)
+        textureManager->unprotectTexture(tokens[i]);
+
+    textureManager->reduceMemoryToLimit(texturesMemorySize(maxTextures));
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->currentMemoryUseBytes());
+    textureManager->reduceMemoryToLimit(texturesMemorySize(preferredTextures));
+    EXPECT_EQ(texturesMemorySize(preferredTextures), textureManager->currentMemoryUseBytes());
+    textureManager->reduceMemoryToLimit(texturesMemorySize(1));
+    EXPECT_EQ(texturesMemorySize(maxTextures - unprotectedTextures), textureManager->currentMemoryUseBytes());
+
+    // reduceMemoryToLimit doesn't change the current memory limits.
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->maxMemoryLimitBytes());
+    EXPECT_EQ(texturesMemorySize(preferredTextures), textureManager->preferredMemoryLimitBytes());
+}
+
+TEST(TextureManagerTest, setMaxMemoryLimitBytes)
+{
+    const size_t maxTextures = 8;
+    const size_t preferredTextures = 4;
+    OwnPtr<TextureManager> textureManager = createTextureManager(maxTextures, preferredTextures);
+    TextureToken tokens[maxTextures];
+    for (size_t i = 0; i < maxTextures; ++i) {
+        tokens[i] = textureManager->getToken();
+        EXPECT_TRUE(requestTexture(textureManager.get(), tokens[i]));
+    }
+
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->currentMemoryUseBytes());
+
+    const size_t unprotectedTextures = preferredTextures + 1;
+    for (size_t i = 0; i < unprotectedTextures; ++i)
+        textureManager->unprotectTexture(tokens[i]);
+
+    textureManager->setMaxMemoryLimitBytes(texturesMemorySize(maxTextures));
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->currentMemoryUseBytes());
+    textureManager->setMaxMemoryLimitBytes(texturesMemorySize(preferredTextures));
+    EXPECT_EQ(texturesMemorySize(preferredTextures), textureManager->currentMemoryUseBytes());
+    EXPECT_EQ(texturesMemorySize(preferredTextures), textureManager->maxMemoryLimitBytes());
+}
+
+TEST(TextureManagerTest, setPreferredMemoryLimitBytes)
+{
+    const size_t maxTextures = 8;
+    const size_t preferredTextures = 4;
+    OwnPtr<TextureManager> textureManager = createTextureManager(maxTextures, preferredTextures);
+    TextureToken tokens[maxTextures];
+    for (size_t i = 0; i < maxTextures; ++i) {
+        tokens[i] = textureManager->getToken();
+        EXPECT_TRUE(requestTexture(textureManager.get(), tokens[i]));
+    }
+
+    const size_t unprotectedTextures = preferredTextures + 1;
+    for (size_t i = 0; i < unprotectedTextures; ++i)
+        textureManager->unprotectTexture(tokens[i]);
+
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->currentMemoryUseBytes());
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->maxMemoryLimitBytes());
+
+    // Setting preferred memory limit only won't force reduceMemoryToLimit.
+    textureManager->setPreferredMemoryLimitBytes(texturesMemorySize(preferredTextures));
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->currentMemoryUseBytes());
+    EXPECT_EQ(texturesMemorySize(maxTextures), textureManager->maxMemoryLimitBytes());
+    EXPECT_EQ(texturesMemorySize(preferredTextures), textureManager->preferredMemoryLimitBytes());
+}
+
+} // namespace