diff options
| author | Simon Hausmann <simon.hausmann@nokia.com> | 2012-06-20 13:01:08 +0200 |
|---|---|---|
| committer | Simon Hausmann <simon.hausmann@nokia.com> | 2012-06-20 13:01:08 +0200 |
| commit | 49233e234e5c787396cadb2cea33b31ae0cd65c1 (patch) | |
| tree | 5410cb9a8fd53168bb60d62c54b654d86f03c38d /Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp | |
| parent | b211c645d8ab690f713515dfdc84d80b11c27d2c (diff) | |
| download | qtwebkit-49233e234e5c787396cadb2cea33b31ae0cd65c1.tar.gz | |
Imported WebKit commit 3a8c29f35d00659d2ce7a0ccdfa8304f14e82327 (http://svn.webkit.org/repository/webkit/trunk@120813)
New snapshot with Windows build fixes
Diffstat (limited to 'Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp')
| -rw-r--r-- | Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp | 1787 |
1 files changed, 1713 insertions, 74 deletions
diff --git a/Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp b/Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp index 91c2d1dff..738c88190 100644 --- a/Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp +++ b/Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp @@ -28,18 +28,24 @@ #include "CCAnimationTestCommon.h" #include "CCLayerTestCommon.h" +#include "CCTestCommon.h" #include "FakeWebGraphicsContext3D.h" #include "GraphicsContext3DPrivate.h" #include "LayerRendererChromium.h" +#include "cc/CCIOSurfaceLayerImpl.h" #include "cc/CCLayerImpl.h" #include "cc/CCLayerTilingData.h" #include "cc/CCQuadCuller.h" +#include "cc/CCRenderPassDrawQuad.h" #include "cc/CCScrollbarLayerImpl.h" +#include "cc/CCSettings.h" #include "cc/CCSingleThreadProxy.h" +#include "cc/CCSolidColorDrawQuad.h" #include "cc/CCTextureLayerImpl.h" #include "cc/CCTileDrawQuad.h" #include "cc/CCTiledLayerImpl.h" #include "cc/CCVideoLayerImpl.h" +#include <gmock/gmock.h> #include <gtest/gtest.h> #include <public/WebVideoFrame.h> #include <public/WebVideoFrameProvider.h> @@ -49,6 +55,12 @@ using namespace WebCore; using namespace WebKit; using namespace WebKitTests; +using ::testing::Mock; +using ::testing::Return; +using ::testing::AnyNumber; +using ::testing::AtLeast; +using ::testing::_; + namespace { class CCLayerTreeHostImplTest : public testing::Test, public CCLayerTreeHostImplClient { @@ -57,7 +69,7 @@ public: : m_didRequestCommit(false) , m_didRequestRedraw(false) { - CCSettings settings; + CCLayerTreeSettings settings; m_hostImpl = CCLayerTreeHostImpl::create(settings, this); m_hostImpl->initializeLayerRenderer(createContext(), UnthrottledUploader); m_hostImpl->setViewportSize(IntSize(10, 10)); @@ -70,6 +82,28 @@ public: virtual void postAnimationEventsToMainThreadOnImplThread(PassOwnPtr<CCAnimationEventsVector>, double wallClockTime) OVERRIDE { } virtual void postSetContentsMemoryAllocationLimitBytesToMainThreadOnImplThread(size_t) OVERRIDE { } + PassOwnPtr<CCLayerTreeHostImpl> createLayerTreeHost(bool partialSwap, PassRefPtr<CCGraphicsContext> graphicsContext, PassOwnPtr<CCLayerImpl> rootPtr) + { + CCSettings::setPartialSwapEnabled(partialSwap); + + CCLayerTreeSettings settings; + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + myHostImpl->initializeLayerRenderer(graphicsContext, UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(10, 10)); + + OwnPtr<CCLayerImpl> root = rootPtr; + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(0, 0)); + root->setBounds(IntSize(10, 10)); + root->setContentBounds(IntSize(10, 10)); + root->setVisibleLayerRect(IntRect(0, 0, 10, 10)); + root->setDrawsContent(true); + myHostImpl->setRootLayer(root.release()); + return myHostImpl.release(); + } + static void expectClearedScrollDeltasRecursive(CCLayerImpl* layer) { ASSERT_EQ(layer->scrollDelta(), IntSize()); @@ -106,10 +140,31 @@ public: m_hostImpl->setRootLayer(root.release()); } + static PassOwnPtr<CCLayerImpl> createScrollableLayer(int id, const FloatPoint& position, const IntSize& size) + { + OwnPtr<CCLayerImpl> layer = CCLayerImpl::create(id); + layer->setScrollable(true); + layer->setDrawsContent(true); + layer->setPosition(position); + layer->setBounds(size); + layer->setContentBounds(size); + layer->setMaxScrollPosition(IntSize(size.width() * 2, size.height() * 2)); + return layer.release(); + } + + void initializeLayerRendererAndDrawFrame() + { + m_hostImpl->initializeLayerRenderer(createContext(), UnthrottledUploader); + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + } + protected: - PassRefPtr<GraphicsContext3D> createContext() + PassRefPtr<CCGraphicsContext> createContext() { - return GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new FakeWebGraphicsContext3D()), GraphicsContext3D::RenderDirectlyToHostWindow); + return CCGraphicsContext::create3D(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new FakeWebGraphicsContext3D()), GraphicsContext3D::RenderDirectlyToHostWindow)); } DebugScopedSetImplThread m_alwaysImplThread; @@ -118,6 +173,7 @@ protected: OwnPtr<CCLayerTreeHostImpl> m_hostImpl; bool m_didRequestCommit; bool m_didRequestRedraw; + CCScopedSettings m_scopedSettings; }; TEST_F(CCLayerTreeHostImplTest, scrollDeltaNoLayers) @@ -189,13 +245,9 @@ TEST_F(CCLayerTreeHostImplTest, scrollDeltaRepeatedScrolls) TEST_F(CCLayerTreeHostImplTest, scrollRootCallsCommitAndRedraw) { - { - OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); - root->setScrollable(true); - root->setScrollPosition(IntPoint(0, 0)); - root->setMaxScrollPosition(IntSize(100, 100)); - m_hostImpl->setRootLayer(root.release()); - } + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50)); + initializeLayerRendererAndDrawFrame(); EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); m_hostImpl->scrollBy(IntSize(0, 10)); @@ -204,20 +256,57 @@ TEST_F(CCLayerTreeHostImplTest, scrollRootCallsCommitAndRedraw) EXPECT_TRUE(m_didRequestCommit); } +TEST_F(CCLayerTreeHostImplTest, scrollWithoutRootLayer) +{ + // We should not crash when trying to scroll an empty layer tree. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollIgnored); +} + +TEST_F(CCLayerTreeHostImplTest, replaceTreeWhileScrolling) +{ + const int scrollLayerId = 0; + + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50)); + initializeLayerRendererAndDrawFrame(); + + // We should not crash if the tree is replaced while we are scrolling. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->detachLayerTree(); + + setupScrollAndContentsLayers(IntSize(100, 100)); + + // We should still be scrolling, because the scrolled layer also exists in the new tree. + IntSize scrollDelta(0, 10); + m_hostImpl->scrollBy(scrollDelta); + m_hostImpl->scrollEnd(); + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo, scrollLayerId, scrollDelta); +} + +TEST_F(CCLayerTreeHostImplTest, clearRootRenderSurfaceAndScroll) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50)); + initializeLayerRendererAndDrawFrame(); + + // We should be able to scroll even if the root layer loses its render surface after the most + // recent render. + m_hostImpl->rootLayer()->clearRenderSurface(); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); +} + TEST_F(CCLayerTreeHostImplTest, wheelEventHandlers) { - { - OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); - root->setScrollable(true); - root->setScrollPosition(IntPoint(0, 0)); - root->setMaxScrollPosition(IntSize(100, 100)); - m_hostImpl->setRootLayer(root.release()); - } + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50)); + initializeLayerRendererAndDrawFrame(); CCLayerImpl* root = m_hostImpl->rootLayer(); root->setHaveWheelEventHandlers(true); + // With registered event handlers, wheel scrolls have to go to the main thread. - EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollFailed); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread); // But gesture scrolls can still be handled. EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted); @@ -225,27 +314,29 @@ TEST_F(CCLayerTreeHostImplTest, wheelEventHandlers) TEST_F(CCLayerTreeHostImplTest, shouldScrollOnMainThread) { - OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); - root->setScrollable(true); - root->setScrollPosition(IntPoint(0, 0)); - root->setMaxScrollPosition(IntSize(100, 100)); + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50)); + initializeLayerRendererAndDrawFrame(); + CCLayerImpl* root = m_hostImpl->rootLayer(); + root->setShouldScrollOnMainThread(true); - m_hostImpl->setRootLayer(root.release()); - EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollFailed); - EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollFailed); + + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollOnMainThread); } TEST_F(CCLayerTreeHostImplTest, nonFastScrollableRegionBasic) { - OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); - root->setScrollable(true); - root->setScrollPosition(IntPoint(0, 0)); - root->setMaxScrollPosition(IntSize(100, 100)); + setupScrollAndContentsLayers(IntSize(200, 200)); + m_hostImpl->setViewportSize(IntSize(100, 100)); + initializeLayerRendererAndDrawFrame(); + CCLayerImpl* root = m_hostImpl->rootLayer(); + root->setNonFastScrollableRegion(IntRect(0, 0, 50, 50)); - m_hostImpl->setRootLayer(root.release()); + // All scroll types inside the non-fast scrollable region should fail. - EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(25, 25), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollFailed); - EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(25, 25), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollFailed); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(25, 25), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(25, 25), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollOnMainThread); // All scroll types outside this region should succeed. EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(75, 75), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); @@ -258,16 +349,13 @@ TEST_F(CCLayerTreeHostImplTest, nonFastScrollableRegionBasic) TEST_F(CCLayerTreeHostImplTest, nonFastScrollableRegionWithOffset) { - OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); - root->setScrollable(true); - root->setScrollPosition(IntPoint(0, 0)); - root->setMaxScrollPosition(IntSize(100, 100)); + setupScrollAndContentsLayers(IntSize(200, 200)); + m_hostImpl->setViewportSize(IntSize(100, 100)); + CCLayerImpl* root = m_hostImpl->rootLayer(); + root->setNonFastScrollableRegion(IntRect(0, 0, 50, 50)); root->setPosition(FloatPoint(-25, 0)); - m_hostImpl->setRootLayer(root.release()); - CCLayerTreeHostImpl::FrameData frame; - EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); - m_hostImpl->drawLayers(frame); // Update draw transforms so we can correctly map points into layer space. + initializeLayerRendererAndDrawFrame(); // This point would fall into the non-fast scrollable region except that we've moved the layer down by 25 pixels. EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(40, 10), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); @@ -275,15 +363,16 @@ TEST_F(CCLayerTreeHostImplTest, nonFastScrollableRegionWithOffset) m_hostImpl->scrollEnd(); // This point is still inside the non-fast region. - EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(10, 10), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollFailed); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(10, 10), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread); } TEST_F(CCLayerTreeHostImplTest, pinchGesture) { setupScrollAndContentsLayers(IntSize(100, 100)); m_hostImpl->setViewportSize(IntSize(50, 50)); + initializeLayerRendererAndDrawFrame(); - CCLayerImpl* scrollLayer = m_hostImpl->scrollLayer(); + CCLayerImpl* scrollLayer = m_hostImpl->rootScrollLayer(); ASSERT(scrollLayer); const float minPageScale = 0.5, maxPageScale = 4; @@ -362,8 +451,9 @@ TEST_F(CCLayerTreeHostImplTest, pageScaleAnimation) { setupScrollAndContentsLayers(IntSize(100, 100)); m_hostImpl->setViewportSize(IntSize(50, 50)); + initializeLayerRendererAndDrawFrame(); - CCLayerImpl* scrollLayer = m_hostImpl->scrollLayer(); + CCLayerImpl* scrollLayer = m_hostImpl->rootScrollLayer(); ASSERT(scrollLayer); const float minPageScale = 0.5, maxPageScale = 4; @@ -407,6 +497,101 @@ TEST_F(CCLayerTreeHostImplTest, pageScaleAnimation) } } +TEST_F(CCLayerTreeHostImplTest, inhibitScrollAndPageScaleUpdatesWhilePinchZooming) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50)); + initializeLayerRendererAndDrawFrame(); + + CCLayerImpl* scrollLayer = m_hostImpl->rootScrollLayer(); + ASSERT(scrollLayer); + + const float minPageScale = 0.5, maxPageScale = 4; + + // Pinch zoom in. + { + // Start a pinch in gesture at the bottom right corner of the viewport. + const float zoomInDelta = 2; + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(zoomInDelta, IntPoint(50, 50)); + + // Because we are pinch zooming in, we shouldn't get any scroll or page + // scale deltas. + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, 1); + EXPECT_EQ(scrollInfo->scrolls.size(), 0u); + + // Once the gesture ends, we get the final scroll and page scale values. + m_hostImpl->pinchGestureEnd(); + scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, zoomInDelta); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(25, 25)); + } + + // Pinch zoom out. + { + // Start a pinch out gesture at the bottom right corner of the viewport. + const float zoomOutDelta = 0.75; + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(zoomOutDelta, IntPoint(50, 50)); + + // Since we are pinch zooming out, we should get an update to zoom all + // the way out to the minimum page scale. + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, minPageScale); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(0, 0)); + + // Once the gesture ends, we get the final scroll and page scale values. + m_hostImpl->pinchGestureEnd(); + scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, zoomOutDelta); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(8, 8)); + } +} + +TEST_F(CCLayerTreeHostImplTest, inhibitScrollAndPageScaleUpdatesWhileAnimatingPageScale) +{ + setupScrollAndContentsLayers(IntSize(100, 100)); + m_hostImpl->setViewportSize(IntSize(50, 50)); + initializeLayerRendererAndDrawFrame(); + + CCLayerImpl* scrollLayer = m_hostImpl->rootScrollLayer(); + ASSERT(scrollLayer); + + const float minPageScale = 0.5, maxPageScale = 4; + const double startTime = 1; + const double duration = 0.1; + const double halfwayThroughAnimation = startTime + duration / 2; + const double endTime = startTime + duration; + + // Start a page scale animation. + const float pageScaleDelta = 2; + m_hostImpl->setPageScaleFactorAndLimits(1, minPageScale, maxPageScale); + m_hostImpl->startPageScaleAnimation(IntSize(50, 50), false, pageScaleDelta, startTime, duration); + + // We should immediately get the final zoom and scroll values for the + // animation. + m_hostImpl->animate(halfwayThroughAnimation, halfwayThroughAnimation); + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, pageScaleDelta); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(25, 25)); + + // Scrolling during the animation is ignored. + const IntSize scrollDelta(0, 10); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(25, 25), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(scrollDelta); + m_hostImpl->scrollEnd(); + + // The final page scale and scroll deltas should match what we got + // earlier. + m_hostImpl->animate(endTime, endTime); + scrollInfo = m_hostImpl->processScrollDeltas(); + EXPECT_EQ(scrollInfo->pageScaleDelta, pageScaleDelta); + expectContains(*scrollInfo, scrollLayer->id(), IntSize(25, 25)); +} + class DidDrawCheckLayer : public CCTiledLayerImpl { public: static PassOwnPtr<DidDrawCheckLayer> create(int id) { return adoptPtr(new DidDrawCheckLayer(id)); } @@ -416,7 +601,7 @@ public: m_didDrawCalled = true; } - virtual void willDraw(LayerRendererChromium*) + virtual void willDraw(CCRenderer*, CCGraphicsContext*) { m_willDrawCalled = true; } @@ -424,6 +609,12 @@ public: bool didDrawCalled() const { return m_didDrawCalled; } bool willDrawCalled() const { return m_willDrawCalled; } + void clearDidDrawCheck() + { + m_didDrawCalled = false; + m_willDrawCalled = false; + } + protected: explicit DidDrawCheckLayer(int id) : CCTiledLayerImpl(id) @@ -432,7 +623,12 @@ protected: { setAnchorPoint(FloatPoint(0, 0)); setBounds(IntSize(10, 10)); + setContentBounds(IntSize(10, 10)); setDrawsContent(true); + setSkipsDraw(false); + + OwnPtr<CCLayerTilingData> tiler = CCLayerTilingData::create(IntSize(100, 100), CCLayerTilingData::HasBorderTexels); + setTilingData(*tiler.get()); } private: @@ -485,6 +681,42 @@ TEST_F(CCLayerTreeHostImplTest, didDrawNotCalledOnHiddenLayer) EXPECT_FALSE(layer->visibleLayerRect().isEmpty()); } +TEST_F(CCLayerTreeHostImplTest, willDrawNotCalledOnOccludedLayer) +{ + // Make the viewport large so that we can have large layers that get considered for occlusion (small layers do not). + IntSize bigSize(1000, 1000); + m_hostImpl->setViewportSize(bigSize); + + m_hostImpl->setRootLayer(DidDrawCheckLayer::create(0)); + DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(m_hostImpl->rootLayer()); + + root->addChild(DidDrawCheckLayer::create(1)); + DidDrawCheckLayer* occludedLayer = static_cast<DidDrawCheckLayer*>(root->children()[0].get()); + + root->addChild(DidDrawCheckLayer::create(2)); + DidDrawCheckLayer* topLayer = static_cast<DidDrawCheckLayer*>(root->children()[1].get()); + // This layer covers the occludedLayer above. Make this layer large so it can occlude. + topLayer->setBounds(bigSize); + topLayer->setContentBounds(bigSize); + topLayer->setOpaque(true); + + CCLayerTreeHostImpl::FrameData frame; + + EXPECT_FALSE(occludedLayer->willDrawCalled()); + EXPECT_FALSE(occludedLayer->didDrawCalled()); + EXPECT_FALSE(topLayer->willDrawCalled()); + EXPECT_FALSE(topLayer->didDrawCalled()); + + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + + EXPECT_FALSE(occludedLayer->willDrawCalled()); + EXPECT_FALSE(occludedLayer->didDrawCalled()); + EXPECT_TRUE(topLayer->willDrawCalled()); + EXPECT_TRUE(topLayer->didDrawCalled()); +} + TEST_F(CCLayerTreeHostImplTest, didDrawCalledOnAllLayers) { m_hostImpl->setRootLayer(DidDrawCheckLayer::create(0)); @@ -562,9 +794,7 @@ TEST_F(CCLayerTreeHostImplTest, prepareToDrawFailsWhenAnimationUsesCheckerboard) root = static_cast<DidDrawCheckLayer*>(m_hostImpl->rootLayer()); root->addChild(MissingTextureAnimatingLayer::create(1, true, false, true)); - m_didRequestCommit = false; EXPECT_FALSE(m_hostImpl->prepareToDraw(frame)); - EXPECT_TRUE(m_didRequestCommit); m_hostImpl->drawLayers(frame); m_hostImpl->didDrawAllLayers(frame); @@ -578,6 +808,299 @@ TEST_F(CCLayerTreeHostImplTest, prepareToDrawFailsWhenAnimationUsesCheckerboard) m_hostImpl->didDrawAllLayers(frame); } +TEST_F(CCLayerTreeHostImplTest, scrollRootIgnored) +{ + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); + root->setScrollable(false); + m_hostImpl->setRootLayer(root.release()); + initializeLayerRendererAndDrawFrame(); + + // Scroll event is ignored because layer is not scrollable. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollIgnored); + EXPECT_FALSE(m_didRequestRedraw); + EXPECT_FALSE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollNonCompositedRoot) +{ + // Test the configuration where a non-composited root layer is embedded in a + // scrollable outer layer. + IntSize surfaceSize(10, 10); + + OwnPtr<CCLayerImpl> contentLayer = CCLayerImpl::create(1); + contentLayer->setIsNonCompositedContent(true); + contentLayer->setDrawsContent(true); + contentLayer->setPosition(IntPoint(5, 5)); + contentLayer->setBounds(surfaceSize); + contentLayer->setContentBounds(IntSize(surfaceSize.width() * 2, surfaceSize.height() * 2)); + + OwnPtr<CCLayerImpl> scrollLayer = CCLayerImpl::create(0); + scrollLayer->setScrollable(true); + scrollLayer->setMaxScrollPosition(surfaceSize); + scrollLayer->addChild(contentLayer.release()); + + m_hostImpl->setRootLayer(scrollLayer.release()); + m_hostImpl->setViewportSize(surfaceSize); + initializeLayerRendererAndDrawFrame(); + + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntSize(0, 10)); + m_hostImpl->scrollEnd(); + EXPECT_TRUE(m_didRequestRedraw); + EXPECT_TRUE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollChildCallsCommitAndRedraw) +{ + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); + root->addChild(createScrollableLayer(1, FloatPoint(5, 5), surfaceSize)); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize); + initializeLayerRendererAndDrawFrame(); + + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(IntSize(0, 10)); + m_hostImpl->scrollEnd(); + EXPECT_TRUE(m_didRequestRedraw); + EXPECT_TRUE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollMissesChild) +{ + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); + root->addChild(createScrollableLayer(1, FloatPoint(5, 5), surfaceSize)); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize); + initializeLayerRendererAndDrawFrame(); + + // Scroll event is ignored because the input coordinate is outside the layer boundaries. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(15, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollIgnored); + EXPECT_FALSE(m_didRequestRedraw); + EXPECT_FALSE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollMissesBackfacingChild) +{ + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); + OwnPtr<CCLayerImpl> child = createScrollableLayer(1, FloatPoint(5, 5), surfaceSize); + m_hostImpl->setViewportSize(surfaceSize); + + WebTransformationMatrix matrix; + matrix.rotate3d(180, 0, 0); + child->setTransform(matrix); + child->setDoubleSided(false); + + root->addChild(child.release()); + m_hostImpl->setRootLayer(root.release()); + initializeLayerRendererAndDrawFrame(); + + // Scroll event is ignored because the scrollable layer is not facing the viewer and there is + // nothing scrollable behind it. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollIgnored); + EXPECT_FALSE(m_didRequestRedraw); + EXPECT_FALSE(m_didRequestCommit); +} + +TEST_F(CCLayerTreeHostImplTest, scrollBlockedByContentLayer) +{ + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> contentLayer = createScrollableLayer(0, FloatPoint(5, 5), surfaceSize); + contentLayer->setShouldScrollOnMainThread(true); + contentLayer->setScrollable(false); + + OwnPtr<CCLayerImpl> scrollLayer = createScrollableLayer(1, FloatPoint(5, 5), surfaceSize); + scrollLayer->addChild(contentLayer.release()); + + m_hostImpl->setRootLayer(scrollLayer.release()); + m_hostImpl->setViewportSize(surfaceSize); + initializeLayerRendererAndDrawFrame(); + + // Scrolling fails because the content layer is asking to be scrolled on the main thread. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollOnMainThread); +} + +TEST_F(CCLayerTreeHostImplTest, scrollRootAndChangePageScaleOnMainThread) +{ + IntSize surfaceSize(10, 10); + float pageScale = 2; + OwnPtr<CCLayerImpl> root = createScrollableLayer(0, FloatPoint(5, 5), surfaceSize); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize); + initializeLayerRendererAndDrawFrame(); + + IntSize scrollDelta(0, 10); + IntSize expectedScrollDelta(scrollDelta); + IntSize expectedMaxScroll(m_hostImpl->rootLayer()->maxScrollPosition()); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(scrollDelta); + m_hostImpl->scrollEnd(); + + // Set new page scale from main thread. + m_hostImpl->setPageScaleFactorAndLimits(pageScale, pageScale, pageScale); + + // The scale should apply to the scroll delta. + expectedScrollDelta.scale(pageScale); + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), 0, expectedScrollDelta); + + // The scroll range should also have been updated. + EXPECT_EQ(m_hostImpl->rootLayer()->maxScrollPosition(), expectedMaxScroll); + + // The page scale delta remains constant because the impl thread did not scale. + EXPECT_EQ(m_hostImpl->rootLayer()->pageScaleDelta(), 1); +} + +TEST_F(CCLayerTreeHostImplTest, scrollRootAndChangePageScaleOnImplThread) +{ + IntSize surfaceSize(10, 10); + float pageScale = 2; + OwnPtr<CCLayerImpl> root = createScrollableLayer(0, FloatPoint(5, 5), surfaceSize); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize); + m_hostImpl->setPageScaleFactorAndLimits(1, 1, pageScale); + initializeLayerRendererAndDrawFrame(); + + IntSize scrollDelta(0, 10); + IntSize expectedScrollDelta(scrollDelta); + IntSize expectedMaxScroll(m_hostImpl->rootLayer()->maxScrollPosition()); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(scrollDelta); + m_hostImpl->scrollEnd(); + + // Set new page scale on impl thread by pinching. + m_hostImpl->pinchGestureBegin(); + m_hostImpl->pinchGestureUpdate(pageScale, IntPoint()); + m_hostImpl->pinchGestureEnd(); + + // The scroll delta is not scaled because the main thread did not scale. + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), 0, expectedScrollDelta); + + // The scroll range should also have been updated. + EXPECT_EQ(m_hostImpl->rootLayer()->maxScrollPosition(), expectedMaxScroll); + + // The page scale delta should match the new scale on the impl side. + EXPECT_EQ(m_hostImpl->rootLayer()->pageScaleDelta(), pageScale); +} + +TEST_F(CCLayerTreeHostImplTest, scrollChildAndChangePageScaleOnMainThread) +{ + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); + // Also mark the root scrollable so it becomes the root scroll layer. + root->setScrollable(true); + root->addChild(createScrollableLayer(1, FloatPoint(5, 5), surfaceSize)); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize); + initializeLayerRendererAndDrawFrame(); + + CCLayerImpl* child = m_hostImpl->rootLayer()->children()[0].get(); + + IntSize scrollDelta(0, 10); + IntSize expectedScrollDelta(scrollDelta); + IntSize expectedMaxScroll(child->maxScrollPosition()); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(scrollDelta); + m_hostImpl->scrollEnd(); + + float pageScale = 2; + m_hostImpl->setPageScaleFactorAndLimits(pageScale, 1, pageScale); + + // The scale should apply to the scroll delta. + expectedScrollDelta.scale(pageScale); + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + expectContains(*scrollInfo.get(), 1, expectedScrollDelta); + + // The scroll range should not have changed. + EXPECT_EQ(child->maxScrollPosition(), expectedMaxScroll); + + // The page scale delta remains constant because the impl thread did not scale. + EXPECT_EQ(child->pageScaleDelta(), 1); +} + +TEST_F(CCLayerTreeHostImplTest, scrollChildBeyondLimit) +{ + // Scroll a child layer beyond its maximum scroll range and make sure the + // parent layer is scrolled on the axis on which the child was unable to + // scroll. + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = createScrollableLayer(0, FloatPoint(5, 5), surfaceSize); + + OwnPtr<CCLayerImpl> grandChild = createScrollableLayer(2, FloatPoint(5, 5), surfaceSize); + grandChild->setScrollPosition(IntPoint(0, 5)); + + OwnPtr<CCLayerImpl> child = createScrollableLayer(1, FloatPoint(5, 5), surfaceSize); + child->setScrollPosition(IntPoint(3, 0)); + child->addChild(grandChild.release()); + + root->addChild(child.release()); + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize); + initializeLayerRendererAndDrawFrame(); + { + IntSize scrollDelta(-3, -7); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(scrollDelta); + m_hostImpl->scrollEnd(); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + + // The grand child should have scrolled up to its limit. + CCLayerImpl* child = m_hostImpl->rootLayer()->children()[0].get(); + CCLayerImpl* grandChild = child->children()[0].get(); + expectContains(*scrollInfo.get(), grandChild->id(), IntSize(0, -5)); + + // The child should have only scrolled on the other axis. + expectContains(*scrollInfo.get(), child->id(), IntSize(-3, 0)); + } +} + +TEST_F(CCLayerTreeHostImplTest, scrollEventBubbling) +{ + // When we try to scroll a non-scrollable child layer, the scroll delta + // should be applied to one of its ancestors if possible. + IntSize surfaceSize(10, 10); + OwnPtr<CCLayerImpl> root = createScrollableLayer(0, FloatPoint(5, 5), surfaceSize); + OwnPtr<CCLayerImpl> child = createScrollableLayer(1, FloatPoint(5, 5), surfaceSize); + + child->setScrollable(false); + root->addChild(child.release()); + + m_hostImpl->setRootLayer(root.release()); + m_hostImpl->setViewportSize(surfaceSize); + initializeLayerRendererAndDrawFrame(); + { + IntSize scrollDelta(0, 4); + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); + m_hostImpl->scrollBy(scrollDelta); + m_hostImpl->scrollEnd(); + + OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas(); + + // Only the root should have scrolled. + ASSERT_EQ(scrollInfo->scrolls.size(), 1u); + expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), scrollDelta); + } +} + +TEST_F(CCLayerTreeHostImplTest, scrollBeforeRedraw) +{ + IntSize surfaceSize(10, 10); + m_hostImpl->setRootLayer(createScrollableLayer(0, FloatPoint(5, 5), surfaceSize)); + m_hostImpl->setViewportSize(surfaceSize); + + // Draw one frame and then immediately rebuild the layer tree to mimic a tree synchronization. + initializeLayerRendererAndDrawFrame(); + m_hostImpl->detachLayerTree(); + m_hostImpl->setRootLayer(createScrollableLayer(0, FloatPoint(5, 5), surfaceSize)); + + // Scrolling should still work even though we did not draw yet. + EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted); +} + class BlendStateTrackerContext: public FakeWebGraphicsContext3D { public: BlendStateTrackerContext() : m_blend(false) { } @@ -646,6 +1169,7 @@ private: { setAnchorPoint(FloatPoint(0, 0)); setBounds(IntSize(10, 10)); + setContentBounds(IntSize(10, 10)); setDrawsContent(true); } @@ -666,6 +1190,7 @@ TEST_F(CCLayerTreeHostImplTest, blendingOffWhenDrawingOpaqueLayers) OwnPtr<CCLayerImpl> root = CCLayerImpl::create(0); root->setAnchorPoint(FloatPoint(0, 0)); root->setBounds(IntSize(10, 10)); + root->setContentBounds(root->bounds()); root->setDrawsContent(false); m_hostImpl->setRootLayer(root.release()); } @@ -988,7 +1513,8 @@ TEST_F(CCLayerTreeHostImplTest, reshapeNotCalledUntilDraw) { ReshapeTrackerContext* reshapeTracker = new ReshapeTrackerContext(); RefPtr<GraphicsContext3D> context = GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(reshapeTracker), GraphicsContext3D::RenderDirectlyToHostWindow); - m_hostImpl->initializeLayerRenderer(context, UnthrottledUploader); + RefPtr<CCGraphicsContext> ccContext = CCGraphicsContext::create3D(context); + m_hostImpl->initializeLayerRenderer(ccContext, UnthrottledUploader); CCLayerImpl* root = new FakeDrawableCCLayerImpl(1); root->setAnchorPoint(FloatPoint(0, 0)); @@ -1031,13 +1557,14 @@ TEST_F(CCLayerTreeHostImplTest, partialSwapReceivesDamageRect) { PartialSwapTrackerContext* partialSwapTracker = new PartialSwapTrackerContext(); RefPtr<GraphicsContext3D> context = GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(partialSwapTracker), GraphicsContext3D::RenderDirectlyToHostWindow); + RefPtr<CCGraphicsContext> ccContext = CCGraphicsContext::create3D(context); // This test creates its own CCLayerTreeHostImpl, so // that we can force partial swap enabled. - CCSettings settings; - settings.partialSwapEnabled = true; + CCLayerTreeSettings settings; + CCSettings::setPartialSwapEnabled(true); OwnPtr<CCLayerTreeHostImpl> layerTreeHostImpl = CCLayerTreeHostImpl::create(settings, this); - layerTreeHostImpl->initializeLayerRenderer(context, UnthrottledUploader); + layerTreeHostImpl->initializeLayerRenderer(ccContext, UnthrottledUploader); layerTreeHostImpl->setViewportSize(IntSize(500, 500)); CCLayerImpl* root = new FakeDrawableCCLayerImpl(1); @@ -1099,6 +1626,349 @@ TEST_F(CCLayerTreeHostImplTest, partialSwapReceivesDamageRect) EXPECT_EQ(expectedSwapRect.height(), actualSwapRect.height()); } +} // namespace + +class FakeLayerWithQuads : public CCLayerImpl { +public: + static PassOwnPtr<FakeLayerWithQuads> create(int id) { return adoptPtr(new FakeLayerWithQuads(id)); } + + virtual void appendQuads(CCQuadCuller& quadList, const CCSharedQuadState* sharedQuadState, bool&) OVERRIDE + { + const Color gray(100, 100, 100); + IntRect quadRect(0, 0, 5, 5); + OwnPtr<CCDrawQuad> myQuad = CCSolidColorDrawQuad::create(sharedQuadState, quadRect, gray); + quadList.append(myQuad.release()); + } + +private: + FakeLayerWithQuads(int id) + : CCLayerImpl(id) + { + } +}; + +namespace { + +class MockContext : public FakeWebGraphicsContext3D { +public: + MOCK_METHOD1(useProgram, void(WebGLId program)); + MOCK_METHOD5(uniform4f, void(WGC3Dint location, WGC3Dfloat x, WGC3Dfloat y, WGC3Dfloat z, WGC3Dfloat w)); + MOCK_METHOD4(uniformMatrix4fv, void(WGC3Dint location, WGC3Dsizei count, WGC3Dboolean transpose, const WGC3Dfloat* value)); + MOCK_METHOD4(drawElements, void(WGC3Denum mode, WGC3Dsizei count, WGC3Denum type, WGC3Dintptr offset)); + MOCK_METHOD1(getString, WebString(WGC3Denum name)); + MOCK_METHOD0(getRequestableExtensionsCHROMIUM, WebString()); + MOCK_METHOD1(enable, void(WGC3Denum cap)); + MOCK_METHOD4(scissor, void(WGC3Dint x, WGC3Dint y, WGC3Dsizei width, WGC3Dsizei height)); +}; + +class MockContextHarness { +private: + MockContext* m_context; +public: + MockContextHarness(MockContext* context) + : m_context(context) + { + // Catch "uninteresting" calls + EXPECT_CALL(*m_context, useProgram(_)) + .Times(0); + + EXPECT_CALL(*m_context, drawElements(_, _, _, _)) + .Times(0); + + // These are not asserted + EXPECT_CALL(*m_context, uniformMatrix4fv(_, _, _, _)) + .WillRepeatedly(Return()); + + EXPECT_CALL(*m_context, uniform4f(_, _, _, _, _)) + .WillRepeatedly(Return()); + + // Any other strings are empty + EXPECT_CALL(*m_context, getString(_)) + .WillRepeatedly(Return(WebString())); + + // Support for partial swap, if needed + EXPECT_CALL(*m_context, getString(GraphicsContext3D::EXTENSIONS)) + .WillRepeatedly(Return(WebString("GL_CHROMIUM_post_sub_buffer"))); + + EXPECT_CALL(*m_context, getRequestableExtensionsCHROMIUM()) + .WillRepeatedly(Return(WebString("GL_CHROMIUM_post_sub_buffer"))); + + // Any un-sanctioned calls to enable() are OK + EXPECT_CALL(*m_context, enable(_)) + .WillRepeatedly(Return()); + } + + void mustDrawSolidQuad() + { + EXPECT_CALL(*m_context, drawElements(GraphicsContext3D::TRIANGLES, 6, GraphicsContext3D::UNSIGNED_SHORT, 0)) + .WillOnce(Return()) + .RetiresOnSaturation(); + + // 1 is hardcoded return value of fake createProgram() + EXPECT_CALL(*m_context, useProgram(1)) + .WillOnce(Return()) + .RetiresOnSaturation(); + + } + + void mustSetScissor(int x, int y, int width, int height) + { + EXPECT_CALL(*m_context, enable(GraphicsContext3D::SCISSOR_TEST)) + .WillRepeatedly(Return()); + + EXPECT_CALL(*m_context, scissor(x, y, width, height)) + .Times(AtLeast(1)) + .WillRepeatedly(Return()); + } + +}; + +TEST_F(CCLayerTreeHostImplTest, noPartialSwap) +{ + MockContext* mockContext = new MockContext(); + RefPtr<CCGraphicsContext> context = CCGraphicsContext::create3D(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(mockContext), GraphicsContext3D::RenderDirectlyToHostWindow)); + MockContextHarness harness(mockContext); + + harness.mustDrawSolidQuad(); + harness.mustSetScissor(0, 0, 10, 10); + + // Run test case + OwnPtr<CCLayerTreeHostImpl> myHostImpl = createLayerTreeHost(false, context, FakeLayerWithQuads::create(1)); + + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + Mock::VerifyAndClearExpectations(&mockContext); +} + +TEST_F(CCLayerTreeHostImplTest, partialSwap) +{ + MockContext* mockContext = new MockContext(); + RefPtr<CCGraphicsContext> context = CCGraphicsContext::create3D(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(mockContext), GraphicsContext3D::RenderDirectlyToHostWindow)); + MockContextHarness harness(mockContext); + + harness.mustDrawSolidQuad(); + harness.mustSetScissor(0, 0, 10, 10); + + OwnPtr<CCLayerTreeHostImpl> myHostImpl = createLayerTreeHost(true, context, FakeLayerWithQuads::create(1)); + + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + Mock::VerifyAndClearExpectations(&mockContext); +} + +TEST_F(CCLayerTreeHostImplTest, partialSwapNoUpdate) +{ + MockContext* mockContext = new MockContext(); + RefPtr<CCGraphicsContext> context = CCGraphicsContext::create3D(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(mockContext), GraphicsContext3D::RenderDirectlyToHostWindow)); + MockContextHarness harness(mockContext); + + harness.mustDrawSolidQuad(); + harness.mustSetScissor(0, 8, 2, 2); + harness.mustDrawSolidQuad(); + harness.mustSetScissor(0, 0, 10, 10); + + OwnPtr<CCLayerTreeHostImpl> myHostImpl = createLayerTreeHost(true, context, FakeLayerWithQuads::create(1)); + + // Draw once to make sure layer is not new + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + + // Generate update in layer + CCLayerImpl* root = myHostImpl->rootLayer(); + root->setUpdateRect(FloatRect(0, 0, 2, 2)); + + // This draw should generate no new udpates + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + + Mock::VerifyAndClearExpectations(&mockContext); +} + +class PartialSwapContext: public FakeWebGraphicsContext3D { +public: + WebString getString(WGC3Denum name) + { + if (name == GraphicsContext3D::EXTENSIONS) + return WebString("GL_CHROMIUM_post_sub_buffer"); + return WebString(); + } + + WebString getRequestableExtensionsCHROMIUM() + { + return WebString("GL_CHROMIUM_post_sub_buffer"); + } +}; + +static PassOwnPtr<CCLayerTreeHostImpl> setupLayersForOpacity(bool partialSwap, CCLayerTreeHostImplClient* client) +{ + CCSettings::setPartialSwapEnabled(partialSwap); + + RefPtr<CCGraphicsContext> context = CCGraphicsContext::create3D(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new PartialSwapContext()), GraphicsContext3D::RenderDirectlyToHostWindow)); + + CCLayerTreeSettings settings; + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, client); + myHostImpl->initializeLayerRenderer(context.release(), UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(100, 100)); + + /* + Layers are created as follows: + + +--------------------+ + | 1 | + | +-----------+ | + | | 2 | | + | | +-------------------+ + | | | 3 | + | | +-------------------+ + | | | | + | +-----------+ | + | | + | | + +--------------------+ + + Layers 1, 2 have render surfaces + */ + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + OwnPtr<CCLayerImpl> child = CCLayerImpl::create(2); + OwnPtr<CCLayerImpl> grandChild = FakeLayerWithQuads::create(3); + + IntRect rootRect(0, 0, 100, 100); + IntRect childRect(10, 10, 50, 50); + IntRect grandChildRect(5, 5, 150, 150); + + root->createRenderSurface(); + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(rootRect.x(), rootRect.y())); + root->setBounds(IntSize(rootRect.width(), rootRect.height())); + root->setContentBounds(root->bounds()); + root->setVisibleLayerRect(rootRect); + root->setDrawsContent(false); + root->renderSurface()->setContentRect(IntRect(IntPoint(), IntSize(rootRect.width(), rootRect.height()))); + + child->setAnchorPoint(FloatPoint(0, 0)); + child->setPosition(FloatPoint(childRect.x(), childRect.y())); + child->setOpacity(0.5f); + child->setBounds(IntSize(childRect.width(), childRect.height())); + child->setContentBounds(child->bounds()); + child->setVisibleLayerRect(childRect); + child->setDrawsContent(false); + + grandChild->setAnchorPoint(FloatPoint(0, 0)); + grandChild->setPosition(IntPoint(grandChildRect.x(), grandChildRect.y())); + grandChild->setBounds(IntSize(grandChildRect.width(), grandChildRect.height())); + grandChild->setContentBounds(grandChild->bounds()); + grandChild->setVisibleLayerRect(grandChildRect); + grandChild->setDrawsContent(true); + + child->addChild(grandChild.release()); + root->addChild(child.release()); + + myHostImpl->setRootLayer(root.release()); + return myHostImpl.release(); +} + +TEST_F(CCLayerTreeHostImplTest, contributingLayerEmptyScissorPartialSwap) +{ + OwnPtr<CCLayerTreeHostImpl> myHostImpl = setupLayersForOpacity(true, this); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Just for consistency, the most interesting stuff already happened + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + + // Verify all quads have been computed + ASSERT_EQ(2U, frame.renderPasses.size()); + ASSERT_EQ(1U, frame.renderPasses[0]->quadList().size()); + ASSERT_EQ(1U, frame.renderPasses[1]->quadList().size()); + EXPECT_EQ(CCDrawQuad::SolidColor, frame.renderPasses[0]->quadList()[0]->material()); + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + } +} + +TEST_F(CCLayerTreeHostImplTest, contributingLayerEmptyScissorNoPartialSwap) +{ + OwnPtr<CCLayerTreeHostImpl> myHostImpl = setupLayersForOpacity(false, this); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Just for consistency, the most interesting stuff already happened + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + + // Verify all quads have been computed + ASSERT_EQ(2U, frame.renderPasses.size()); + ASSERT_EQ(1U, frame.renderPasses[0]->quadList().size()); + ASSERT_EQ(1U, frame.renderPasses[1]->quadList().size()); + EXPECT_EQ(CCDrawQuad::SolidColor, frame.renderPasses[0]->quadList()[0]->material()); + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + } +} + +TEST_F(CCLayerTreeHostImplTest, didDrawNotCalledOnScissoredLayer) +{ + CCLayerTreeSettings settings; + CCSettings::setPartialSwapEnabled(true); + + RefPtr<CCGraphicsContext> context = CCGraphicsContext::create3D(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new PartialSwapContext()), GraphicsContext3D::RenderDirectlyToHostWindow)); + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + myHostImpl->initializeLayerRenderer(context.release(), UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(10, 10)); + + myHostImpl->setRootLayer(DidDrawCheckLayer::create(1)); + DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(myHostImpl->rootLayer()); + root->setMasksToBounds(true); + + root->addChild(DidDrawCheckLayer::create(2)); + DidDrawCheckLayer* layer = static_cast<DidDrawCheckLayer*>(root->children()[0].get()); + + CCLayerTreeHostImpl::FrameData frame; + + EXPECT_FALSE(root->willDrawCalled()); + EXPECT_FALSE(root->didDrawCalled()); + EXPECT_FALSE(layer->willDrawCalled()); + EXPECT_FALSE(layer->didDrawCalled()); + + // We should draw everything the first frame. + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + + EXPECT_TRUE(root->willDrawCalled()); + EXPECT_TRUE(root->didDrawCalled()); + EXPECT_TRUE(layer->willDrawCalled()); + EXPECT_TRUE(layer->didDrawCalled()); + + root->clearDidDrawCheck(); + layer->clearDidDrawCheck(); + + EXPECT_FALSE(root->willDrawCalled()); + EXPECT_FALSE(root->didDrawCalled()); + EXPECT_FALSE(layer->willDrawCalled()); + EXPECT_FALSE(layer->didDrawCalled()); + + // Drawing again, we should scissor out everything since there is no damage. + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + + EXPECT_FALSE(root->willDrawCalled()); + EXPECT_FALSE(root->didDrawCalled()); + EXPECT_FALSE(layer->willDrawCalled()); + EXPECT_FALSE(layer->didDrawCalled()); +} + // Make sure that context lost notifications are propagated through the tree. class ContextLostNotificationCheckLayer : public CCLayerImpl { public: @@ -1151,7 +2021,7 @@ public: TEST_F(CCLayerTreeHostImplTest, finishAllRenderingAfterContextLost) { // The context initialization will fail, but we should still be able to call finishAllRendering() without any ill effects. - m_hostImpl->initializeLayerRenderer(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new FakeWebGraphicsContext3DMakeCurrentFails), GraphicsContext3D::RenderDirectlyToHostWindow), UnthrottledUploader); + m_hostImpl->initializeLayerRenderer(CCGraphicsContext::create3D(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new FakeWebGraphicsContext3DMakeCurrentFails), GraphicsContext3D::RenderDirectlyToHostWindow)), UnthrottledUploader); m_hostImpl->finishAllRendering(); } @@ -1165,26 +2035,6 @@ private: ScrollbarLayerFakePaint(int id) : CCScrollbarLayerImpl(id) { } }; -TEST_F(CCLayerTreeHostImplTest, scrollbarLayerLostContext) -{ - m_hostImpl->setRootLayer(ScrollbarLayerFakePaint::create(0)); - ScrollbarLayerFakePaint* scrollbar = static_cast<ScrollbarLayerFakePaint*>(m_hostImpl->rootLayer()); - scrollbar->setBounds(IntSize(1, 1)); - scrollbar->setContentBounds(IntSize(1, 1)); - scrollbar->setDrawsContent(true); - - for (int i = 0; i < 2; ++i) { - CCLayerTreeHostImpl::FrameData frame; - EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); - ASSERT(frame.renderPasses.size() == 1); - CCRenderPass* renderPass = frame.renderPasses[0].get(); - // Scrollbar layer should always generate quads, even after lost context - EXPECT_GT(renderPass->quadList().size(), 0u); - m_hostImpl->didDrawAllLayers(frame); - m_hostImpl->initializeLayerRenderer(createContext(), UnthrottledUploader); - } -} - // Fake WebGraphicsContext3D that will cause a failure if trying to use a // resource that wasn't created by it (resources created by // FakeWebGraphicsContext3D have an id of 1). @@ -1312,6 +2162,38 @@ private: Client* m_client; }; +class StrictWebGraphicsContext3DWithIOSurface : public StrictWebGraphicsContext3D { +public: + virtual WebString getString(WGC3Denum name) OVERRIDE + { + if (name == WebCore::GraphicsContext3D::EXTENSIONS) + return WebString("GL_CHROMIUM_iosurface GL_ARB_texture_rectangle"); + + return WebString(); + } + + static PassRefPtr<GraphicsContext3D> createGraphicsContext() + { + return GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new StrictWebGraphicsContext3DWithIOSurface()), GraphicsContext3D::RenderDirectlyToHostWindow); + } +}; + +class FakeWebGraphicsContext3DWithIOSurface : public FakeWebGraphicsContext3D { +public: + virtual WebString getString(WGC3Denum name) OVERRIDE + { + if (name == WebCore::GraphicsContext3D::EXTENSIONS) + return WebString("GL_CHROMIUM_iosurface GL_ARB_texture_rectangle"); + + return WebString(); + } + + static PassRefPtr<GraphicsContext3D> createGraphicsContext() + { + return GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new FakeWebGraphicsContext3DWithIOSurface()), GraphicsContext3D::RenderDirectlyToHostWindow); + } +}; + TEST_F(CCLayerTreeHostImplTest, dontUseOldResourcesAfterLostContext) { OwnPtr<CCLayerImpl> rootLayer(CCLayerImpl::create(0)); @@ -1344,8 +2226,21 @@ TEST_F(CCLayerTreeHostImplTest, dontUseOldResourcesAfterLostContext) videoLayer->setAnchorPoint(FloatPoint(0, 0)); videoLayer->setContentBounds(IntSize(10, 10)); videoLayer->setDrawsContent(true); + videoLayer->setLayerTreeHostImpl(m_hostImpl.get()); rootLayer->addChild(videoLayer.release()); + OwnPtr<CCIOSurfaceLayerImpl> ioSurfaceLayer = CCIOSurfaceLayerImpl::create(4); + ioSurfaceLayer->setBounds(IntSize(10, 10)); + ioSurfaceLayer->setAnchorPoint(FloatPoint(0, 0)); + ioSurfaceLayer->setContentBounds(IntSize(10, 10)); + ioSurfaceLayer->setDrawsContent(true); + ioSurfaceLayer->setIOSurfaceProperties(1, IntSize(10, 10)); + ioSurfaceLayer->setLayerTreeHostImpl(m_hostImpl.get()); + rootLayer->addChild(ioSurfaceLayer.release()); + + // Use a context that supports IOSurfaces + m_hostImpl->initializeLayerRenderer(CCGraphicsContext::create3D(FakeWebGraphicsContext3DWithIOSurface::createGraphicsContext()), UnthrottledUploader); + m_hostImpl->setRootLayer(rootLayer.release()); CCLayerTreeHostImpl::FrameData frame; @@ -1354,13 +2249,757 @@ TEST_F(CCLayerTreeHostImplTest, dontUseOldResourcesAfterLostContext) m_hostImpl->didDrawAllLayers(frame); m_hostImpl->swapBuffers(); - // Lose the context, replacing it with a StrictWebGraphicsContext3D, that - // will warn if any resource from the previous context gets used. - m_hostImpl->initializeLayerRenderer(StrictWebGraphicsContext3D::createGraphicsContext(), UnthrottledUploader); + // Lose the context, replacing it with a StrictWebGraphicsContext3DWithIOSurface, + // that will warn if any resource from the previous context gets used. + m_hostImpl->initializeLayerRenderer(CCGraphicsContext::create3D(StrictWebGraphicsContext3DWithIOSurface::createGraphicsContext()), UnthrottledUploader); + EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); + m_hostImpl->drawLayers(frame); + m_hostImpl->didDrawAllLayers(frame); + m_hostImpl->swapBuffers(); +} + +// Fake WebGraphicsContext3D that tracks the number of textures in use. +class TrackingWebGraphicsContext3D : public FakeWebGraphicsContext3D { +public: + TrackingWebGraphicsContext3D() + : m_nextTextureId(1) + , m_numTextures(0) + { } + + virtual WebGLId createTexture() OVERRIDE + { + WebGLId id = m_nextTextureId; + ++m_nextTextureId; + + m_textures.set(id, true); + ++m_numTextures; + return id; + } + + virtual void deleteTexture(WebGLId id) OVERRIDE + { + if (!m_textures.get(id)) + return; + + m_textures.set(id, false); + --m_numTextures; + } + + virtual WebString getString(WGC3Denum name) OVERRIDE + { + if (name == WebCore::GraphicsContext3D::EXTENSIONS) + return WebString("GL_CHROMIUM_iosurface GL_ARB_texture_rectangle"); + + return WebString(); + } + + PassRefPtr<GraphicsContext3D> createGraphicsContext() + { + return GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(this), GraphicsContext3D::RenderDirectlyToHostWindow); + } + + unsigned numTextures() const { return m_numTextures; } + +private: + WebGLId m_nextTextureId; + HashMap<WebGLId, bool> m_textures; + unsigned m_numTextures; +}; + +TEST_F(CCLayerTreeHostImplTest, layersFreeTextures) +{ + OwnPtr<CCLayerImpl> rootLayer(CCLayerImpl::create(1)); + rootLayer->setBounds(IntSize(10, 10)); + rootLayer->setAnchorPoint(FloatPoint(0, 0)); + + OwnPtr<CCTiledLayerImpl> tileLayer = CCTiledLayerImpl::create(2); + tileLayer->setBounds(IntSize(10, 10)); + tileLayer->setAnchorPoint(FloatPoint(0, 0)); + tileLayer->setContentBounds(IntSize(10, 10)); + tileLayer->setDrawsContent(true); + tileLayer->setSkipsDraw(false); + OwnPtr<CCLayerTilingData> tilingData(CCLayerTilingData::create(IntSize(10, 10), CCLayerTilingData::NoBorderTexels)); + tilingData->setBounds(IntSize(10, 10)); + tileLayer->setTilingData(*tilingData); + tileLayer->pushTileProperties(0, 0, 1, IntRect(0, 0, 10, 10)); + rootLayer->addChild(tileLayer.release()); + + OwnPtr<CCTextureLayerImpl> textureLayer = CCTextureLayerImpl::create(3); + textureLayer->setBounds(IntSize(10, 10)); + textureLayer->setAnchorPoint(FloatPoint(0, 0)); + textureLayer->setContentBounds(IntSize(10, 10)); + textureLayer->setDrawsContent(true); + textureLayer->setTextureId(1); + rootLayer->addChild(textureLayer.release()); + + FakeVideoFrameProvider provider; + OwnPtr<CCVideoLayerImpl> videoLayer = CCVideoLayerImpl::create(4, &provider); + videoLayer->setBounds(IntSize(10, 10)); + videoLayer->setAnchorPoint(FloatPoint(0, 0)); + videoLayer->setContentBounds(IntSize(10, 10)); + videoLayer->setDrawsContent(true); + videoLayer->setLayerTreeHostImpl(m_hostImpl.get()); + rootLayer->addChild(videoLayer.release()); + + OwnPtr<CCIOSurfaceLayerImpl> ioSurfaceLayer = CCIOSurfaceLayerImpl::create(5); + ioSurfaceLayer->setBounds(IntSize(10, 10)); + ioSurfaceLayer->setAnchorPoint(FloatPoint(0, 0)); + ioSurfaceLayer->setContentBounds(IntSize(10, 10)); + ioSurfaceLayer->setDrawsContent(true); + ioSurfaceLayer->setIOSurfaceProperties(1, IntSize(10, 10)); + ioSurfaceLayer->setLayerTreeHostImpl(m_hostImpl.get()); + rootLayer->addChild(ioSurfaceLayer.release()); + + // Lose the context, replacing it with a TrackingWebGraphicsContext3D, that + // tracks the number of textures allocated. This pointer is owned by its + // GraphicsContext3D. + TrackingWebGraphicsContext3D* trackingWebGraphicsContext = new TrackingWebGraphicsContext3D(); + m_hostImpl->initializeLayerRenderer(CCGraphicsContext::create3D(trackingWebGraphicsContext->createGraphicsContext()), UnthrottledUploader); + + m_hostImpl->setRootLayer(rootLayer.release()); + + CCLayerTreeHostImpl::FrameData frame; EXPECT_TRUE(m_hostImpl->prepareToDraw(frame)); m_hostImpl->drawLayers(frame); m_hostImpl->didDrawAllLayers(frame); m_hostImpl->swapBuffers(); + + EXPECT_GT(trackingWebGraphicsContext->numTextures(), 0u); + + // Kill the layer tree. + m_hostImpl->setRootLayer(CCLayerImpl::create(100)); + // FIXME: Remove this when we don't use ManagedTextures in impl layers. + m_hostImpl->layerRenderer()->implTextureManager()->deleteEvictedTextures(m_hostImpl->layerRenderer()->implTextureAllocator()); + // There should be no textures left in use after. + EXPECT_EQ(0u, trackingWebGraphicsContext->numTextures()); +} + +class MockDrawQuadsToFillScreenContext : public FakeWebGraphicsContext3D { +public: + MOCK_METHOD1(useProgram, void(WebGLId program)); + MOCK_METHOD4(drawElements, void(WGC3Denum mode, WGC3Dsizei count, WGC3Denum type, WGC3Dintptr offset)); +}; + +TEST_F(CCLayerTreeHostImplTest, hasTransparentBackground) +{ + MockDrawQuadsToFillScreenContext* mockContext = new MockDrawQuadsToFillScreenContext(); + RefPtr<CCGraphicsContext> context = CCGraphicsContext::create3D(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(mockContext), GraphicsContext3D::RenderDirectlyToHostWindow)); + + // Run test case + OwnPtr<CCLayerTreeHostImpl> myHostImpl = createLayerTreeHost(false, context, CCLayerImpl::create(1)); + myHostImpl->setBackgroundColor(Color::white); + + // Verify one quad is drawn when transparent background set is not set. + myHostImpl->setHasTransparentBackground(false); + EXPECT_CALL(*mockContext, useProgram(_)) + .Times(1); + EXPECT_CALL(*mockContext, drawElements(_, _, _, _)) + .Times(1); + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + Mock::VerifyAndClearExpectations(&mockContext); + + // Verify no quads are drawn when transparent background is set. + myHostImpl->setHasTransparentBackground(true); + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + Mock::VerifyAndClearExpectations(&mockContext); +} + +TEST_F(CCLayerTreeHostImplTest, surfaceTextureCaching) +{ + CCSettings::setPartialSwapEnabled(true); + + CCLayerTreeSettings settings; + OwnPtr<CCLayerTreeHostImpl> myHostImpl = CCLayerTreeHostImpl::create(settings, this); + + RefPtr<CCGraphicsContext> context = CCGraphicsContext::create3D(GraphicsContext3DPrivate::createGraphicsContextFromWebContext(adoptPtr(new PartialSwapContext()), GraphicsContext3D::RenderDirectlyToHostWindow)); + + myHostImpl->initializeLayerRenderer(context.release(), UnthrottledUploader); + myHostImpl->setViewportSize(IntSize(100, 100)); + + OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1); + CCLayerImpl* rootPtr = root.get(); + + root->setAnchorPoint(FloatPoint(0, 0)); + root->setPosition(FloatPoint(0, 0)); + root->setBounds(IntSize(100, 100)); + root->setContentBounds(IntSize(100, 100)); + root->setVisibleLayerRect(IntRect(0, 0, 100, 100)); + root->setDrawsContent(true); + myHostImpl->setRootLayer(root.release()); + + // Intermediate layer does not own a surface, and does not draw content. + OwnPtr<CCLayerImpl> intermediateLayer = CCLayerImpl::create(2); + CCLayerImpl* intermediateLayerPtr = intermediateLayer.get(); + + intermediateLayerPtr->setAnchorPoint(FloatPoint(0, 0)); + intermediateLayerPtr->setPosition(FloatPoint(10, 10)); + intermediateLayerPtr->setBounds(IntSize(100, 100)); + intermediateLayerPtr->setContentBounds(IntSize(100, 100)); + intermediateLayerPtr->setVisibleLayerRect(IntRect(0, 0, 100, 100)); + intermediateLayerPtr->setDrawsContent(false); // only children draw content + rootPtr->addChild(intermediateLayer.release()); + + OwnPtr<CCLayerImpl> surfaceLayer = CCLayerImpl::create(3); + CCLayerImpl* surfaceLayerPtr = surfaceLayer.get(); + + // Surface layer is the layer that changes its opacity + // It will contain other layers that draw content. + surfaceLayerPtr->setAnchorPoint(FloatPoint(0, 0)); + surfaceLayerPtr->setPosition(FloatPoint(10, 10)); + surfaceLayerPtr->setBounds(IntSize(50, 50)); + surfaceLayerPtr->setContentBounds(IntSize(50, 50)); + surfaceLayerPtr->setVisibleLayerRect(IntRect(0, 0, 50, 50)); + surfaceLayerPtr->setDrawsContent(false); // only children draw content + surfaceLayerPtr->setOpacity(0.5f); // This will cause it to have a surface + intermediateLayerPtr->addChild(surfaceLayer.release()); + + // Child of the surface layer will produce some quads + OwnPtr<FakeLayerWithQuads> child = FakeLayerWithQuads::create(4); + FakeLayerWithQuads* childPtr = child.get(); + + childPtr->setAnchorPoint(FloatPoint(0, 0)); + childPtr->setPosition(FloatPoint(5, 5)); + childPtr->setBounds(IntSize(10, 10)); + childPtr->setContentBounds(IntSize(10, 10)); + childPtr->setVisibleLayerRect(IntRect(0, 0, 10, 10)); + childPtr->setDrawsContent(true); + + surfaceLayerPtr->addChild(child.release()); + + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes, each with one quad + ASSERT_EQ(2U, frame.renderPasses.size()); + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(1U, frame.renderPasses[1]->quadList().size()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get()); + EXPECT_TRUE(quad->renderPass()->targetSurface()->contentsChanged()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Draw without any change + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two EMPTY render passes + ASSERT_EQ(2U, frame.renderPasses.size()); + EXPECT_EQ(0U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(0U, frame.renderPasses[1]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change opacity and draw + surfaceLayerPtr->setOpacity(0.6f); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive one render pass, as the other one should be culled + ASSERT_EQ(1U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[0]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[0]->quadList()[0].get()); + EXPECT_FALSE(quad->renderPass()->targetSurface()->contentsChanged()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change less benign property and draw - should have contents changed flag + surfaceLayerPtr->setStackingOrderChanged(true); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes, each with one quad + ASSERT_EQ(2U, frame.renderPasses.size()); + + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(CCDrawQuad::SolidColor, frame.renderPasses[0]->quadList()[0]->material()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get()); + EXPECT_TRUE(quad->renderPass()->targetSurface()->contentsChanged()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change opacity again, but evict the cached surface texture + surfaceLayerPtr->setOpacity(0.5f); + ManagedTexture* contentsTexture = surfaceLayerPtr->renderSurface()->contentsTexture(); + ASSERT_TRUE(contentsTexture->isValid(contentsTexture->size(), contentsTexture->format())); + CCRenderer* renderer = myHostImpl->layerRenderer(); + TextureManager* textureManager = renderer->implTextureManager(); + size_t maxMemoryLimit = textureManager->maxMemoryLimitBytes(); + + // This should evice all cached surfaces + textureManager->setMaxMemoryLimitBytes(0); + + // Restore original limit + textureManager->setMaxMemoryLimitBytes(maxMemoryLimit); + + // Was our surface evicted? + ASSERT_FALSE(contentsTexture->isValid(contentsTexture->size(), contentsTexture->format())); + + // Change opacity and draw + surfaceLayerPtr->setOpacity(0.6f); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two render passes + ASSERT_EQ(2U, frame.renderPasses.size()); + + // Even though not enough properties changed, the entire thing must be + // redrawn as we don't have cached textures + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(1U, frame.renderPasses[1]->quadList().size()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[1]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[1]->quadList()[0].get()); + EXPECT_FALSE(quad->renderPass()->targetSurface()->contentsChanged()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Draw without any change, to make sure the state is clear + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive two EMPTY render passes + ASSERT_EQ(2U, frame.renderPasses.size()); + EXPECT_EQ(0U, frame.renderPasses[0]->quadList().size()); + EXPECT_EQ(0U, frame.renderPasses[1]->quadList().size()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } + + // Change opacity on the intermediate layer + WebTransformationMatrix transform = intermediateLayerPtr->transform(); + transform.setM11(1.0001); + intermediateLayerPtr->setTransform(transform); + { + CCLayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(myHostImpl->prepareToDraw(frame)); + + // Must receive one render pass, as the other one should be culled. + ASSERT_EQ(1U, frame.renderPasses.size()); + EXPECT_EQ(1U, frame.renderPasses[0]->quadList().size()); + + EXPECT_EQ(CCDrawQuad::RenderPass, frame.renderPasses[0]->quadList()[0]->material()); + CCRenderPassDrawQuad* quad = static_cast<CCRenderPassDrawQuad*>(frame.renderPasses[0]->quadList()[0].get()); + EXPECT_FALSE(quad->renderPass()->targetSurface()->contentsChanged()); + + myHostImpl->drawLayers(frame); + myHostImpl->didDrawAllLayers(frame); + } +} + +struct RenderPassCacheEntry { + mutable OwnPtr<CCRenderPass> renderPassPtr; + CCRenderPass* renderPass; + + RenderPassCacheEntry(PassOwnPtr<CCRenderPass> r) + : renderPassPtr(r), + renderPass(renderPassPtr.get()) + { + } + + RenderPassCacheEntry() + { + } + + RenderPassCacheEntry(const RenderPassCacheEntry& entry) + : renderPassPtr(entry.renderPassPtr.release()), + renderPass(entry.renderPass) + { + } + + RenderPassCacheEntry& operator=(const RenderPassCacheEntry& entry) + { + renderPassPtr = entry.renderPassPtr.release(); + renderPass = entry.renderPass; + return *this; + } +}; + +struct RenderPassRemovalTestData { + CCRenderPassList renderPassList; + std::map<char, RenderPassCacheEntry> renderPassCache; + std::map<const CCRenderPass*, char> renderPassId; + Vector<OwnPtr<CCRenderSurface> > renderSurfaceStore; + Vector<OwnPtr<CCLayerImpl> > layerStore; + OwnPtr<CCSharedQuadState> sharedQuadState; +}; + +class FakeRenderSurface : public CCRenderSurface { +private: + bool m_hasCachedTexture; + bool m_contentsChanged; + +public: + FakeRenderSurface(CCLayerImpl* layerImpl) + : CCRenderSurface(layerImpl), + m_hasCachedTexture(false) + { + } + + virtual bool hasCachedContentsTexture() const OVERRIDE + { + return m_hasCachedTexture; + } + + virtual bool prepareContentsTexture(LayerRendererChromium* lrc) OVERRIDE + { + return true; + } + + virtual bool contentsChanged() const OVERRIDE + { + return m_contentsChanged; + } + + void setHasCachedTexture(bool hasCachedTexture) + { + m_hasCachedTexture = hasCachedTexture; + } + + void setContentsChanged(bool contentsChanged) + { + m_contentsChanged = contentsChanged; + } +}; + +class CCTestRenderPass: public CCRenderPass { +public: + static PassOwnPtr<CCRenderPass> create(CCRenderSurface* targetSurface) + { + return adoptPtr(new CCTestRenderPass(targetSurface)); + } + +protected: + CCTestRenderPass(CCRenderSurface* surface) + : CCRenderPass(surface) + { + } + +public: + void appendQuad(PassOwnPtr<CCDrawQuad> quad) + { + m_quadList.append(quad); + } +}; + +static PassOwnPtr<CCRenderPass> createDummyRenderPass(RenderPassRemovalTestData& testData) +{ + OwnPtr<CCLayerImpl> layerImpl = CCLayerImpl::create(1); + CCRenderSurface* renderSurface = new FakeRenderSurface(layerImpl.get()); + OwnPtr<CCRenderPass> renderPassPtr = CCTestRenderPass::create(renderSurface); + + testData.renderSurfaceStore.append(adoptPtr(renderSurface)); + testData.layerStore.append(layerImpl.release()); + return renderPassPtr.release(); +} + +static void configureRenderPassTestData(const char* testScript, RenderPassRemovalTestData& testData) +{ + // One shared state for all quads - we don't need the correct details + testData.sharedQuadState = CCSharedQuadState::create(WebTransformationMatrix(), WebTransformationMatrix(), IntRect(), IntRect(), 1.0, true); + + const char* currentChar = testScript; + + // Pre-create root pass + OwnPtr<CCRenderPass> rootRenderPass = createDummyRenderPass(testData); + testData.renderPassId.insert(std::pair<CCRenderPass*, char>(rootRenderPass.get(), testScript[0])); + testData.renderPassCache.insert(std::pair<char, RenderPassCacheEntry>(testScript[0], RenderPassCacheEntry(rootRenderPass.release()))); + while (*currentChar != '\0') { + char renderPassId = currentChar[0]; + currentChar++; + + OwnPtr<CCRenderPass> renderPass; + + bool isReplica = false; + if (!testData.renderPassCache[renderPassId].renderPassPtr.get()) + isReplica = true; + + renderPass = testData.renderPassCache[renderPassId].renderPassPtr.release(); + + // Cycle through quad data and create all quads + while (*currentChar != '\n' && *currentChar != '\0') { + if (*currentChar == 's') { + // Solid color draw quad + OwnPtr<CCDrawQuad> quad = CCSolidColorDrawQuad::create(testData.sharedQuadState.get(), IntRect(0, 0, 10, 10), Color::white); + + static_cast<CCTestRenderPass*>(renderPass.get())->appendQuad(quad.release()); + currentChar++; + } else if ((*currentChar >= 'A') && (*currentChar <= 'Z')) { + // RenderPass draw quad + char newRenderPassId = *currentChar; + currentChar++; + bool hasTexture = false; + bool contentsChanged = true; + + if (*currentChar == '[') { + currentChar++; + while ((*currentChar != ']') && (*currentChar != '\0')) { + switch (*currentChar) { + case 'c': + contentsChanged = false; + break; + case 't': + hasTexture = true; + break; + } + currentChar++; + } + if (*currentChar == ']') + currentChar++; + } + + CCRenderPass* refRenderPassPtr; + + if (testData.renderPassCache.find(newRenderPassId) == testData.renderPassCache.end()) { + OwnPtr<CCRenderPass> refRenderPass = createDummyRenderPass(testData); + refRenderPassPtr = refRenderPass.get(); + FakeRenderSurface* refRenderSurface = static_cast<FakeRenderSurface*>(refRenderPass->targetSurface()); + refRenderSurface->setHasCachedTexture(hasTexture); + refRenderSurface->setContentsChanged(contentsChanged); + testData.renderPassId.insert(std::pair<CCRenderPass*, char>(refRenderPass.get(), newRenderPassId)); + testData.renderPassCache.insert(std::pair<char, RenderPassCacheEntry>(newRenderPassId, RenderPassCacheEntry(refRenderPass.release()))); + } else + refRenderPassPtr = testData.renderPassCache[newRenderPassId].renderPass; + + OwnPtr<CCRenderPassDrawQuad> quad = CCRenderPassDrawQuad::create(testData.sharedQuadState.get(), IntRect(), refRenderPassPtr, isReplica, WebKit::WebFilterOperations(), WebKit::WebFilterOperations(), 1); + static_cast<CCTestRenderPass*>(renderPass.get())->appendQuad(quad.release()); + } + } + testData.renderPassList.insert(0, renderPass.release()); + if (*currentChar != '\0') + currentChar++; + } +} + +void dumpRenderPassTestData(const RenderPassRemovalTestData& testData, char* buffer) +{ + char* pos = buffer; + CCRenderPassList::const_reverse_iterator it = testData.renderPassList.rbegin(); + while (it != testData.renderPassList.rend()) { + CCRenderPass* currentPass = it->get(); + char passId = testData.renderPassId.find(currentPass)->second; + *pos = passId; + pos++; + + CCQuadList::const_iterator quadListIterator = currentPass->quadList().begin(); + while (quadListIterator != currentPass->quadList().end()) { + CCDrawQuad* currentQuad = (*quadListIterator).get(); + switch (currentQuad->material()) { + case CCDrawQuad::SolidColor: + *pos = 's'; + pos++; + break; + case CCDrawQuad::RenderPass: + { + CCRenderPassDrawQuad* renderPassDrawQuad = static_cast<CCRenderPassDrawQuad*>(currentQuad); + const CCRenderPass* refPass = renderPassDrawQuad->renderPass(); + char refPassId = testData.renderPassId.find(refPass)->second; + *pos = refPassId; + pos++; + } + break; + default: + *pos = 'x'; + pos++; + break; + } + + quadListIterator++; + } + *pos = '\n'; + pos++; + it++; + } + *pos = '\0'; +} + +// Each CCRenderPassList is represented by a string which describes the configuration. +// The syntax of the string is as follows: +// +// RsssssX[c]ssYsssZ[t]ssW[ct] +// Identifies the render pass---------------------------^ ^^^ ^ ^ ^ ^ ^ +// These are solid color quads-----------------------------+ | | | | | +// Identifies RenderPassDrawQuad's RenderPass-----------------+ | | | | +// This quad's contents didn't change---------------------------+ | | | +// This quad's contents changed and it has no texture---------------+ | | +// This quad has texture but its contents changed-------------------------+ | +// This quad's contents didn't change and it has texture - will be removed------+ +// +// Expected results have exactly the same syntax, except they do not use square brackets, +// since we only check the structure, not attributes. +// +// Test case configuration consists of initialization script and expected results, +// all in the same format. +struct TestCase { + const char* name; + const char* initScript; + const char* expectedResult; +}; + +TestCase removeRenderPassesCases[] = + { + { + "Single root pass", + "Rssss\n", + "Rssss\n" + }, { + "Single pass - no quads", + "R\n", + "R\n" + }, { + "Two passes, no removal", + "RssssAsss\n" + "Assss\n", + "RssssAsss\n" + "Assss\n" + }, { + "Two passes, remove last", + "RssssA[ct]sss\n" + "Assss\n", + "RssssAsss\n" + }, { + "Have texture but contents changed - leave pass", + "RssssA[t]sss\n" + "Assss\n", + "RssssAsss\n" + "Assss\n" + }, { + "Contents didn't change but no texture - leave pass", + "RssssA[c]sss\n" + "Assss\n", + "RssssAsss\n" + "Assss\n" + }, { + "Replica: two quads reference the same pass; remove", + "RssssA[ct]A[ct]sss\n" + "Assss\n", + "RssssAAsss\n" + }, { + "Replica: two quads reference the same pass; leave", + "RssssA[c]A[c]sss\n" + "Assss\n", + "RssssAAsss\n" + "Assss\n", + }, { + "Many passes, remove all", + "RssssA[ct]sss\n" + "AsssB[ct]C[ct]s\n" + "BsssD[ct]ssE[ct]F[ct]\n" + "Essssss\n" + "CG[ct]\n" + "Dsssssss\n" + "Fsssssss\n" + "Gsss\n", + + "RssssAsss\n" + }, { + "Deep recursion, remove all", + + "RsssssA[ct]ssss\n" + "AssssBsss\n" + "BC\n" + "CD\n" + "DE\n" + "EF\n" + "FG\n" + "GH\n" + "HsssIsss\n" + "IJ\n" + "Jssss\n", + + "RsssssAssss\n" + }, { + "Wide recursion, remove all", + "RA[ct]B[ct]C[ct]D[ct]E[ct]F[ct]G[ct]H[ct]I[ct]J[ct]\n" + "As\n" + "Bs\n" + "Cssss\n" + "Dssss\n" + "Es\n" + "F\n" + "Gs\n" + "Hs\n" + "Is\n" + "Jssss\n", + + "RABCDEFGHIJ\n" + }, { + "Remove passes regardless of cache state", + "RssssA[ct]sss\n" + "AsssBCs\n" + "BsssD[c]ssE[t]F\n" + "Essssss\n" + "CG\n" + "Dsssssss\n" + "Fsssssss\n" + "Gsss\n", + + "RssssAsss\n" + }, { + "Leave some passes, remove others", + + "RssssA[c]sss\n" + "AsssB[t]C[ct]s\n" + "BsssD[c]ss\n" + "CG\n" + "Dsssssss\n" + "Gsss\n", + + "RssssAsss\n" + "AsssBCs\n" + "BsssDss\n" + "Dsssssss\n" + }, { + 0, 0, 0 + } + }; + +static void verifyRenderPassTestData(TestCase& testCase, RenderPassRemovalTestData& testData) +{ + char actualResult[1024]; + dumpRenderPassTestData(testData, actualResult); + EXPECT_STREQ(testCase.expectedResult, actualResult) << "In test case: " << testCase.name; +} + +TEST(RenderPassRemovalTest, testRemoveRenderPasses) +{ + int testCaseIndex = 0; + while (removeRenderPassesCases[testCaseIndex].name) { + DebugScopedSetImplThread implThread; + RenderPassRemovalTestData testData; + CCRenderPassList skippedPasses; + configureRenderPassTestData(removeRenderPassesCases[testCaseIndex].initScript, testData); + CCLayerTreeHostImpl::removePassesWithCachedTextures(testData.renderPassList, skippedPasses); + verifyRenderPassTestData(removeRenderPassesCases[testCaseIndex], testData); + testCaseIndex++; + } } } // namespace |
