blob: 479138aafd1cc204db27e83ed44d49d7faf685bc [file] [log] [blame]
// Copyright 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/layer_tree_host.h"
#include "base/synchronization/lock.h"
#include "cc/content_layer.h"
#include "cc/content_layer_client.h"
#include "cc/graphics_context.h"
#include "cc/layer_tree_host_impl.h"
#include "cc/single_thread_proxy.h"
#include "cc/test/fake_content_layer_client.h"
#include "cc/test/fake_web_compositor_output_surface.h"
#include "cc/test/geometry_test_utils.h"
#include "cc/test/layer_tree_test_common.h"
#include "cc/test/occlusion_tracker_test_common.h"
#include "cc/resource_update_queue.h"
#include "cc/timing_function.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/khronos/GLES2/gl2.h"
#include "third_party/khronos/GLES2/gl2ext.h"
#include "ui/gfx/point_conversions.h"
#include "ui/gfx/size_conversions.h"
#include "ui/gfx/vector2d_conversions.h"
#include <public/WebLayerScrollClient.h>
#include <public/WebSize.h>
using namespace WebKit;
using namespace WebKitTests;
namespace cc {
namespace {
class LayerTreeHostTest : public ThreadedTest { };
// Shortlived layerTreeHosts shouldn't die.
class LayerTreeHostTestShortlived1 : public LayerTreeHostTest {
public:
LayerTreeHostTestShortlived1() { }
virtual void beginTest() OVERRIDE
{
// Kill the layerTreeHost immediately.
m_layerTreeHost->setRootLayer(0);
m_layerTreeHost.reset();
endTest();
}
virtual void afterTest() OVERRIDE
{
}
};
// Shortlived layerTreeHosts shouldn't die with a commit in flight.
class LayerTreeHostTestShortlived2 : public LayerTreeHostTest {
public:
LayerTreeHostTestShortlived2() { }
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
// Kill the layerTreeHost immediately.
m_layerTreeHost->setRootLayer(0);
m_layerTreeHost.reset();
endTest();
}
virtual void afterTest() OVERRIDE
{
}
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestShortlived2)
// Shortlived layerTreeHosts shouldn't die with a redraw in flight.
class LayerTreeHostTestShortlived3 : public LayerTreeHostTest {
public:
LayerTreeHostTestShortlived3() { }
virtual void beginTest() OVERRIDE
{
postSetNeedsRedrawToMainThread();
// Kill the layerTreeHost immediately.
m_layerTreeHost->setRootLayer(0);
m_layerTreeHost.reset();
endTest();
}
virtual void afterTest() OVERRIDE
{
}
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestShortlived3)
// Test interleaving of redraws and commits
class LayerTreeHostTestCommitingWithContinuousRedraw : public LayerTreeHostTest {
public:
LayerTreeHostTestCommitingWithContinuousRedraw()
: m_numCompleteCommits(0)
, m_numDraws(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
m_numCompleteCommits++;
if (m_numCompleteCommits == 2)
endTest();
}
virtual void drawLayersOnThread(LayerTreeHostImpl*) OVERRIDE
{
if (m_numDraws == 1)
postSetNeedsCommitToMainThread();
m_numDraws++;
postSetNeedsRedrawToMainThread();
}
virtual void afterTest() OVERRIDE
{
}
private:
int m_numCompleteCommits;
int m_numDraws;
};
TEST_F(LayerTreeHostTestCommitingWithContinuousRedraw, runMultiThread)
{
runTest(true);
}
// Two setNeedsCommits in a row should lead to at least 1 commit and at least 1
// draw with frame 0.
class LayerTreeHostTestSetNeedsCommit1 : public LayerTreeHostTest {
public:
LayerTreeHostTestSetNeedsCommit1()
: m_numCommits(0)
, m_numDraws(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
postSetNeedsCommitToMainThread();
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
m_numDraws++;
if (!impl->sourceFrameNumber())
endTest();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
m_numCommits++;
}
virtual void afterTest() OVERRIDE
{
EXPECT_GE(1, m_numCommits);
EXPECT_GE(1, m_numDraws);
}
private:
int m_numCommits;
int m_numDraws;
};
TEST_F(LayerTreeHostTestSetNeedsCommit1, DISABLED_runMultiThread)
{
runTest(true);
}
// A setNeedsCommit should lead to 1 commit. Issuing a second commit after that
// first committed frame draws should lead to another commit.
class LayerTreeHostTestSetNeedsCommit2 : public LayerTreeHostTest {
public:
LayerTreeHostTestSetNeedsCommit2()
: m_numCommits(0)
, m_numDraws(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
if (!impl->sourceFrameNumber())
postSetNeedsCommitToMainThread();
else if (impl->sourceFrameNumber() == 1)
endTest();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
m_numCommits++;
}
virtual void afterTest() OVERRIDE
{
EXPECT_EQ(2, m_numCommits);
EXPECT_GE(2, m_numDraws);
}
private:
int m_numCommits;
int m_numDraws;
};
TEST_F(LayerTreeHostTestSetNeedsCommit2, runMultiThread)
{
runTest(true);
}
// 1 setNeedsRedraw after the first commit has completed should lead to 1
// additional draw.
class LayerTreeHostTestSetNeedsRedraw : public LayerTreeHostTest {
public:
LayerTreeHostTestSetNeedsRedraw()
: m_numCommits(0)
, m_numDraws(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
EXPECT_EQ(0, impl->sourceFrameNumber());
if (!m_numDraws)
postSetNeedsRedrawToMainThread(); // Redraw again to verify that the second redraw doesn't commit.
else
endTest();
m_numDraws++;
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
EXPECT_EQ(0, m_numDraws);
m_numCommits++;
}
virtual void afterTest() OVERRIDE
{
EXPECT_GE(2, m_numDraws);
EXPECT_EQ(1, m_numCommits);
}
private:
int m_numCommits;
int m_numDraws;
};
TEST_F(LayerTreeHostTestSetNeedsRedraw, runMultiThread)
{
runTest(true);
}
// If the layerTreeHost says it can't draw, then we should not try to draw.
class LayerTreeHostTestCanDrawBlocksDrawing : public LayerTreeHostTest {
public:
LayerTreeHostTestCanDrawBlocksDrawing()
: m_numCommits(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
// Only the initial draw should bring us here.
EXPECT_TRUE(impl->canDraw());
EXPECT_EQ(0, impl->sourceFrameNumber());
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
if (m_numCommits >= 1) {
// After the first commit, we should not be able to draw.
EXPECT_FALSE(impl->canDraw());
}
}
virtual void didCommit() OVERRIDE
{
m_numCommits++;
if (m_numCommits == 1) {
// Make the viewport empty so the host says it can't draw.
m_layerTreeHost->setViewportSize(gfx::Size(0, 0), gfx::Size(0, 0));
char pixels[4];
m_layerTreeHost->compositeAndReadback(static_cast<void*>(&pixels), gfx::Rect(0, 0, 1, 1));
} else if (m_numCommits == 2) {
m_layerTreeHost->setNeedsRedraw();
m_layerTreeHost->setNeedsCommit();
} else
endTest();
}
virtual void afterTest() OVERRIDE
{
}
private:
int m_numCommits;
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestCanDrawBlocksDrawing)
// beginLayerWrite should prevent draws from executing until a commit occurs
class LayerTreeHostTestWriteLayersRedraw : public LayerTreeHostTest {
public:
LayerTreeHostTestWriteLayersRedraw()
: m_numCommits(0)
, m_numDraws(0)
{
}
virtual void beginTest() OVERRIDE
{
postAcquireLayerTextures();
postSetNeedsRedrawToMainThread(); // should be inhibited without blocking
postSetNeedsCommitToMainThread();
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
m_numDraws++;
EXPECT_EQ(m_numDraws, m_numCommits);
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
m_numCommits++;
endTest();
}
virtual void afterTest() OVERRIDE
{
EXPECT_EQ(1, m_numCommits);
}
private:
int m_numCommits;
int m_numDraws;
};
TEST_F(LayerTreeHostTestWriteLayersRedraw, runMultiThread)
{
runTest(true);
}
// Verify that when resuming visibility, requesting layer write permission
// will not deadlock the main thread even though there are not yet any
// scheduled redraws. This behavior is critical for reliably surviving tab
// switching. There are no failure conditions to this test, it just passes
// by not timing out.
class LayerTreeHostTestWriteLayersAfterVisible : public LayerTreeHostTest {
public:
LayerTreeHostTestWriteLayersAfterVisible()
: m_numCommits(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
m_numCommits++;
if (m_numCommits == 2)
endTest();
else {
postSetVisibleToMainThread(false);
postSetVisibleToMainThread(true);
postAcquireLayerTextures();
postSetNeedsCommitToMainThread();
}
}
virtual void afterTest() OVERRIDE
{
}
private:
int m_numCommits;
};
TEST_F(LayerTreeHostTestWriteLayersAfterVisible, runMultiThread)
{
runTest(true);
}
// A compositeAndReadback while invisible should force a normal commit without assertion.
class LayerTreeHostTestCompositeAndReadbackWhileInvisible : public LayerTreeHostTest {
public:
LayerTreeHostTestCompositeAndReadbackWhileInvisible()
: m_numCommits(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void didCommitAndDrawFrame() OVERRIDE
{
m_numCommits++;
if (m_numCommits == 1) {
m_layerTreeHost->setVisible(false);
m_layerTreeHost->setNeedsCommit();
m_layerTreeHost->setNeedsCommit();
char pixels[4];
m_layerTreeHost->compositeAndReadback(static_cast<void*>(&pixels), gfx::Rect(0, 0, 1, 1));
} else
endTest();
}
virtual void afterTest() OVERRIDE
{
}
private:
int m_numCommits;
};
TEST_F(LayerTreeHostTestCompositeAndReadbackWhileInvisible, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestAbortFrameWhenInvisible : public LayerTreeHostTest {
public:
LayerTreeHostTestAbortFrameWhenInvisible()
{
}
virtual void beginTest() OVERRIDE
{
// Request a commit (from the main thread), which will trigger the commit flow from the impl side.
m_layerTreeHost->setNeedsCommit();
// Then mark ourselves as not visible before processing any more messages on the main thread.
m_layerTreeHost->setVisible(false);
// If we make it without kicking a frame, we pass!
endTestAfterDelay(1);
}
virtual void layout() OVERRIDE
{
ASSERT_FALSE(true);
endTest();
}
virtual void afterTest() OVERRIDE
{
}
private:
};
TEST_F(LayerTreeHostTestAbortFrameWhenInvisible, runMultiThread)
{
runTest(true);
}
// Makes sure that setNedsAnimate does not cause the commitRequested() state to be set.
class LayerTreeHostTestSetNeedsAnimateShouldNotSetCommitRequested : public LayerTreeHostTest {
public:
LayerTreeHostTestSetNeedsAnimateShouldNotSetCommitRequested()
: m_numCommits(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void animate(base::TimeTicks monotonicTime) OVERRIDE
{
// We skip the first commit becasue its the commit that populates the
// impl thread with a tree.
if (!m_numCommits)
return;
m_layerTreeHost->setNeedsAnimate();
// Right now, commitRequested is going to be true, because during
// beginFrame, we force commitRequested to true to prevent requests from
// hitting the impl thread. But, when the next didCommit happens, we should
// verify that commitRequested has gone back to false.
}
virtual void didCommit() OVERRIDE
{
if (!m_numCommits) {
EXPECT_FALSE(m_layerTreeHost->commitRequested());
m_layerTreeHost->setNeedsAnimate();
EXPECT_FALSE(m_layerTreeHost->commitRequested());
m_numCommits++;
}
// Verifies that the setNeedsAnimate we made in ::animate did not
// trigger commitRequested.
EXPECT_FALSE(m_layerTreeHost->commitRequested());
endTest();
}
virtual void afterTest() OVERRIDE
{
}
private:
int m_numCommits;
};
TEST_F(LayerTreeHostTestSetNeedsAnimateShouldNotSetCommitRequested, runMultiThread)
{
runTest(true);
}
// Trigger a frame with setNeedsCommit. Then, inside the resulting animate
// callback, requet another frame using setNeedsAnimate. End the test when
// animate gets called yet-again, indicating that the proxy is correctly
// handling the case where setNeedsAnimate() is called inside the begin frame
// flow.
class LayerTreeHostTestSetNeedsAnimateInsideAnimationCallback : public LayerTreeHostTest {
public:
LayerTreeHostTestSetNeedsAnimateInsideAnimationCallback()
: m_numAnimates(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsAnimateToMainThread();
}
virtual void animate(base::TimeTicks) OVERRIDE
{
if (!m_numAnimates) {
m_layerTreeHost->setNeedsAnimate();
m_numAnimates++;
return;
}
endTest();
}
virtual void afterTest() OVERRIDE
{
}
private:
int m_numAnimates;
};
TEST_F(LayerTreeHostTestSetNeedsAnimateInsideAnimationCallback, runMultiThread)
{
runTest(true);
}
// Add a layer animation and confirm that LayerTreeHostImpl::animateLayers does get
// called and continues to get called.
class LayerTreeHostTestAddAnimation : public LayerTreeHostTest {
public:
LayerTreeHostTestAddAnimation()
: m_numAnimates(0)
, m_receivedAnimationStartedNotification(false)
, m_startTime(0)
{
}
virtual void beginTest() OVERRIDE
{
postAddInstantAnimationToMainThread();
}
virtual void animateLayers(LayerTreeHostImpl* layerTreeHostImpl, base::TimeTicks monotonicTime) OVERRIDE
{
if (!m_numAnimates) {
// The animation had zero duration so layerTreeHostImpl should no
// longer need to animate its layers.
EXPECT_FALSE(layerTreeHostImpl->needsAnimateLayers());
m_numAnimates++;
m_firstMonotonicTime = monotonicTime;
return;
}
EXPECT_LT(0, m_startTime);
EXPECT_TRUE(m_receivedAnimationStartedNotification);
endTest();
}
virtual void notifyAnimationStarted(double wallClockTime) OVERRIDE
{
m_receivedAnimationStartedNotification = true;
m_startTime = wallClockTime;
}
virtual void afterTest() OVERRIDE
{
}
private:
int m_numAnimates;
bool m_receivedAnimationStartedNotification;
double m_startTime;
base::TimeTicks m_firstMonotonicTime;
};
TEST_F(LayerTreeHostTestAddAnimation, runMultiThread)
{
runTest(true);
}
// Add a layer animation to a layer, but continually fail to draw. Confirm that after
// a while, we do eventually force a draw.
class LayerTreeHostTestCheckerboardDoesNotStarveDraws : public LayerTreeHostTest {
public:
LayerTreeHostTestCheckerboardDoesNotStarveDraws()
: m_startedAnimating(false)
{
}
virtual void beginTest() OVERRIDE
{
postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
}
virtual void afterTest() OVERRIDE
{
}
virtual void animateLayers(LayerTreeHostImpl* layerTreeHostImpl, base::TimeTicks monotonicTime) OVERRIDE
{
m_startedAnimating = true;
}
virtual void drawLayersOnThread(LayerTreeHostImpl*) OVERRIDE
{
if (m_startedAnimating)
endTest();
}
virtual bool prepareToDrawOnThread(LayerTreeHostImpl*) OVERRIDE
{
return false;
}
private:
bool m_startedAnimating;
};
// Starvation can only be an issue with the MT compositor.
TEST_F(LayerTreeHostTestCheckerboardDoesNotStarveDraws, runMultiThread)
{
runTest(true);
}
// Ensures that animations continue to be ticked when we are backgrounded.
class LayerTreeHostTestTickAnimationWhileBackgrounded : public LayerTreeHostTest {
public:
LayerTreeHostTestTickAnimationWhileBackgrounded()
: m_numAnimates(0)
{
}
virtual void beginTest() OVERRIDE
{
postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
}
// Use willAnimateLayers to set visible false before the animation runs and
// causes a commit, so we block the second visible animate in single-thread
// mode.
virtual void willAnimateLayers(LayerTreeHostImpl* layerTreeHostImpl, base::TimeTicks monotonicTime) OVERRIDE
{
if (m_numAnimates < 2) {
if (!m_numAnimates) {
// We have a long animation running. It should continue to tick even if we are not visible.
postSetVisibleToMainThread(false);
}
m_numAnimates++;
return;
}
endTest();
}
virtual void afterTest() OVERRIDE
{
}
private:
int m_numAnimates;
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestTickAnimationWhileBackgrounded)
// Ensures that animations continue to be ticked when we are backgrounded.
class LayerTreeHostTestAddAnimationWithTimingFunction : public LayerTreeHostTest {
public:
LayerTreeHostTestAddAnimationWithTimingFunction()
{
}
virtual void beginTest() OVERRIDE
{
postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
}
virtual void animateLayers(LayerTreeHostImpl* layerTreeHostImpl, base::TimeTicks monotonicTime) OVERRIDE
{
const ActiveAnimation* animation = m_layerTreeHost->rootLayer()->layerAnimationController()->getActiveAnimation(0, ActiveAnimation::Opacity);
if (!animation)
return;
const FloatAnimationCurve* curve = animation->curve()->toFloatAnimationCurve();
float startOpacity = curve->getValue(0);
float endOpacity = curve->getValue(curve->duration());
float linearlyInterpolatedOpacity = 0.25 * endOpacity + 0.75 * startOpacity;
double time = curve->duration() * 0.25;
// If the linear timing function associated with this animation was not picked up,
// then the linearly interpolated opacity would be different because of the
// default ease timing function.
EXPECT_FLOAT_EQ(linearlyInterpolatedOpacity, curve->getValue(time));
endTest();
}
virtual void afterTest() OVERRIDE
{
}
private:
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestAddAnimationWithTimingFunction)
// Ensures that main thread animations have their start times synchronized with impl thread animations.
class LayerTreeHostTestSynchronizeAnimationStartTimes : public LayerTreeHostTest {
public:
LayerTreeHostTestSynchronizeAnimationStartTimes()
: m_layerTreeHostImpl(0)
{
}
virtual void beginTest() OVERRIDE
{
postAddAnimationToMainThread(m_layerTreeHost->rootLayer());
}
// This is guaranteed to be called before CCLayerTreeHostImpl::animateLayers.
virtual void willAnimateLayers(LayerTreeHostImpl* layerTreeHostImpl, base::TimeTicks monotonicTime) OVERRIDE
{
m_layerTreeHostImpl = layerTreeHostImpl;
}
virtual void notifyAnimationStarted(double time) OVERRIDE
{
EXPECT_TRUE(m_layerTreeHostImpl);
LayerAnimationController* controllerImpl = m_layerTreeHostImpl->rootLayer()->layerAnimationController();
LayerAnimationController* controller = m_layerTreeHost->rootLayer()->layerAnimationController();
ActiveAnimation* animationImpl = controllerImpl->getActiveAnimation(0, ActiveAnimation::Opacity);
ActiveAnimation* animation = controller->getActiveAnimation(0, ActiveAnimation::Opacity);
EXPECT_EQ(animationImpl->startTime(), animation->startTime());
endTest();
}
virtual void afterTest() OVERRIDE
{
}
private:
LayerTreeHostImpl* m_layerTreeHostImpl;
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSynchronizeAnimationStartTimes)
// Ensures that main thread animations have their start times synchronized with impl thread animations.
class LayerTreeHostTestAnimationFinishedEvents : public LayerTreeHostTest {
public:
LayerTreeHostTestAnimationFinishedEvents()
{
}
virtual void beginTest() OVERRIDE
{
postAddInstantAnimationToMainThread();
}
virtual void notifyAnimationFinished(double time) OVERRIDE
{
endTest();
}
virtual void afterTest() OVERRIDE
{
}
private:
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestAnimationFinishedEvents)
class LayerTreeHostTestScrollSimple : public LayerTreeHostTest {
public:
LayerTreeHostTestScrollSimple()
: m_initialScroll(10, 20)
, m_secondScroll(40, 5)
, m_scrollAmount(2, -1)
, m_scrolls(0)
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->rootLayer()->setScrollable(true);
m_layerTreeHost->rootLayer()->setScrollOffset(m_initialScroll);
postSetNeedsCommitToMainThread();
}
virtual void layout() OVERRIDE
{
Layer* root = m_layerTreeHost->rootLayer();
if (!m_layerTreeHost->commitNumber())
EXPECT_VECTOR_EQ(root->scrollOffset(), m_initialScroll);
else {
EXPECT_VECTOR_EQ(root->scrollOffset(), m_initialScroll + m_scrollAmount);
// Pretend like Javascript updated the scroll position itself.
root->setScrollOffset(m_secondScroll);
}
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
LayerImpl* root = impl->rootLayer();
EXPECT_VECTOR_EQ(root->scrollDelta(), gfx::Vector2d());
root->setScrollable(true);
root->setMaxScrollOffset(gfx::Vector2d(100, 100));
root->scrollBy(m_scrollAmount);
if (!impl->sourceFrameNumber()) {
EXPECT_VECTOR_EQ(root->scrollOffset(), m_initialScroll);
EXPECT_VECTOR_EQ(root->scrollDelta(), m_scrollAmount);
postSetNeedsCommitToMainThread();
} else if (impl->sourceFrameNumber() == 1) {
EXPECT_VECTOR_EQ(root->scrollOffset(), m_secondScroll);
EXPECT_VECTOR_EQ(root->scrollDelta(), m_scrollAmount);
endTest();
}
}
virtual void applyScrollAndScale(gfx::Vector2d scrollDelta, float scale) OVERRIDE
{
gfx::Vector2d offset = m_layerTreeHost->rootLayer()->scrollOffset();
m_layerTreeHost->rootLayer()->setScrollOffset(offset + scrollDelta);
m_scrolls++;
}
virtual void afterTest() OVERRIDE
{
EXPECT_EQ(1, m_scrolls);
}
private:
gfx::Vector2d m_initialScroll;
gfx::Vector2d m_secondScroll;
gfx::Vector2d m_scrollAmount;
int m_scrolls;
};
TEST_F(LayerTreeHostTestScrollSimple, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestScrollMultipleRedraw : public LayerTreeHostTest {
public:
LayerTreeHostTestScrollMultipleRedraw()
: m_initialScroll(40, 10)
, m_scrollAmount(-3, 17)
, m_scrolls(0)
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->rootLayer()->setScrollable(true);
m_layerTreeHost->rootLayer()->setScrollOffset(m_initialScroll);
postSetNeedsCommitToMainThread();
}
virtual void beginCommitOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
Layer* root = m_layerTreeHost->rootLayer();
if (!m_layerTreeHost->commitNumber())
EXPECT_VECTOR_EQ(root->scrollOffset(), m_initialScroll);
else if (m_layerTreeHost->commitNumber() == 1)
EXPECT_VECTOR_EQ(root->scrollOffset(), m_initialScroll + m_scrollAmount + m_scrollAmount);
else if (m_layerTreeHost->commitNumber() == 2)
EXPECT_VECTOR_EQ(root->scrollOffset(), m_initialScroll + m_scrollAmount + m_scrollAmount);
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
LayerImpl* root = impl->rootLayer();
root->setScrollable(true);
root->setMaxScrollOffset(gfx::Vector2d(100, 100));
if (!impl->sourceFrameNumber() && impl->sourceAnimationFrameNumber() == 1) {
// First draw after first commit.
EXPECT_VECTOR_EQ(root->scrollDelta(), gfx::Vector2d());
root->scrollBy(m_scrollAmount);
EXPECT_VECTOR_EQ(root->scrollDelta(), m_scrollAmount);
EXPECT_VECTOR_EQ(root->scrollOffset(), m_initialScroll);
postSetNeedsRedrawToMainThread();
} else if (!impl->sourceFrameNumber() && impl->sourceAnimationFrameNumber() == 2) {
// Second draw after first commit.
EXPECT_EQ(root->scrollDelta(), m_scrollAmount);
root->scrollBy(m_scrollAmount);
EXPECT_VECTOR_EQ(root->scrollDelta(), m_scrollAmount + m_scrollAmount);
EXPECT_VECTOR_EQ(root->scrollOffset(), m_initialScroll);
postSetNeedsCommitToMainThread();
} else if (impl->sourceFrameNumber() == 1) {
// Third or later draw after second commit.
EXPECT_GE(impl->sourceAnimationFrameNumber(), 3);
EXPECT_VECTOR_EQ(root->scrollDelta(), gfx::Vector2d());
EXPECT_VECTOR_EQ(root->scrollOffset(), m_initialScroll + m_scrollAmount + m_scrollAmount);
endTest();
}
}
virtual void applyScrollAndScale(gfx::Vector2d scrollDelta, float scale) OVERRIDE
{
gfx::Vector2d offset = m_layerTreeHost->rootLayer()->scrollOffset();
m_layerTreeHost->rootLayer()->setScrollOffset(offset + scrollDelta);
m_scrolls++;
}
virtual void afterTest() OVERRIDE
{
EXPECT_EQ(1, m_scrolls);
}
private:
gfx::Vector2d m_initialScroll;
gfx::Vector2d m_scrollAmount;
int m_scrolls;
};
TEST_F(LayerTreeHostTestScrollMultipleRedraw, runMultiThread)
{
runTest(true);
}
// This test verifies that properties on the layer tree host are commited to the impl side.
class LayerTreeHostTestCommit : public LayerTreeHostTest {
public:
LayerTreeHostTestCommit() { }
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setViewportSize(gfx::Size(20, 20), gfx::Size(20, 20));
m_layerTreeHost->setBackgroundColor(SK_ColorGRAY);
m_layerTreeHost->setPageScaleFactorAndLimits(5, 5, 5);
postSetNeedsCommitToMainThread();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
EXPECT_EQ(gfx::Size(20, 20), impl->layoutViewportSize());
EXPECT_EQ(SK_ColorGRAY, impl->backgroundColor());
EXPECT_EQ(5, impl->pageScaleFactor());
endTest();
}
virtual void afterTest() OVERRIDE { }
};
TEST_F(LayerTreeHostTestCommit, runTest)
{
runTest(true);
}
// Verifies that startPageScaleAnimation events propagate correctly from LayerTreeHost to
// LayerTreeHostImpl in the MT compositor.
class LayerTreeHostTestStartPageScaleAnimation : public LayerTreeHostTest {
public:
LayerTreeHostTestStartPageScaleAnimation()
: m_animationRequested(false)
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->rootLayer()->setScrollable(true);
m_layerTreeHost->rootLayer()->setScrollOffset(gfx::Vector2d());
postSetNeedsCommitToMainThread();
postSetNeedsRedrawToMainThread();
}
void requestStartPageScaleAnimation()
{
layerTreeHost()->startPageScaleAnimation(gfx::Vector2d(), false, 1.25, base::TimeDelta());
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
impl->rootLayer()->setScrollable(true);
impl->rootLayer()->setScrollOffset(gfx::Vector2d());
impl->setPageScaleFactorAndLimits(impl->pageScaleFactor(), 0.5, 2);
// We request animation only once.
if (!m_animationRequested) {
m_mainThreadProxy->postTask(FROM_HERE, base::Bind(&LayerTreeHostTestStartPageScaleAnimation::requestStartPageScaleAnimation, base::Unretained(this)));
m_animationRequested = true;
}
}
virtual void applyScrollAndScale(gfx::Vector2d scrollDelta, float scale) OVERRIDE
{
gfx::Vector2d offset = m_layerTreeHost->rootLayer()->scrollOffset();
m_layerTreeHost->rootLayer()->setScrollOffset(offset + scrollDelta);
m_layerTreeHost->setPageScaleFactorAndLimits(scale, 0.5, 2);
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
impl->processScrollDeltas();
// We get one commit before the first draw, and the animation doesn't happen until the second draw.
if (impl->sourceFrameNumber() == 1) {
EXPECT_EQ(1.25, impl->pageScaleFactor());
endTest();
} else
postSetNeedsRedrawToMainThread();
}
virtual void afterTest() OVERRIDE
{
}
private:
bool m_animationRequested;
};
TEST_F(LayerTreeHostTestStartPageScaleAnimation, runTest)
{
runTest(true);
}
class LayerTreeHostTestSetVisible : public LayerTreeHostTest {
public:
LayerTreeHostTestSetVisible()
: m_numDraws(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
postSetVisibleToMainThread(false);
postSetNeedsRedrawToMainThread(); // This is suppressed while we're invisible.
postSetVisibleToMainThread(true); // Triggers the redraw.
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
EXPECT_TRUE(impl->visible());
++m_numDraws;
endTest();
}
virtual void afterTest() OVERRIDE
{
EXPECT_EQ(1, m_numDraws);
}
private:
int m_numDraws;
};
TEST_F(LayerTreeHostTestSetVisible, runMultiThread)
{
runTest(true);
}
class TestOpacityChangeLayerDelegate : public ContentLayerClient {
public:
TestOpacityChangeLayerDelegate()
: m_testLayer(0)
{
}
void setTestLayer(Layer* testLayer)
{
m_testLayer = testLayer;
}
virtual void paintContents(SkCanvas*, const gfx::Rect&, gfx::RectF&) OVERRIDE
{
// Set layer opacity to 0.
if (m_testLayer)
m_testLayer->setOpacity(0);
}
private:
Layer* m_testLayer;
};
class ContentLayerWithUpdateTracking : public ContentLayer {
public:
static scoped_refptr<ContentLayerWithUpdateTracking> create(ContentLayerClient* client) { return make_scoped_refptr(new ContentLayerWithUpdateTracking(client)); }
int paintContentsCount() { return m_paintContentsCount; }
void resetPaintContentsCount() { m_paintContentsCount = 0; }
virtual void update(ResourceUpdateQueue& queue, const OcclusionTracker* occlusion, RenderingStats& stats) OVERRIDE
{
ContentLayer::update(queue, occlusion, stats);
m_paintContentsCount++;
}
private:
explicit ContentLayerWithUpdateTracking(ContentLayerClient* client)
: ContentLayer(client)
, m_paintContentsCount(0)
{
setAnchorPoint(gfx::PointF(0, 0));
setBounds(gfx::Size(10, 10));
setIsDrawable(true);
}
virtual ~ContentLayerWithUpdateTracking()
{
}
int m_paintContentsCount;
};
// Layer opacity change during paint should not prevent compositor resources from being updated during commit.
class LayerTreeHostTestOpacityChange : public LayerTreeHostTest {
public:
LayerTreeHostTestOpacityChange()
: m_testOpacityChangeDelegate()
, m_updateCheckLayer(ContentLayerWithUpdateTracking::create(&m_testOpacityChangeDelegate))
{
m_testOpacityChangeDelegate.setTestLayer(m_updateCheckLayer.get());
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setViewportSize(gfx::Size(10, 10), gfx::Size(10, 10));
m_layerTreeHost->rootLayer()->addChild(m_updateCheckLayer);
postSetNeedsCommitToMainThread();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
endTest();
}
virtual void afterTest() OVERRIDE
{
// update() should have been called once.
EXPECT_EQ(1, m_updateCheckLayer->paintContentsCount());
// clear m_updateCheckLayer so LayerTreeHost dies.
m_updateCheckLayer = NULL;
}
private:
TestOpacityChangeLayerDelegate m_testOpacityChangeDelegate;
scoped_refptr<ContentLayerWithUpdateTracking> m_updateCheckLayer;
};
TEST_F(LayerTreeHostTestOpacityChange, runMultiThread)
{
runTest(true);
}
class NoScaleContentLayer : public ContentLayer {
public:
static scoped_refptr<NoScaleContentLayer> create(ContentLayerClient* client) { return make_scoped_refptr(new NoScaleContentLayer(client)); }
virtual gfx::Size contentBounds() const OVERRIDE { return bounds(); }
virtual float contentsScaleX() const OVERRIDE { return 1.0; }
virtual float contentsScaleY() const OVERRIDE { return 1.0; }
private:
explicit NoScaleContentLayer(ContentLayerClient* client)
: ContentLayer(client) { }
virtual ~NoScaleContentLayer() { }
};
// Ensures that when opacity is being animated, this value does not cause the subtree to be skipped.
class LayerTreeHostTestDoNotSkipLayersWithAnimatedOpacity : public LayerTreeHostTest {
public:
LayerTreeHostTestDoNotSkipLayersWithAnimatedOpacity()
: m_updateCheckLayer(ContentLayerWithUpdateTracking::create(&m_client))
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setViewportSize(gfx::Size(10, 10), gfx::Size(10, 10));
m_layerTreeHost->rootLayer()->addChild(m_updateCheckLayer);
m_updateCheckLayer->setOpacity(0);
m_updateCheckLayer->setDrawOpacity(0);
postAddAnimationToMainThread(m_updateCheckLayer.get());
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
endTest();
}
virtual void afterTest() OVERRIDE
{
// update() should have been called once, proving that the layer was not skipped.
EXPECT_EQ(1, m_updateCheckLayer->paintContentsCount());
// clear m_updateCheckLayer so LayerTreeHost dies.
m_updateCheckLayer = NULL;
}
private:
FakeContentLayerClient m_client;
scoped_refptr<ContentLayerWithUpdateTracking> m_updateCheckLayer;
};
TEST_F(LayerTreeHostTestDoNotSkipLayersWithAnimatedOpacity, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers : public LayerTreeHostTest {
public:
LayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers()
: m_rootLayer(NoScaleContentLayer::create(&m_client))
, m_childLayer(ContentLayer::create(&m_client))
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setViewportSize(gfx::Size(40, 40), gfx::Size(60, 60));
m_layerTreeHost->setDeviceScaleFactor(1.5);
EXPECT_EQ(gfx::Size(40, 40), m_layerTreeHost->layoutViewportSize());
EXPECT_EQ(gfx::Size(60, 60), m_layerTreeHost->deviceViewportSize());
m_rootLayer->addChild(m_childLayer);
m_rootLayer->setIsDrawable(true);
m_rootLayer->setBounds(gfx::Size(30, 30));
m_rootLayer->setAnchorPoint(gfx::PointF(0, 0));
m_childLayer->setIsDrawable(true);
m_childLayer->setPosition(gfx::Point(2, 2));
m_childLayer->setBounds(gfx::Size(10, 10));
m_childLayer->setAnchorPoint(gfx::PointF(0, 0));
m_layerTreeHost->setRootLayer(m_rootLayer);
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
// Get access to protected methods.
MockLayerTreeHostImpl* mockImpl = static_cast<MockLayerTreeHostImpl*>(impl);
// Should only do one commit.
EXPECT_EQ(0, impl->sourceFrameNumber());
// Device scale factor should come over to impl.
EXPECT_NEAR(impl->deviceScaleFactor(), 1.5, 0.00001);
// Both layers are on impl.
ASSERT_EQ(1u, impl->rootLayer()->children().size());
// Device viewport is scaled.
EXPECT_EQ(gfx::Size(40, 40), impl->layoutViewportSize());
EXPECT_EQ(gfx::Size(60, 60), impl->deviceViewportSize());
LayerImpl* root = impl->rootLayer();
LayerImpl* child = impl->rootLayer()->children()[0];
// Positions remain in layout pixels.
EXPECT_EQ(gfx::Point(0, 0), root->position());
EXPECT_EQ(gfx::Point(2, 2), child->position());
// Compute all the layer transforms for the frame.
MockLayerTreeHostImpl::LayerList renderSurfaceLayerList;
mockImpl->calculateRenderSurfaceLayerList(renderSurfaceLayerList);
// Both layers should be drawing into the root render surface.
ASSERT_EQ(1u, renderSurfaceLayerList.size());
ASSERT_EQ(root->renderSurface(), renderSurfaceLayerList[0]->renderSurface());
ASSERT_EQ(2u, root->renderSurface()->layerList().size());
// The root render surface is the size of the viewport.
EXPECT_RECT_EQ(gfx::Rect(0, 0, 60, 60), root->renderSurface()->contentRect());
// The content bounds of the child should be scaled.
gfx::Size childBoundsScaled = gfx::ToCeiledSize(gfx::ScaleSize(child->bounds(), 1.5));
EXPECT_EQ(childBoundsScaled, child->contentBounds());
gfx::Transform scaleTransform;
scaleTransform.Scale(impl->deviceScaleFactor(), impl->deviceScaleFactor());
// The root layer is scaled by 2x.
gfx::Transform rootScreenSpaceTransform = scaleTransform;
gfx::Transform rootDrawTransform = scaleTransform;
EXPECT_EQ(rootDrawTransform, root->drawTransform());
EXPECT_EQ(rootScreenSpaceTransform, root->screenSpaceTransform());
// The child is at position 2,2, which is transformed to 3,3 after the scale
gfx::Transform childScreenSpaceTransform;
childScreenSpaceTransform.Translate(3, 3);
gfx::Transform childDrawTransform = childScreenSpaceTransform;
EXPECT_EQ(childDrawTransform, child->drawTransform());
EXPECT_EQ(childScreenSpaceTransform, child->screenSpaceTransform());
endTest();
}
virtual void afterTest() OVERRIDE
{
m_rootLayer = NULL;
m_childLayer = NULL;
}
private:
FakeContentLayerClient m_client;
scoped_refptr<NoScaleContentLayer> m_rootLayer;
scoped_refptr<ContentLayer> m_childLayer;
};
// Test is flaky - http://crbug.com/148490
TEST_F(LayerTreeHostTestDeviceScaleFactorScalesViewportAndLayers, DISABLED_runMultiThread)
{
runTest(true);
}
// Verify atomicity of commits and reuse of textures.
class LayerTreeHostTestAtomicCommit : public LayerTreeHostTest {
public:
LayerTreeHostTestAtomicCommit()
: m_layer(ContentLayerWithUpdateTracking::create(&m_client))
{
// Make sure partial texture updates are turned off.
m_settings.maxPartialTextureUpdates = 0;
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setRootLayer(m_layer);
m_layerTreeHost->setViewportSize(gfx::Size(10, 10), gfx::Size(10, 10));
postSetNeedsCommitToMainThread();
postSetNeedsRedrawToMainThread();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
CompositorFakeWebGraphicsContext3DWithTextureTracking* context = static_cast<CompositorFakeWebGraphicsContext3DWithTextureTracking*>(impl->context()->context3D());
switch (impl->sourceFrameNumber()) {
case 0:
// Number of textures should be one.
ASSERT_EQ(1, context->numTextures());
// Number of textures used for commit should be one.
EXPECT_EQ(1, context->numUsedTextures());
// Verify that used texture is correct.
EXPECT_TRUE(context->usedTexture(context->texture(0)));
context->resetUsedTextures();
break;
case 1:
// Number of textures should be two as the first texture
// is used by impl thread and cannot by used for update.
ASSERT_EQ(2, context->numTextures());
// Number of textures used for commit should still be one.
EXPECT_EQ(1, context->numUsedTextures());
// First texture should not have been used.
EXPECT_FALSE(context->usedTexture(context->texture(0)));
// New texture should have been used.
EXPECT_TRUE(context->usedTexture(context->texture(1)));
context->resetUsedTextures();
break;
default:
NOTREACHED();
break;
}
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
CompositorFakeWebGraphicsContext3DWithTextureTracking* context = static_cast<CompositorFakeWebGraphicsContext3DWithTextureTracking*>(impl->context()->context3D());
// Number of textures used for draw should always be one.
EXPECT_EQ(1, context->numUsedTextures());
if (impl->sourceFrameNumber() < 1) {
context->resetUsedTextures();
postSetNeedsAnimateAndCommitToMainThread();
postSetNeedsRedrawToMainThread();
} else
endTest();
}
virtual void layout() OVERRIDE
{
m_layer->setNeedsDisplay();
}
virtual void afterTest() OVERRIDE
{
}
private:
FakeContentLayerClient m_client;
scoped_refptr<ContentLayerWithUpdateTracking> m_layer;
};
TEST_F(LayerTreeHostTestAtomicCommit, runMultiThread)
{
runTest(true);
}
static void setLayerPropertiesForTesting(Layer* layer, Layer* parent, const gfx::Transform& transform, const gfx::PointF& anchor, const gfx::PointF& position, const gfx::Size& bounds, bool opaque)
{
layer->removeAllChildren();
if (parent)
parent->addChild(layer);
layer->setTransform(transform);
layer->setAnchorPoint(anchor);
layer->setPosition(position);
layer->setBounds(bounds);
layer->setContentsOpaque(opaque);
}
class LayerTreeHostTestAtomicCommitWithPartialUpdate : public LayerTreeHostTest {
public:
LayerTreeHostTestAtomicCommitWithPartialUpdate()
: m_parent(ContentLayerWithUpdateTracking::create(&m_client))
, m_child(ContentLayerWithUpdateTracking::create(&m_client))
, m_numCommits(0)
{
// Allow one partial texture update.
m_settings.maxPartialTextureUpdates = 1;
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setRootLayer(m_parent);
m_layerTreeHost->setViewportSize(gfx::Size(10, 20), gfx::Size(10, 20));
gfx::Transform identityMatrix;
setLayerPropertiesForTesting(m_parent.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(10, 20), true);
setLayerPropertiesForTesting(m_child.get(), m_parent.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 10), gfx::Size(10, 10), false);
postSetNeedsCommitToMainThread();
postSetNeedsRedrawToMainThread();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
CompositorFakeWebGraphicsContext3DWithTextureTracking* context = static_cast<CompositorFakeWebGraphicsContext3DWithTextureTracking*>(impl->context()->context3D());
switch (impl->sourceFrameNumber()) {
case 0:
// Number of textures should be two.
ASSERT_EQ(2, context->numTextures());
// Number of textures used for commit should be two.
EXPECT_EQ(2, context->numUsedTextures());
// Verify that used textures are correct.
EXPECT_TRUE(context->usedTexture(context->texture(0)));
EXPECT_TRUE(context->usedTexture(context->texture(1)));
context->resetUsedTextures();
break;
case 1:
// Number of textures used for commit should still be two.
EXPECT_EQ(2, context->numUsedTextures());
// First two textures should not have been used.
EXPECT_FALSE(context->usedTexture(context->texture(0)));
EXPECT_FALSE(context->usedTexture(context->texture(1)));
// New textures should have been used.
EXPECT_TRUE(context->usedTexture(context->texture(2)));
EXPECT_TRUE(context->usedTexture(context->texture(3)));
context->resetUsedTextures();
break;
case 2:
// Number of textures used for commit should still be two.
EXPECT_EQ(2, context->numUsedTextures());
context->resetUsedTextures();
break;
case 3:
// No textures should be used for commit.
EXPECT_EQ(0, context->numUsedTextures());
context->resetUsedTextures();
break;
case 4:
// Number of textures used for commit should be one.
EXPECT_EQ(1, context->numUsedTextures());
context->resetUsedTextures();
break;
default:
NOTREACHED();
break;
}
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
CompositorFakeWebGraphicsContext3DWithTextureTracking* context = static_cast<CompositorFakeWebGraphicsContext3DWithTextureTracking*>(impl->context()->context3D());
// Number of textures used for drawing should two except for frame 4
// where the viewport only contains one layer.
if (impl->sourceFrameNumber() == 3)
EXPECT_EQ(1, context->numUsedTextures());
else
EXPECT_EQ(2, context->numUsedTextures());
if (impl->sourceFrameNumber() < 4) {
context->resetUsedTextures();
postSetNeedsAnimateAndCommitToMainThread();
postSetNeedsRedrawToMainThread();
} else
endTest();
}
virtual void layout() OVERRIDE
{
switch (m_numCommits++) {
case 0:
case 1:
m_parent->setNeedsDisplay();
m_child->setNeedsDisplay();
break;
case 2:
// Damage part of layers.
m_parent->setNeedsDisplayRect(gfx::RectF(0, 0, 5, 5));
m_child->setNeedsDisplayRect(gfx::RectF(0, 0, 5, 5));
break;
case 3:
m_child->setNeedsDisplay();
m_layerTreeHost->setViewportSize(gfx::Size(10, 10), gfx::Size(10, 10));
break;
case 4:
m_layerTreeHost->setViewportSize(gfx::Size(10, 20), gfx::Size(10, 20));
break;
default:
NOTREACHED();
break;
}
}
virtual void afterTest() OVERRIDE
{
}
private:
FakeContentLayerClient m_client;
scoped_refptr<ContentLayerWithUpdateTracking> m_parent;
scoped_refptr<ContentLayerWithUpdateTracking> m_child;
int m_numCommits;
};
TEST_F(LayerTreeHostTestAtomicCommitWithPartialUpdate, runMultiThread)
{
runTest(true);
}
class TestLayer : public Layer {
public:
static scoped_refptr<TestLayer> create() { return make_scoped_refptr(new TestLayer()); }
virtual void update(ResourceUpdateQueue&, const OcclusionTracker* occlusion, RenderingStats&) OVERRIDE
{
// Gain access to internals of the OcclusionTracker.
const TestOcclusionTracker* testOcclusion = static_cast<const TestOcclusionTracker*>(occlusion);
m_occludedScreenSpace = testOcclusion ? testOcclusion->occlusionInScreenSpace() : Region();
}
virtual bool drawsContent() const OVERRIDE { return true; }
const Region& occludedScreenSpace() const { return m_occludedScreenSpace; }
void clearOccludedScreenSpace() { m_occludedScreenSpace.Clear(); }
private:
TestLayer() : Layer() { }
virtual ~TestLayer() { }
Region m_occludedScreenSpace;
};
static void setTestLayerPropertiesForTesting(TestLayer* layer, Layer* parent, const gfx::Transform& transform, const gfx::PointF& anchor, const gfx::PointF& position, const gfx::Size& bounds, bool opaque)
{
setLayerPropertiesForTesting(layer, parent, transform, anchor, position, bounds, opaque);
layer->clearOccludedScreenSpace();
}
class LayerTreeHostTestLayerOcclusion : public LayerTreeHostTest {
public:
LayerTreeHostTestLayerOcclusion() { }
virtual void beginTest() OVERRIDE
{
scoped_refptr<TestLayer> rootLayer = TestLayer::create();
scoped_refptr<TestLayer> child = TestLayer::create();
scoped_refptr<TestLayer> child2 = TestLayer::create();
scoped_refptr<TestLayer> grandChild = TestLayer::create();
scoped_refptr<TestLayer> mask = TestLayer::create();
gfx::Transform identityMatrix;
gfx::Transform childTransform;
childTransform.Translate(250, 250);
childTransform.Rotate(90);
childTransform.Translate(-250, -250);
child->setMasksToBounds(true);
// See LayerTreeHostCommonTest.layerAddsSelfToOccludedRegionWithRotatedSurface for a nice visual of these layers and how they end up
// positioned on the screen.
// The child layer is rotated and the grandChild is opaque, but clipped to the child and rootLayer
setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, gfx::PointF(0, 0), gfx::PointF(30, 30), gfx::Size(500, 500), false);
setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 10), gfx::Size(500, 500), true);
m_layerTreeHost->setRootLayer(rootLayer);
m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds());
ASSERT_TRUE(m_layerTreeHost->initializeRendererIfNeeded());
ResourceUpdateQueue queue;
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
EXPECT_EQ(gfx::Rect().ToString(), grandChild->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 40, 170, 160).ToString(), child->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 40, 170, 160).ToString(), rootLayer->occludedScreenSpace().ToString());
// If the child layer is opaque, then it adds to the occlusion seen by the rootLayer.
setLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
setLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, gfx::PointF(0, 0), gfx::PointF(30, 30), gfx::Size(500, 500), true);
setLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 10), gfx::Size(500, 500), true);
m_layerTreeHost->setRootLayer(rootLayer);
m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds());
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
EXPECT_EQ(gfx::Rect().ToString(), grandChild->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 40, 170, 160).ToString(), child->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 30, 170, 170).ToString(), rootLayer->occludedScreenSpace().ToString());
// Add a second child to the root layer and the regions should merge
setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(70, 20), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, gfx::PointF(0, 0), gfx::PointF(30, 30), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 10), gfx::Size(500, 500), true);
m_layerTreeHost->setRootLayer(rootLayer);
m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds());
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
EXPECT_EQ(gfx::Rect().ToString(), grandChild->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 40, 170, 160).ToString(), child->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 30, 170, 170).ToString(), child2->occludedScreenSpace().ToString());
EXPECT_EQ(UnionRegions(gfx::Rect(30, 30, 170, 170), gfx::Rect(70, 20, 130, 180)).ToString(), rootLayer->occludedScreenSpace().ToString());
// Move the second child to be sure.
setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 70), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, gfx::PointF(0, 0), gfx::PointF(30, 30), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 10), gfx::Size(500, 500), true);
m_layerTreeHost->setRootLayer(rootLayer);
m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds());
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
EXPECT_EQ(gfx::Rect().ToString(), grandChild->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 40, 170, 160).ToString(), child->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 30, 170, 170).ToString(), child2->occludedScreenSpace().ToString());
EXPECT_EQ(UnionRegions(gfx::Rect(10, 70, 190, 130), gfx::Rect(30, 30, 170, 170)).ToString(), rootLayer->occludedScreenSpace().ToString());
// If the child layer has a mask on it, then it shouldn't contribute to occlusion on stuff below it
setLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
setLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 70), gfx::Size(500, 500), true);
setLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, gfx::PointF(0, 0), gfx::PointF(30, 30), gfx::Size(500, 500), true);
setLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 10), gfx::Size(500, 500), true);
child->setMaskLayer(mask.get());
m_layerTreeHost->setRootLayer(rootLayer);
m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds());
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
EXPECT_EQ(gfx::Rect().ToString(), grandChild->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 40, 170, 160).ToString(), child->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect().ToString(), child2->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(10, 70, 190, 130).ToString(), rootLayer->occludedScreenSpace().ToString());
// If the child layer with a mask is below child2, then child2 should contribute to occlusion on everything, and child shouldn't contribute to the rootLayer
setLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
setLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, gfx::PointF(0, 0), gfx::PointF(30, 30), gfx::Size(500, 500), true);
setLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 10), gfx::Size(500, 500), true);
setLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 70), gfx::Size(500, 500), true);
child->setMaskLayer(mask.get());
m_layerTreeHost->setRootLayer(rootLayer);
m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds());
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
EXPECT_EQ(gfx::Rect().ToString(), child2->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(10, 70, 190, 130).ToString(), grandChild->occludedScreenSpace().ToString());
EXPECT_EQ(UnionRegions(gfx::Rect(30, 40, 170, 160), gfx::Rect(10, 70, 190, 130)).ToString(), child->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(10, 70, 190, 130), rootLayer->occludedScreenSpace());
// If the child layer has a non-opaque drawOpacity, then it shouldn't contribute to occlusion on stuff below it
setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 70), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, gfx::PointF(0, 0), gfx::PointF(30, 30), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 10), gfx::Size(500, 500), true);
child->setMaskLayer(0);
child->setOpacity(0.5);
m_layerTreeHost->setRootLayer(rootLayer);
m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds());
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
EXPECT_EQ(gfx::Rect().ToString(), grandChild->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 40, 170, 160).ToString(), child->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect().ToString(), child2->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(10, 70, 190, 130).ToString(), rootLayer->occludedScreenSpace().ToString());
// If the child layer with non-opaque drawOpacity is below child2, then child2 should contribute to occlusion on everything, and child shouldn't contribute to the rootLayer
setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, gfx::PointF(0, 0), gfx::PointF(30, 30), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 10), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 70), gfx::Size(500, 500), true);
child->setMaskLayer(0);
child->setOpacity(0.5);
m_layerTreeHost->setRootLayer(rootLayer);
m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds());
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
EXPECT_EQ(gfx::Rect().ToString(), child2->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(10, 70, 190, 130).ToString(), grandChild->occludedScreenSpace().ToString());
EXPECT_EQ(UnionRegions(gfx::Rect(30, 40, 170, 160), gfx::Rect(10, 70, 190, 130)).ToString(), child->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(10, 70, 190, 130).ToString(), rootLayer->occludedScreenSpace().ToString());
// Kill the layerTreeHost immediately.
m_layerTreeHost->setRootLayer(0);
m_layerTreeHost.reset();
endTest();
}
virtual void afterTest() OVERRIDE
{
}
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestLayerOcclusion)
class LayerTreeHostTestLayerOcclusionWithFilters : public LayerTreeHostTest {
public:
LayerTreeHostTestLayerOcclusionWithFilters() { }
virtual void beginTest() OVERRIDE
{
scoped_refptr<TestLayer> rootLayer = TestLayer::create();
scoped_refptr<TestLayer> child = TestLayer::create();
scoped_refptr<TestLayer> child2 = TestLayer::create();
scoped_refptr<TestLayer> grandChild = TestLayer::create();
scoped_refptr<TestLayer> mask = TestLayer::create();
gfx::Transform identityMatrix;
gfx::Transform childTransform;
childTransform.Translate(250, 250);
childTransform.Rotate(90);
childTransform.Translate(-250, -250);
child->setMasksToBounds(true);
// If the child layer has a filter that changes alpha values, and is below child2, then child2 should contribute to occlusion on everything,
// and child shouldn't contribute to the rootLayer
setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, gfx::PointF(0, 0), gfx::PointF(30, 30), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 10), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 70), gfx::Size(500, 500), true);
{
WebFilterOperations filters;
filters.append(WebFilterOperation::createOpacityFilter(0.5));
child->setFilters(filters);
}
m_layerTreeHost->setRootLayer(rootLayer);
m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds());
ASSERT_TRUE(m_layerTreeHost->initializeRendererIfNeeded());
ResourceUpdateQueue queue;
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
EXPECT_EQ(gfx::Rect().ToString(), child2->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(10, 70, 190, 130).ToString(), grandChild->occludedScreenSpace().ToString());
EXPECT_EQ(UnionRegions(gfx::Rect(30, 40, 170, 30), gfx::Rect(10, 70, 190, 130)).ToString(), child->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(10, 70, 190, 130).ToString(), rootLayer->occludedScreenSpace().ToString());
// If the child layer has a filter that moves pixels/changes alpha, and is below child2, then child should not inherit occlusion from outside its subtree,
// and should not contribute to the rootLayer
setTestLayerPropertiesForTesting(rootLayer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
setTestLayerPropertiesForTesting(child.get(), rootLayer.get(), childTransform, gfx::PointF(0, 0), gfx::PointF(30, 30), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(grandChild.get(), child.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 10), gfx::Size(500, 500), true);
setTestLayerPropertiesForTesting(child2.get(), rootLayer.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(10, 70), gfx::Size(500, 500), true);
{
WebFilterOperations filters;
filters.append(WebFilterOperation::createBlurFilter(10));
child->setFilters(filters);
}
m_layerTreeHost->setRootLayer(rootLayer);
m_layerTreeHost->setViewportSize(rootLayer->bounds(), rootLayer->bounds());
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
EXPECT_EQ(gfx::Rect().ToString(), child2->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect().ToString(), grandChild->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(30, 40, 170, 160).ToString(), child->occludedScreenSpace().ToString());
EXPECT_EQ(gfx::Rect(10, 70, 190, 130).ToString(), rootLayer->occludedScreenSpace().ToString());
// Kill the layerTreeHost immediately.
m_layerTreeHost->setRootLayer(0);
m_layerTreeHost.reset();
LayerTreeHost::setNeedsFilterContext(false);
endTest();
}
virtual void afterTest() OVERRIDE
{
}
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestLayerOcclusionWithFilters)
class LayerTreeHostTestManySurfaces : public LayerTreeHostTest {
public:
LayerTreeHostTestManySurfaces() { }
virtual void beginTest() OVERRIDE
{
// We create enough RenderSurfaces that it will trigger Vector reallocation while computing occlusion.
Region occluded;
const gfx::Transform identityMatrix;
std::vector<scoped_refptr<TestLayer> > layers;
std::vector<scoped_refptr<TestLayer> > children;
int numSurfaces = 20;
scoped_refptr<TestLayer> replica = TestLayer::create();
for (int i = 0; i < numSurfaces; ++i) {
layers.push_back(TestLayer::create());
if (!i) {
setTestLayerPropertiesForTesting(layers.back().get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(200, 200), true);
layers.back()->createRenderSurface();
} else {
setTestLayerPropertiesForTesting(layers.back().get(), layers[layers.size()-2].get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(1, 1), gfx::Size(200-i, 200-i), true);
layers.back()->setMasksToBounds(true);
layers.back()->setReplicaLayer(replica.get()); // Make it have a RenderSurfaceImpl
}
}
for (int i = 1; i < numSurfaces; ++i) {
children.push_back(TestLayer::create());
setTestLayerPropertiesForTesting(children.back().get(), layers[i].get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(500, 500), false);
}
m_layerTreeHost->setRootLayer(layers[0].get());
m_layerTreeHost->setViewportSize(layers[0]->bounds(), layers[0]->bounds());
ASSERT_TRUE(m_layerTreeHost->initializeRendererIfNeeded());
ResourceUpdateQueue queue;
m_layerTreeHost->updateLayers(queue, std::numeric_limits<size_t>::max());
m_layerTreeHost->commitComplete();
for (int i = 0; i < numSurfaces-1; ++i) {
gfx::Rect expectedOcclusion(i+1, i+1, 200-i-1, 200-i-1);
EXPECT_EQ(expectedOcclusion.ToString(), layers[i]->occludedScreenSpace().ToString());
}
// Kill the layerTreeHost immediately.
m_layerTreeHost->setRootLayer(0);
m_layerTreeHost.reset();
endTest();
}
virtual void afterTest() OVERRIDE
{
}
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestManySurfaces)
// A loseContext(1) should lead to a didRecreateOutputSurface(true)
class LayerTreeHostTestSetSingleLostContext : public LayerTreeHostTest {
public:
LayerTreeHostTestSetSingleLostContext()
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void didCommitAndDrawFrame() OVERRIDE
{
m_layerTreeHost->loseContext(1);
}
virtual void didRecreateOutputSurface(bool succeeded) OVERRIDE
{
EXPECT_TRUE(succeeded);
endTest();
}
virtual void afterTest() OVERRIDE
{
}
};
TEST_F(LayerTreeHostTestSetSingleLostContext, runMultiThread)
{
runTest(true);
}
// A loseContext(10) should lead to a didRecreateOutputSurface(false), and
// a finishAllRendering() should not hang.
class LayerTreeHostTestSetRepeatedLostContext : public LayerTreeHostTest {
public:
LayerTreeHostTestSetRepeatedLostContext()
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void didCommitAndDrawFrame() OVERRIDE
{
m_layerTreeHost->loseContext(10);
}
virtual void didRecreateOutputSurface(bool succeeded) OVERRIDE
{
EXPECT_FALSE(succeeded);
m_layerTreeHost->finishAllRendering();
endTest();
}
virtual void afterTest() OVERRIDE
{
}
};
TEST_F(LayerTreeHostTestSetRepeatedLostContext, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestFractionalScroll : public LayerTreeHostTest {
public:
LayerTreeHostTestFractionalScroll()
: m_scrollAmount(1.75, 0)
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->rootLayer()->setScrollable(true);
postSetNeedsCommitToMainThread();
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
LayerImpl* root = impl->rootLayer();
root->setMaxScrollOffset(gfx::Vector2d(100, 100));
// Check that a fractional scroll delta is correctly accumulated over multiple commits.
if (!impl->sourceFrameNumber()) {
EXPECT_VECTOR_EQ(root->scrollOffset(), gfx::Vector2d(0, 0));
EXPECT_VECTOR_EQ(root->scrollDelta(), gfx::Vector2d(0, 0));
postSetNeedsCommitToMainThread();
} else if (impl->sourceFrameNumber() == 1) {
EXPECT_VECTOR_EQ(root->scrollOffset(), gfx::ToFlooredVector2d(m_scrollAmount));
EXPECT_VECTOR_EQ(root->scrollDelta(), gfx::Vector2dF(fmod(m_scrollAmount.x(), 1), 0));
postSetNeedsCommitToMainThread();
} else if (impl->sourceFrameNumber() == 2) {
EXPECT_VECTOR_EQ(root->scrollOffset(), gfx::ToFlooredVector2d(m_scrollAmount + m_scrollAmount));
EXPECT_VECTOR_EQ(root->scrollDelta(), gfx::Vector2dF(fmod(2 * m_scrollAmount.x(), 1), 0));
endTest();
}
root->scrollBy(m_scrollAmount);
}
virtual void applyScrollAndScale(gfx::Vector2d scrollDelta, float scale) OVERRIDE
{
gfx::Vector2d offset = m_layerTreeHost->rootLayer()->scrollOffset();
m_layerTreeHost->rootLayer()->setScrollOffset(offset + scrollDelta);
}
virtual void afterTest() OVERRIDE
{
}
private:
gfx::Vector2dF m_scrollAmount;
};
TEST_F(LayerTreeHostTestFractionalScroll, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestFinishAllRendering : public LayerTreeHostTest {
public:
LayerTreeHostTestFinishAllRendering()
: m_once(false)
, m_drawCount(0)
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setNeedsRedraw();
postSetNeedsCommitToMainThread();
}
virtual void didCommitAndDrawFrame() OVERRIDE
{
if (m_once)
return;
m_once = true;
m_layerTreeHost->setNeedsRedraw();
m_layerTreeHost->acquireLayerTextures();
{
base::AutoLock lock(m_lock);
m_drawCount = 0;
}
m_layerTreeHost->finishAllRendering();
{
base::AutoLock lock(m_lock);
EXPECT_EQ(0, m_drawCount);
}
endTest();
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
base::AutoLock lock(m_lock);
++m_drawCount;
}
virtual void afterTest() OVERRIDE
{
}
private:
bool m_once;
base::Lock m_lock;
int m_drawCount;
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestFinishAllRendering)
// Layers added to tree with existing active animations should have the animation
// correctly recognized.
class LayerTreeHostTestLayerAddedWithAnimation : public LayerTreeHostTest {
public:
LayerTreeHostTestLayerAddedWithAnimation()
: m_addedAnimation(false)
{
}
virtual void beginTest() OVERRIDE
{
EXPECT_FALSE(m_addedAnimation);
scoped_refptr<Layer> layer = Layer::create();
layer->setLayerAnimationDelegate(this);
// Any valid AnimationCurve will do here.
scoped_ptr<AnimationCurve> curve(EaseTimingFunction::create());
scoped_ptr<ActiveAnimation> animation(ActiveAnimation::create(curve.Pass(), 1, 1, ActiveAnimation::Opacity));
layer->layerAnimationController()->addAnimation(animation.Pass());
// We add the animation *before* attaching the layer to the tree.
m_layerTreeHost->rootLayer()->addChild(layer);
EXPECT_TRUE(m_addedAnimation);
endTest();
}
virtual void didAddAnimation() OVERRIDE
{
m_addedAnimation = true;
}
virtual void afterTest() OVERRIDE { }
private:
bool m_addedAnimation;
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestLayerAddedWithAnimation)
class LayerTreeHostTestScrollChildLayer : public LayerTreeHostTest, public WebLayerScrollClient {
public:
LayerTreeHostTestScrollChildLayer(float deviceScaleFactor)
: m_deviceScaleFactor(deviceScaleFactor)
, m_initialScroll(10, 20)
, m_secondScroll(40, 5)
, m_scrollAmount(2, -1)
, m_rootScrolls(0)
{
}
virtual void beginTest() OVERRIDE
{
gfx::Size viewportSize(10, 10);
gfx::Size deviceViewportSize = gfx::ToCeiledSize(gfx::ScaleSize(viewportSize, m_deviceScaleFactor));
m_layerTreeHost->setViewportSize(viewportSize, deviceViewportSize);
m_layerTreeHost->setDeviceScaleFactor(m_deviceScaleFactor);
m_rootScrollLayer = ContentLayer::create(&m_fakeDelegate);
m_rootScrollLayer->setBounds(gfx::Size(110, 110));
m_rootScrollLayer->setPosition(gfx::PointF(0, 0));
m_rootScrollLayer->setAnchorPoint(gfx::PointF(0, 0));
m_rootScrollLayer->setIsDrawable(true);
m_rootScrollLayer->setScrollable(true);
m_rootScrollLayer->setMaxScrollOffset(gfx::Vector2d(100, 100));
m_layerTreeHost->rootLayer()->addChild(m_rootScrollLayer);
m_childLayer = ContentLayer::create(&m_fakeDelegate);
m_childLayer->setLayerScrollClient(this);
m_childLayer->setBounds(gfx::Size(110, 110));
// The scrolls will happen at 5, 5. If they are treated like device pixels, then
// they will be at 2.5, 2.5 in logical pixels, and will miss this layer.
m_childLayer->setPosition(gfx::PointF(5, 5));
m_childLayer->setAnchorPoint(gfx::PointF(0, 0));
m_childLayer->setIsDrawable(true);
m_childLayer->setScrollable(true);
m_childLayer->setMaxScrollOffset(gfx::Vector2d(100, 100));
m_rootScrollLayer->addChild(m_childLayer);
m_childLayer->setScrollOffset(m_initialScroll);
postSetNeedsCommitToMainThread();
}
virtual void didScroll() OVERRIDE
{
m_finalScrollOffset = m_childLayer->scrollOffset();
}
virtual void applyScrollAndScale(gfx::Vector2d scrollDelta, float scale) OVERRIDE
{
gfx::Vector2d offset = m_rootScrollLayer->scrollOffset();
m_rootScrollLayer->setScrollOffset(offset + scrollDelta);
m_rootScrolls++;
}
virtual void layout() OVERRIDE
{
EXPECT_VECTOR_EQ(gfx::Vector2d(), m_rootScrollLayer->scrollOffset());
switch (m_layerTreeHost->commitNumber()) {
case 0:
EXPECT_VECTOR_EQ(m_initialScroll, m_childLayer->scrollOffset());
break;
case 1:
EXPECT_VECTOR_EQ(m_initialScroll + m_scrollAmount, m_childLayer->scrollOffset());
// Pretend like Javascript updated the scroll position itself.
m_childLayer->setScrollOffset(m_secondScroll);
break;
case 2:
EXPECT_VECTOR_EQ(m_secondScroll + m_scrollAmount, m_childLayer->scrollOffset());
break;
}
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
LayerImpl* root = impl->rootLayer();
LayerImpl* rootScrollLayer = root->children()[0];
LayerImpl* childLayer = rootScrollLayer->children()[0];
EXPECT_VECTOR_EQ(root->scrollDelta(), gfx::Vector2d());
EXPECT_VECTOR_EQ(rootScrollLayer->scrollDelta(), gfx::Vector2d());
EXPECT_EQ(rootScrollLayer->bounds().width() * m_deviceScaleFactor, rootScrollLayer->contentBounds().width());
EXPECT_EQ(rootScrollLayer->bounds().height() * m_deviceScaleFactor, rootScrollLayer->contentBounds().height());
EXPECT_EQ(childLayer->bounds().width() * m_deviceScaleFactor, childLayer->contentBounds().width());
EXPECT_EQ(childLayer->bounds().height() * m_deviceScaleFactor, childLayer->contentBounds().height());
switch (impl->sourceFrameNumber()) {
case 0:
// Gesture scroll on impl thread.
EXPECT_EQ(impl->scrollBegin(gfx::Point(5, 5), InputHandlerClient::Gesture), InputHandlerClient::ScrollStarted);
impl->scrollBy(gfx::Point(), m_scrollAmount);
impl->scrollEnd();
EXPECT_VECTOR_EQ(m_initialScroll, childLayer->scrollOffset());
EXPECT_VECTOR_EQ(m_scrollAmount, childLayer->scrollDelta());
break;
case 1:
// Wheel scroll on impl thread.
EXPECT_EQ(impl->scrollBegin(gfx::Point(5, 5), InputHandlerClient::Wheel), InputHandlerClient::ScrollStarted);
impl->scrollBy(gfx::Point(), m_scrollAmount);
impl->scrollEnd();
EXPECT_VECTOR_EQ(m_secondScroll, childLayer->scrollOffset());
EXPECT_VECTOR_EQ(m_scrollAmount, childLayer->scrollDelta());
break;
case 2:
EXPECT_VECTOR_EQ(m_secondScroll + m_scrollAmount, childLayer->scrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2d(0, 0), childLayer->scrollDelta());
endTest();
}
}
virtual void afterTest() OVERRIDE
{
EXPECT_EQ(0, m_rootScrolls);
EXPECT_VECTOR_EQ(m_secondScroll + m_scrollAmount, m_finalScrollOffset);
}
private:
float m_deviceScaleFactor;
gfx::Vector2d m_initialScroll;
gfx::Vector2d m_secondScroll;
gfx::Vector2d m_scrollAmount;
int m_rootScrolls;
gfx::Vector2d m_finalScrollOffset;
FakeContentLayerClient m_fakeDelegate;
scoped_refptr<Layer> m_rootScrollLayer;
scoped_refptr<Layer> m_childLayer;
};
class LayerTreeHostTestScrollChildLayerNormalDpi : public LayerTreeHostTestScrollChildLayer {
public:
LayerTreeHostTestScrollChildLayerNormalDpi() : LayerTreeHostTestScrollChildLayer(1) { }
};
TEST_F(LayerTreeHostTestScrollChildLayerNormalDpi, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestScrollChildLayerHighDpi : public LayerTreeHostTestScrollChildLayer {
public:
LayerTreeHostTestScrollChildLayerHighDpi() : LayerTreeHostTestScrollChildLayer(2) { }
};
TEST_F(LayerTreeHostTestScrollChildLayerHighDpi, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestScrollRootScrollLayer : public LayerTreeHostTest {
public:
LayerTreeHostTestScrollRootScrollLayer(float deviceScaleFactor)
: m_deviceScaleFactor(deviceScaleFactor)
, m_initialScroll(10, 20)
, m_secondScroll(40, 5)
, m_scrollAmount(2, -1)
, m_rootScrolls(0)
{
}
virtual void beginTest() OVERRIDE
{
gfx::Size viewportSize(10, 10);
gfx::Size deviceViewportSize = gfx::ToCeiledSize(gfx::ScaleSize(viewportSize, m_deviceScaleFactor));
m_layerTreeHost->setViewportSize(viewportSize, deviceViewportSize);
m_layerTreeHost->setDeviceScaleFactor(m_deviceScaleFactor);
m_rootScrollLayer = ContentLayer::create(&m_fakeDelegate);
m_rootScrollLayer->setBounds(gfx::Size(110, 110));
m_rootScrollLayer->setPosition(gfx::PointF(0, 0));
m_rootScrollLayer->setAnchorPoint(gfx::PointF(0, 0));
m_rootScrollLayer->setIsDrawable(true);
m_rootScrollLayer->setScrollable(true);
m_rootScrollLayer->setMaxScrollOffset(gfx::Vector2d(100, 100));
m_layerTreeHost->rootLayer()->addChild(m_rootScrollLayer);
m_rootScrollLayer->setScrollOffset(m_initialScroll);
postSetNeedsCommitToMainThread();
}
virtual void applyScrollAndScale(gfx::Vector2d scrollDelta, float scale) OVERRIDE
{
gfx::Vector2d offset = m_rootScrollLayer->scrollOffset();
m_rootScrollLayer->setScrollOffset(offset + scrollDelta);
m_rootScrolls++;
}
virtual void layout() OVERRIDE
{
switch (m_layerTreeHost->commitNumber()) {
case 0:
EXPECT_VECTOR_EQ(m_initialScroll, m_rootScrollLayer->scrollOffset());
break;
case 1:
EXPECT_VECTOR_EQ(m_initialScroll + m_scrollAmount, m_rootScrollLayer->scrollOffset());
// Pretend like Javascript updated the scroll position itself.
m_rootScrollLayer->setScrollOffset(m_secondScroll);
break;
case 2:
EXPECT_VECTOR_EQ(m_secondScroll + m_scrollAmount, m_rootScrollLayer->scrollOffset());
break;
}
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
LayerImpl* root = impl->rootLayer();
LayerImpl* rootScrollLayer = root->children()[0];
EXPECT_VECTOR_EQ(root->scrollDelta(), gfx::Vector2d());
EXPECT_EQ(rootScrollLayer->bounds().width() * m_deviceScaleFactor, rootScrollLayer->contentBounds().width());
EXPECT_EQ(rootScrollLayer->bounds().height() * m_deviceScaleFactor, rootScrollLayer->contentBounds().height());
switch (impl->sourceFrameNumber()) {
case 0:
// Gesture scroll on impl thread.
EXPECT_EQ(impl->scrollBegin(gfx::Point(5, 5), InputHandlerClient::Gesture), InputHandlerClient::ScrollStarted);
impl->scrollBy(gfx::Point(), m_scrollAmount);
impl->scrollEnd();
EXPECT_VECTOR_EQ(m_initialScroll, rootScrollLayer->scrollOffset());
EXPECT_VECTOR_EQ(m_scrollAmount, rootScrollLayer->scrollDelta());
break;
case 1:
// Wheel scroll on impl thread.
EXPECT_EQ(impl->scrollBegin(gfx::Point(5, 5), InputHandlerClient::Wheel), InputHandlerClient::ScrollStarted);
impl->scrollBy(gfx::Point(), m_scrollAmount);
impl->scrollEnd();
EXPECT_VECTOR_EQ(m_secondScroll, rootScrollLayer->scrollOffset());
EXPECT_VECTOR_EQ(m_scrollAmount, rootScrollLayer->scrollDelta());
break;
case 2:
EXPECT_VECTOR_EQ(m_secondScroll + m_scrollAmount, rootScrollLayer->scrollOffset());
EXPECT_VECTOR_EQ(gfx::Vector2d(0, 0), rootScrollLayer->scrollDelta());
endTest();
}
}
virtual void afterTest() OVERRIDE
{
EXPECT_EQ(2, m_rootScrolls);
}
private:
float m_deviceScaleFactor;
gfx::Vector2d m_initialScroll;
gfx::Vector2d m_secondScroll;
gfx::Vector2d m_scrollAmount;
int m_rootScrolls;
FakeContentLayerClient m_fakeDelegate;
scoped_refptr<Layer> m_rootScrollLayer;
};
class LayerTreeHostTestScrollRootScrollLayerNormalDpi : public LayerTreeHostTestScrollRootScrollLayer {
public:
LayerTreeHostTestScrollRootScrollLayerNormalDpi() : LayerTreeHostTestScrollRootScrollLayer(1) { }
};
TEST_F(LayerTreeHostTestScrollRootScrollLayerNormalDpi, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestScrollRootScrollLayerHighDpi : public LayerTreeHostTestScrollRootScrollLayer {
public:
LayerTreeHostTestScrollRootScrollLayerHighDpi() : LayerTreeHostTestScrollRootScrollLayer(2) { }
};
TEST_F(LayerTreeHostTestScrollRootScrollLayerHighDpi, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestCompositeAndReadbackCleanup : public LayerTreeHostTest {
public:
LayerTreeHostTestCompositeAndReadbackCleanup() { }
virtual void beginTest() OVERRIDE
{
Layer* rootLayer = m_layerTreeHost->rootLayer();
char pixels[4];
m_layerTreeHost->compositeAndReadback(static_cast<void*>(&pixels), gfx::Rect(0, 0, 1, 1));
EXPECT_FALSE(rootLayer->renderSurface());
endTest();
}
virtual void afterTest() OVERRIDE
{
}
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestCompositeAndReadbackCleanup)
class LayerTreeHostTestCompositeAndReadbackAnimateCount : public LayerTreeHostTest {
public:
LayerTreeHostTestCompositeAndReadbackAnimateCount()
: m_layoutCount(0)
{
}
virtual void animate(base::TimeTicks) OVERRIDE
{
// We shouldn't animate on the compositeAndReadback-forced commit, but we should
// for the setNeedsCommit-triggered commit.
EXPECT_EQ(1, m_layoutCount);
}
virtual void layout() OVERRIDE
{
m_layoutCount++;
if (m_layoutCount == 2)
endTest();
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setNeedsCommit();
char pixels[4];
m_layerTreeHost->compositeAndReadback(&pixels, gfx::Rect(0, 0, 1, 1));
}
virtual void afterTest() OVERRIDE
{
}
private:
int m_layoutCount;
};
TEST_F(LayerTreeHostTestCompositeAndReadbackAnimateCount, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit : public LayerTreeHostTest {
public:
LayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit()
: m_rootLayer(ContentLayerWithUpdateTracking::create(&m_fakeDelegate))
, m_surfaceLayer1(ContentLayerWithUpdateTracking::create(&m_fakeDelegate))
, m_replicaLayer1(ContentLayerWithUpdateTracking::create(&m_fakeDelegate))
, m_surfaceLayer2(ContentLayerWithUpdateTracking::create(&m_fakeDelegate))
, m_replicaLayer2(ContentLayerWithUpdateTracking::create(&m_fakeDelegate))
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setViewportSize(gfx::Size(100, 100), gfx::Size(100, 100));
m_rootLayer->setBounds(gfx::Size(100, 100));
m_surfaceLayer1->setBounds(gfx::Size(100, 100));
m_surfaceLayer1->setForceRenderSurface(true);
m_surfaceLayer1->setOpacity(0.5);
m_surfaceLayer2->setBounds(gfx::Size(100, 100));
m_surfaceLayer2->setForceRenderSurface(true);
m_surfaceLayer2->setOpacity(0.5);
m_surfaceLayer1->setReplicaLayer(m_replicaLayer1.get());
m_surfaceLayer2->setReplicaLayer(m_replicaLayer2.get());
m_rootLayer->addChild(m_surfaceLayer1);
m_surfaceLayer1->addChild(m_surfaceLayer2);
m_layerTreeHost->setRootLayer(m_rootLayer);
postSetNeedsCommitToMainThread();
}
virtual void drawLayersOnThread(LayerTreeHostImpl* hostImpl) OVERRIDE
{
Renderer* renderer = hostImpl->renderer();
RenderPass::Id surface1RenderPassId = hostImpl->rootLayer()->children()[0]->renderSurface()->renderPassId();
RenderPass::Id surface2RenderPassId = hostImpl->rootLayer()->children()[0]->children()[0]->renderSurface()->renderPassId();
switch (hostImpl->sourceFrameNumber()) {
case 0:
EXPECT_TRUE(renderer->haveCachedResourcesForRenderPassId(surface1RenderPassId));
EXPECT_TRUE(renderer->haveCachedResourcesForRenderPassId(surface2RenderPassId));
// Reduce the memory limit to only fit the root layer and one render surface. This
// prevents any contents drawing into surfaces from being allocated.
hostImpl->setManagedMemoryPolicy(ManagedMemoryPolicy(100 * 100 * 4 * 2));
break;
case 1:
EXPECT_FALSE(renderer->haveCachedResourcesForRenderPassId(surface1RenderPassId));
EXPECT_FALSE(renderer->haveCachedResourcesForRenderPassId(surface2RenderPassId));
endTest();
break;
}
}
virtual void afterTest() OVERRIDE
{
EXPECT_EQ(2, m_rootLayer->paintContentsCount());
EXPECT_EQ(2, m_surfaceLayer1->paintContentsCount());
EXPECT_EQ(2, m_surfaceLayer2->paintContentsCount());
// Clear layer references so LayerTreeHost dies.
m_rootLayer = NULL;
m_surfaceLayer1 = NULL;
m_replicaLayer1 = NULL;
m_surfaceLayer2 = NULL;
m_replicaLayer2 = NULL;
}
private:
FakeContentLayerClient m_fakeDelegate;
scoped_refptr<ContentLayerWithUpdateTracking> m_rootLayer;
scoped_refptr<ContentLayerWithUpdateTracking> m_surfaceLayer1;
scoped_refptr<ContentLayerWithUpdateTracking> m_replicaLayer1;
scoped_refptr<ContentLayerWithUpdateTracking> m_surfaceLayer2;
scoped_refptr<ContentLayerWithUpdateTracking> m_replicaLayer2;
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestSurfaceNotAllocatedForLayersOutsideMemoryLimit)
class EvictionTestLayer : public Layer {
public:
static scoped_refptr<EvictionTestLayer> create() { return make_scoped_refptr(new EvictionTestLayer()); }
virtual void update(ResourceUpdateQueue&, const OcclusionTracker*, RenderingStats&) OVERRIDE;
virtual bool drawsContent() const OVERRIDE { return true; }
virtual scoped_ptr<LayerImpl> createLayerImpl() OVERRIDE;
virtual void pushPropertiesTo(LayerImpl*) OVERRIDE;
virtual void setTexturePriorities(const PriorityCalculator&) OVERRIDE;
bool haveBackingTexture() const { return m_texture.get() ? m_texture->haveBackingTexture() : false; }
private:
EvictionTestLayer() : Layer() { }
virtual ~EvictionTestLayer() { }
void createTextureIfNeeded()
{
if (m_texture.get())
return;
m_texture = PrioritizedResource::create(layerTreeHost()->contentsTextureManager());
m_texture->setDimensions(gfx::Size(10, 10), GL_RGBA);
m_bitmap.setConfig(SkBitmap::kARGB_8888_Config, 10, 10);
}
scoped_ptr<PrioritizedResource> m_texture;
SkBitmap m_bitmap;
};
class EvictionTestLayerImpl : public LayerImpl {
public:
static scoped_ptr<EvictionTestLayerImpl> create(int id)
{
return make_scoped_ptr(new EvictionTestLayerImpl(id));
}
virtual ~EvictionTestLayerImpl() { }
virtual void appendQuads(QuadSink& quadSink, AppendQuadsData&) OVERRIDE
{
ASSERT_TRUE(m_hasTexture);
ASSERT_NE(0u, layerTreeHostImpl()->resourceProvider()->numResources());
}
void setHasTexture(bool hasTexture) { m_hasTexture = hasTexture; }
private:
explicit EvictionTestLayerImpl(int id)
: LayerImpl(id)
, m_hasTexture(false) { }
bool m_hasTexture;
};
void EvictionTestLayer::setTexturePriorities(const PriorityCalculator&)
{
createTextureIfNeeded();
if (!m_texture.get())
return;
m_texture->setRequestPriority(PriorityCalculator::uiPriority(true));
}
void EvictionTestLayer::update(ResourceUpdateQueue& queue, const OcclusionTracker*, RenderingStats&)
{
createTextureIfNeeded();
if (!m_texture.get())
return;
gfx::Rect fullRect(0, 0, 10, 10);
ResourceUpdate upload = ResourceUpdate::Create(
m_texture.get(), &m_bitmap, fullRect, fullRect, gfx::Vector2d());
queue.appendFullUpload(upload);
}
scoped_ptr<LayerImpl> EvictionTestLayer::createLayerImpl()
{
return EvictionTestLayerImpl::create(m_layerId).PassAs<LayerImpl>();
}
void EvictionTestLayer::pushPropertiesTo(LayerImpl* layerImpl)
{
Layer::pushPropertiesTo(layerImpl);
EvictionTestLayerImpl* testLayerImpl = static_cast<EvictionTestLayerImpl*>(layerImpl);
testLayerImpl->setHasTexture(m_texture->haveBackingTexture());
}
class LayerTreeHostTestEvictTextures : public LayerTreeHostTest {
public:
LayerTreeHostTestEvictTextures()
: m_layer(EvictionTestLayer::create())
, m_implForEvictTextures(0)
, m_numCommits(0)
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setRootLayer(m_layer);
m_layerTreeHost->setViewportSize(gfx::Size(10, 20), gfx::Size(10, 20));
gfx::Transform identityMatrix;
setLayerPropertiesForTesting(m_layer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(10, 20), true);
postSetNeedsCommitToMainThread();
}
void postEvictTextures()
{
DCHECK(implThread());
implThread()->postTask(base::Bind(&LayerTreeHostTestEvictTextures::evictTexturesOnImplThread,
base::Unretained(this)));
}
void evictTexturesOnImplThread()
{
DCHECK(m_implForEvictTextures);
m_implForEvictTextures->enforceManagedMemoryPolicy(ManagedMemoryPolicy(0));
}
// Commit 1: Just commit and draw normally, then post an eviction at the end
// that will trigger a commit.
// Commit 2: Triggered by the eviction, let it go through and then set
// needsCommit.
// Commit 3: Triggered by the setNeedsCommit. In layout(), post an eviction
// task, which will be handled before the commit. Don't set needsCommit, it
// should have been posted. A frame should not be drawn (note,
// didCommitAndDrawFrame may be called anyway).
// Commit 4: Triggered by the eviction, let it go through and then set
// needsCommit.
// Commit 5: Triggered by the setNeedsCommit, post an eviction task in
// layout(), a frame should not be drawn but a commit will be posted.
// Commit 6: Triggered by the eviction, post an eviction task in
// layout(), which will be a noop, letting the commit (which recreates the
// textures) go through and draw a frame, then end the test.
//
// Commits 1+2 test the eviction recovery path where eviction happens outside
// of the beginFrame/commit pair.
// Commits 3+4 test the eviction recovery path where eviction happens inside
// the beginFrame/commit pair.
// Commits 5+6 test the path where an eviction happens during the eviction
// recovery path.
virtual void didCommitAndDrawFrame() OVERRIDE
{
switch (m_numCommits) {
case 1:
EXPECT_TRUE(m_layer->haveBackingTexture());
postEvictTextures();
break;
case 2:
EXPECT_TRUE(m_layer->haveBackingTexture());
m_layerTreeHost->setNeedsCommit();
break;
case 3:
break;
case 4:
EXPECT_TRUE(m_layer->haveBackingTexture());
m_layerTreeHost->setNeedsCommit();
break;
case 5:
break;
case 6:
EXPECT_TRUE(m_layer->haveBackingTexture());
endTest();
break;
default:
NOTREACHED();
break;
}
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
m_implForEvictTextures = impl;
}
virtual void layout() OVERRIDE
{
++m_numCommits;
switch (m_numCommits) {
case 1:
case 2:
break;
case 3:
postEvictTextures();
break;
case 4:
// We couldn't check in didCommitAndDrawFrame on commit 3, so check here.
EXPECT_FALSE(m_layer->haveBackingTexture());
break;
case 5:
postEvictTextures();
break;
case 6:
// We couldn't check in didCommitAndDrawFrame on commit 5, so check here.
EXPECT_FALSE(m_layer->haveBackingTexture());
postEvictTextures();
break;
default:
NOTREACHED();
break;
}
}
virtual void afterTest() OVERRIDE
{
}
private:
FakeContentLayerClient m_client;
scoped_refptr<EvictionTestLayer> m_layer;
LayerTreeHostImpl* m_implForEvictTextures;
int m_numCommits;
};
TEST_F(LayerTreeHostTestEvictTextures, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestLostContextAfterEvictTextures : public LayerTreeHostTest {
public:
LayerTreeHostTestLostContextAfterEvictTextures()
: m_layer(EvictionTestLayer::create())
, m_implForEvictTextures(0)
, m_numCommits(0)
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setRootLayer(m_layer);
m_layerTreeHost->setViewportSize(gfx::Size(10, 20), gfx::Size(10, 20));
gfx::Transform identityMatrix;
setLayerPropertiesForTesting(m_layer.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(10, 20), true);
postSetNeedsCommitToMainThread();
}
void postEvictTextures()
{
if (implThread()) {
implThread()->postTask(base::Bind(&LayerTreeHostTestLostContextAfterEvictTextures::evictTexturesOnImplThread,
base::Unretained(this)));
} else {
DebugScopedSetImplThread impl(proxy());
evictTexturesOnImplThread();
}
}
void evictTexturesOnImplThread()
{
DCHECK(m_implForEvictTextures);
m_implForEvictTextures->enforceManagedMemoryPolicy(ManagedMemoryPolicy(0));
}
// Commit 1: Just commit and draw normally, then at the end, set ourselves
// invisible (to prevent a commit that would recreate textures after
// eviction, before the context recovery), and post a task that will evict
// textures, then cause the context to be lost, and then set ourselves
// visible again (to allow commits, since that's what causes context
// recovery in single thread).
virtual void didCommitAndDrawFrame() OVERRIDE
{
++m_numCommits;
switch (m_numCommits) {
case 1:
EXPECT_TRUE(m_layer->haveBackingTexture());
m_layerTreeHost->setVisible(false);
postEvictTextures();
m_layerTreeHost->loseContext(1);
m_layerTreeHost->setVisible(true);
break;
default:
break;
}
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
m_implForEvictTextures = impl;
}
virtual void didRecreateOutputSurface(bool succeeded) OVERRIDE
{
EXPECT_TRUE(succeeded);
endTest();
}
virtual void afterTest() OVERRIDE
{
}
private:
FakeContentLayerClient m_client;
scoped_refptr<EvictionTestLayer> m_layer;
LayerTreeHostImpl* m_implForEvictTextures;
int m_numCommits;
};
SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostTestLostContextAfterEvictTextures)
class CompositorFakeWebGraphicsContext3DWithEndQueryCausingLostContext : public WebKit::CompositorFakeWebGraphicsContext3D {
public:
static scoped_ptr<CompositorFakeWebGraphicsContext3DWithEndQueryCausingLostContext> create(Attributes attrs)
{
return make_scoped_ptr(new CompositorFakeWebGraphicsContext3DWithEndQueryCausingLostContext(attrs));
}
virtual void setContextLostCallback(WebGraphicsContextLostCallback* callback) { m_contextLostCallback = callback; }
virtual bool isContextLost() { return m_isContextLost; }
virtual void beginQueryEXT(WGC3Denum, WebGLId) { }
virtual void endQueryEXT(WGC3Denum)
{
// Lose context.
if (!m_isContextLost) {
m_contextLostCallback->onContextLost();
m_isContextLost = true;
}
}
virtual void getQueryObjectuivEXT(WebGLId, WGC3Denum pname, WGC3Duint* params)
{
// Context is lost. We need to behave as if result is available.
if (pname == GL_QUERY_RESULT_AVAILABLE_EXT)
*params = 1;
}
private:
explicit CompositorFakeWebGraphicsContext3DWithEndQueryCausingLostContext(Attributes attrs)
: CompositorFakeWebGraphicsContext3D(attrs)
, m_contextLostCallback(0)
, m_isContextLost(false) { }
WebGraphicsContextLostCallback* m_contextLostCallback;
bool m_isContextLost;
};
class LayerTreeHostTestLostContextWhileUpdatingResources : public LayerTreeHostTest {
public:
LayerTreeHostTestLostContextWhileUpdatingResources()
: m_parent(ContentLayerWithUpdateTracking::create(&m_client))
, m_numChildren(50)
{
for (int i = 0; i < m_numChildren; i++)
m_children.push_back(ContentLayerWithUpdateTracking::create(&m_client));
}
virtual scoped_ptr<WebKit::WebCompositorOutputSurface> createOutputSurface()
{
return FakeWebCompositorOutputSurface::create(CompositorFakeWebGraphicsContext3DWithEndQueryCausingLostContext::create(WebGraphicsContext3D::Attributes()).PassAs<WebKit::WebGraphicsContext3D>()).PassAs<WebKit::WebCompositorOutputSurface>();
}
virtual void beginTest()
{
m_layerTreeHost->setRootLayer(m_parent);
m_layerTreeHost->setViewportSize(gfx::Size(m_numChildren, 1), gfx::Size(m_numChildren, 1));
gfx::Transform identityMatrix;
setLayerPropertiesForTesting(m_parent.get(), 0, identityMatrix, gfx::PointF(0, 0), gfx::PointF(0, 0), gfx::Size(m_numChildren, 1), true);
for (int i = 0; i < m_numChildren; i++)
setLayerPropertiesForTesting(m_children[i].get(), m_parent.get(), identityMatrix, gfx::PointF(0, 0), gfx::PointF(i, 0), gfx::Size(1, 1), false);
postSetNeedsCommitToMainThread();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl* impl)
{
endTest();
}
virtual void layout()
{
m_parent->setNeedsDisplay();
for (int i = 0; i < m_numChildren; i++)
m_children[i]->setNeedsDisplay();
}
virtual void afterTest()
{
}
private:
FakeContentLayerClient m_client;
scoped_refptr<ContentLayerWithUpdateTracking> m_parent;
int m_numChildren;
std::vector<scoped_refptr<ContentLayerWithUpdateTracking> > m_children;
};
TEST_F(LayerTreeHostTestLostContextWhileUpdatingResources, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestContinuousCommit : public LayerTreeHostTest {
public:
LayerTreeHostTestContinuousCommit()
: m_numCommitComplete(0)
, m_numDrawLayers(0)
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setViewportSize(gfx::Size(10, 10), gfx::Size(10, 10));
m_layerTreeHost->rootLayer()->setBounds(gfx::Size(10, 10));
postSetNeedsCommitToMainThread();
}
virtual void didCommit() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
if (m_numDrawLayers == 1)
m_numCommitComplete++;
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
m_numDrawLayers++;
if (m_numDrawLayers == 2)
endTest();
}
virtual void afterTest() OVERRIDE
{
// Check that we didn't commit twice between first and second draw.
EXPECT_EQ(1, m_numCommitComplete);
}
private:
int m_numCommitComplete;
int m_numDrawLayers;
};
TEST_F(LayerTreeHostTestContinuousCommit, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestContinuousInvalidate : public LayerTreeHostTest {
public:
LayerTreeHostTestContinuousInvalidate()
: m_numCommitComplete(0)
, m_numDrawLayers(0)
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setViewportSize(gfx::Size(10, 10), gfx::Size(10, 10));
m_layerTreeHost->rootLayer()->setBounds(gfx::Size(10, 10));
m_contentLayer = ContentLayer::create(&m_fakeDelegate);
m_contentLayer->setBounds(gfx::Size(10, 10));
m_contentLayer->setPosition(gfx::PointF(0, 0));
m_contentLayer->setAnchorPoint(gfx::PointF(0, 0));
m_contentLayer->setIsDrawable(true);
m_layerTreeHost->rootLayer()->addChild(m_contentLayer);
postSetNeedsCommitToMainThread();
}
virtual void didCommit() OVERRIDE
{
m_contentLayer->setNeedsDisplay();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
if (m_numDrawLayers == 1)
m_numCommitComplete++;
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
m_numDrawLayers++;
if (m_numDrawLayers == 2)
endTest();
}
virtual void afterTest() OVERRIDE
{
// Check that we didn't commit twice between first and second draw.
EXPECT_EQ(1, m_numCommitComplete);
// Clear layer references so LayerTreeHost dies.
m_contentLayer = NULL;
}
private:
FakeContentLayerClient m_fakeDelegate;
scoped_refptr<Layer> m_contentLayer;
int m_numCommitComplete;
int m_numDrawLayers;
};
TEST_F(LayerTreeHostTestContinuousInvalidate, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestAdjustPointForZoom : public LayerTreeHostTest {
public:
LayerTreeHostTestAdjustPointForZoom()
{
}
virtual void beginTest() OVERRIDE
{
gfx::Transform m;
m.Translate(250, 360);
m.Scale(2, 2);
gfx::Point point(400, 550);
gfx::Point transformedPoint;
// Unit transform, no change expected.
m_layerTreeHost->setImplTransform(gfx::Transform());
transformedPoint = gfx::ToRoundedPoint(m_layerTreeHost->adjustEventPointForPinchZoom(point));
EXPECT_EQ(point.x(), transformedPoint.x());
EXPECT_EQ(point.y(), transformedPoint.y());
m_layerTreeHost->setImplTransform(m);
// Apply m^(-1): 75 = (400 - 250) / 2; 95 = (550 - 360) / 2.
transformedPoint = gfx::ToRoundedPoint(m_layerTreeHost->adjustEventPointForPinchZoom(point));
EXPECT_EQ(75, transformedPoint.x());
EXPECT_EQ(95, transformedPoint.y());
endTest();
}
virtual void afterTest() OVERRIDE
{
}
};
TEST_F(LayerTreeHostTestAdjustPointForZoom, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestContinuousAnimate : public LayerTreeHostTest {
public:
LayerTreeHostTestContinuousAnimate()
: m_numCommitComplete(0)
, m_numDrawLayers(0)
{
}
virtual void beginTest() OVERRIDE
{
m_layerTreeHost->setViewportSize(gfx::Size(10, 10), gfx::Size(10, 10));
m_layerTreeHost->rootLayer()->setBounds(gfx::Size(10, 10));
postSetNeedsCommitToMainThread();
}
virtual void animate(base::TimeTicks) OVERRIDE
{
m_layerTreeHost->setNeedsAnimate();
}
virtual void layout() OVERRIDE
{
m_layerTreeHost->rootLayer()->setNeedsDisplay();
}
virtual void commitCompleteOnThread(LayerTreeHostImpl*) OVERRIDE
{
if (m_numDrawLayers == 1)
m_numCommitComplete++;
}
virtual void drawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE
{
m_numDrawLayers++;
if (m_numDrawLayers == 2)
endTest();
}
virtual void afterTest() OVERRIDE
{
// Check that we didn't commit twice between first and second draw.
EXPECT_EQ(1, m_numCommitComplete);
}
private:
int m_numCommitComplete;
int m_numDrawLayers;
};
TEST_F(LayerTreeHostTestContinuousAnimate, runMultiThread)
{
runTest(true);
}
class LayerTreeHostTestDeferCommits : public LayerTreeHostTest {
public:
LayerTreeHostTestDeferCommits()
: m_numCommitsDeferred(0)
, m_numCompleteCommits(0)
{
}
virtual void beginTest() OVERRIDE
{
postSetNeedsCommitToMainThread();
}
virtual void didDeferCommit() OVERRIDE
{
m_numCommitsDeferred++;
m_layerTreeHost->setDeferCommits(false);
}
virtual void didCommit() OVERRIDE
{
m_numCompleteCommits++;
switch (m_numCompleteCommits) {
case 1:
EXPECT_EQ(0, m_numCommitsDeferred);
m_layerTreeHost->setDeferCommits(true);
postSetNeedsCommitToMainThread();
break;
case 2:
endTest();
break;
default:
NOTREACHED();
break;
}
}
virtual void afterTest() OVERRIDE
{
EXPECT_EQ(1, m_numCommitsDeferred);
EXPECT_EQ(2, m_numCompleteCommits);
}
private:
int m_numCommitsDeferred;
int m_numCompleteCommits;
};
TEST_F(LayerTreeHostTestDeferCommits, runMultiThread)
{
runTest(true);
}
} // namespace
} // namespace cc