blob: 2441716f57c81656e75a15c5acd487d60d8ef48e [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/scheduler.h"
#include "base/logging.h"
#include "cc/test/scheduler_test_common.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using namespace WebKitTests;
namespace cc {
namespace {
class FakeSchedulerClient : public SchedulerClient {
public:
FakeSchedulerClient() { reset(); }
void reset()
{
m_actions.clear();
m_drawWillHappen = true;
m_swapWillHappenIfDrawHappens = true;
m_numDraws = 0;
}
int numDraws() const { return m_numDraws; }
int numActions() const { return static_cast<int>(m_actions.size()); }
const char* action(int i) const { return m_actions[i]; }
bool hasAction(const char* action) const
{
for (size_t i = 0; i < m_actions.size(); i++)
if (!strcmp(m_actions[i], action))
return true;
return false;
}
virtual void scheduledActionBeginFrame() OVERRIDE { m_actions.push_back("scheduledActionBeginFrame"); }
virtual ScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapIfPossible() OVERRIDE
{
m_actions.push_back("scheduledActionDrawAndSwapIfPossible");
m_numDraws++;
return ScheduledActionDrawAndSwapResult(m_drawWillHappen, m_drawWillHappen && m_swapWillHappenIfDrawHappens);
}
virtual ScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapForced() OVERRIDE
{
m_actions.push_back("scheduledActionDrawAndSwapForced");
return ScheduledActionDrawAndSwapResult(true, m_swapWillHappenIfDrawHappens);
}
virtual void scheduledActionCommit() OVERRIDE { m_actions.push_back("scheduledActionCommit"); }
virtual void scheduledActionBeginContextRecreation() OVERRIDE { m_actions.push_back("scheduledActionBeginContextRecreation"); }
virtual void scheduledActionAcquireLayerTexturesForMainThread() OVERRIDE { m_actions.push_back("scheduledActionAcquireLayerTexturesForMainThread"); }
virtual void didAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE { }
void setDrawWillHappen(bool drawWillHappen) { m_drawWillHappen = drawWillHappen; }
void setSwapWillHappenIfDrawHappens(bool swapWillHappenIfDrawHappens) { m_swapWillHappenIfDrawHappens = swapWillHappenIfDrawHappens; }
protected:
bool m_drawWillHappen;
bool m_swapWillHappenIfDrawHappens;
int m_numDraws;
std::vector<const char*> m_actions;
};
TEST(SchedulerTest, RequestCommit)
{
FakeSchedulerClient client;
scoped_refptr<FakeTimeSource> timeSource(new FakeTimeSource());
scoped_ptr<Scheduler> scheduler = Scheduler::create(&client, make_scoped_ptr(new FrameRateController(timeSource)));
scheduler->setCanBeginFrame(true);
scheduler->setVisible(true);
scheduler->setCanDraw(true);
// SetNeedsCommit should begin the frame.
scheduler->setNeedsCommit();
EXPECT_EQ(1, client.numActions());
EXPECT_STREQ("scheduledActionBeginFrame", client.action(0));
EXPECT_FALSE(timeSource->active());
client.reset();
// beginFrameComplete should commit
scheduler->beginFrameComplete();
EXPECT_EQ(1, client.numActions());
EXPECT_STREQ("scheduledActionCommit", client.action(0));
EXPECT_TRUE(timeSource->active());
client.reset();
// Tick should draw.
timeSource->tick();
EXPECT_EQ(1, client.numActions());
EXPECT_STREQ("scheduledActionDrawAndSwapIfPossible", client.action(0));
EXPECT_FALSE(timeSource->active());
client.reset();
// Timer should be off.
EXPECT_FALSE(timeSource->active());
}
TEST(SchedulerTest, RequestCommitAfterBeginFrame)
{
FakeSchedulerClient client;
scoped_refptr<FakeTimeSource> timeSource(new FakeTimeSource());
scoped_ptr<Scheduler> scheduler = Scheduler::create(&client, make_scoped_ptr(new FrameRateController(timeSource)));
scheduler->setCanBeginFrame(true);
scheduler->setVisible(true);
scheduler->setCanDraw(true);
// SetNedsCommit should begin the frame.
scheduler->setNeedsCommit();
EXPECT_EQ(1, client.numActions());
EXPECT_STREQ("scheduledActionBeginFrame", client.action(0));
client.reset();
// Now setNeedsCommit again. Calling here means we need a second frame.
scheduler->setNeedsCommit();
// Since, another commit is needed, beginFrameComplete should commit,
// then begin another frame.
scheduler->beginFrameComplete();
EXPECT_EQ(1, client.numActions());
EXPECT_STREQ("scheduledActionCommit", client.action(0));
client.reset();
// Tick should draw but then begin another frame.
timeSource->tick();
EXPECT_FALSE(timeSource->active());
EXPECT_EQ(2, client.numActions());
EXPECT_STREQ("scheduledActionDrawAndSwapIfPossible", client.action(0));
EXPECT_STREQ("scheduledActionBeginFrame", client.action(1));
client.reset();
}
TEST(SchedulerTest, TextureAcquisitionCollision)
{
FakeSchedulerClient client;
scoped_refptr<FakeTimeSource> timeSource(new FakeTimeSource());
scoped_ptr<Scheduler> scheduler = Scheduler::create(&client, make_scoped_ptr(new FrameRateController(timeSource)));
scheduler->setCanBeginFrame(true);
scheduler->setVisible(true);
scheduler->setCanDraw(true);
scheduler->setNeedsCommit();
scheduler->setMainThreadNeedsLayerTextures();
EXPECT_EQ(2, client.numActions());
EXPECT_STREQ("scheduledActionBeginFrame", client.action(0));
EXPECT_STREQ("scheduledActionAcquireLayerTexturesForMainThread", client.action(1));
client.reset();
// Compositor not scheduled to draw because textures are locked by main thread
EXPECT_FALSE(timeSource->active());
// Trigger the commit
scheduler->beginFrameComplete();
EXPECT_TRUE(timeSource->active());
client.reset();
// Between commit and draw, texture acquisition for main thread delayed,
// and main thread blocks.
scheduler->setMainThreadNeedsLayerTextures();
EXPECT_EQ(0, client.numActions());
client.reset();
// Once compositor draw complete, the delayed texture acquisition fires.
timeSource->tick();
EXPECT_EQ(3, client.numActions());
EXPECT_STREQ("scheduledActionDrawAndSwapIfPossible", client.action(0));
EXPECT_STREQ("scheduledActionAcquireLayerTexturesForMainThread", client.action(1));
EXPECT_STREQ("scheduledActionBeginFrame", client.action(2));
client.reset();
}
TEST(SchedulerTest, VisibilitySwitchWithTextureAcquisition)
{
FakeSchedulerClient client;
scoped_refptr<FakeTimeSource> timeSource(new FakeTimeSource());
scoped_ptr<Scheduler> scheduler = Scheduler::create(&client, make_scoped_ptr(new FrameRateController(timeSource)));
scheduler->setCanBeginFrame(true);
scheduler->setVisible(true);
scheduler->setCanDraw(true);
scheduler->setNeedsCommit();
scheduler->beginFrameComplete();
scheduler->setMainThreadNeedsLayerTextures();
client.reset();
// Verify that pending texture acquisition fires when visibility
// is lost in order to avoid a deadlock.
scheduler->setVisible(false);
EXPECT_EQ(1, client.numActions());
EXPECT_STREQ("scheduledActionAcquireLayerTexturesForMainThread", client.action(0));
client.reset();
// Regaining visibility with textures acquired by main thread while
// compositor is waiting for first draw should result in a request
// for a new frame in order to escape a deadlock.
scheduler->setVisible(true);
EXPECT_EQ(1, client.numActions());
EXPECT_STREQ("scheduledActionBeginFrame", client.action(0));
client.reset();
}
class SchedulerClientThatSetNeedsDrawInsideDraw : public FakeSchedulerClient {
public:
SchedulerClientThatSetNeedsDrawInsideDraw()
: m_scheduler(0) { }
void setScheduler(Scheduler* scheduler) { m_scheduler = scheduler; }
virtual void scheduledActionBeginFrame() OVERRIDE { }
virtual ScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapIfPossible() OVERRIDE
{
// Only setNeedsRedraw the first time this is called
if (!m_numDraws)
m_scheduler->setNeedsRedraw();
return FakeSchedulerClient::scheduledActionDrawAndSwapIfPossible();
}
virtual ScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapForced() OVERRIDE
{
NOTREACHED();
return ScheduledActionDrawAndSwapResult(true, true);
}
virtual void scheduledActionCommit() OVERRIDE { }
virtual void scheduledActionBeginContextRecreation() OVERRIDE { }
virtual void didAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE { }
protected:
Scheduler* m_scheduler;
};
// Tests for two different situations:
// 1. the scheduler dropping setNeedsRedraw requests that happen inside
// a scheduledActionDrawAndSwap
// 2. the scheduler drawing twice inside a single tick
TEST(SchedulerTest, RequestRedrawInsideDraw)
{
SchedulerClientThatSetNeedsDrawInsideDraw client;
scoped_refptr<FakeTimeSource> timeSource(new FakeTimeSource());
scoped_ptr<Scheduler> scheduler = Scheduler::create(&client, make_scoped_ptr(new FrameRateController(timeSource)));
client.setScheduler(scheduler.get());
scheduler->setCanBeginFrame(true);
scheduler->setVisible(true);
scheduler->setCanDraw(true);
scheduler->setNeedsRedraw();
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_TRUE(timeSource->active());
EXPECT_EQ(0, client.numDraws());
timeSource->tick();
EXPECT_EQ(1, client.numDraws());
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_TRUE(timeSource->active());
timeSource->tick();
EXPECT_EQ(2, client.numDraws());
EXPECT_FALSE(scheduler->redrawPending());
EXPECT_FALSE(timeSource->active());
}
// Test that requesting redraw inside a failed draw doesn't lose the request.
TEST(SchedulerTest, RequestRedrawInsideFailedDraw)
{
SchedulerClientThatSetNeedsDrawInsideDraw client;
scoped_refptr<FakeTimeSource> timeSource(new FakeTimeSource());
scoped_ptr<Scheduler> scheduler = Scheduler::create(&client, make_scoped_ptr(new FrameRateController(timeSource)));
client.setScheduler(scheduler.get());
scheduler->setCanBeginFrame(true);
scheduler->setVisible(true);
scheduler->setCanDraw(true);
client.setDrawWillHappen(false);
scheduler->setNeedsRedraw();
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_TRUE(timeSource->active());
EXPECT_EQ(0, client.numDraws());
// Fail the draw.
timeSource->tick();
EXPECT_EQ(1, client.numDraws());
// We have a commit pending and the draw failed, and we didn't lose the redraw request.
EXPECT_TRUE(scheduler->commitPending());
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_TRUE(timeSource->active());
// Fail the draw again.
timeSource->tick();
EXPECT_EQ(2, client.numDraws());
EXPECT_TRUE(scheduler->commitPending());
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_TRUE(timeSource->active());
// Draw successfully.
client.setDrawWillHappen(true);
timeSource->tick();
EXPECT_EQ(3, client.numDraws());
EXPECT_TRUE(scheduler->commitPending());
EXPECT_FALSE(scheduler->redrawPending());
EXPECT_FALSE(timeSource->active());
}
class SchedulerClientThatSetNeedsCommitInsideDraw : public FakeSchedulerClient {
public:
SchedulerClientThatSetNeedsCommitInsideDraw()
: m_scheduler(0) { }
void setScheduler(Scheduler* scheduler) { m_scheduler = scheduler; }
virtual void scheduledActionBeginFrame() OVERRIDE { }
virtual ScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapIfPossible() OVERRIDE
{
// Only setNeedsCommit the first time this is called
if (!m_numDraws)
m_scheduler->setNeedsCommit();
return FakeSchedulerClient::scheduledActionDrawAndSwapIfPossible();
}
virtual ScheduledActionDrawAndSwapResult scheduledActionDrawAndSwapForced() OVERRIDE
{
NOTREACHED();
return ScheduledActionDrawAndSwapResult(true, true);
}
virtual void scheduledActionCommit() OVERRIDE { }
virtual void scheduledActionBeginContextRecreation() OVERRIDE { }
virtual void didAnticipatedDrawTimeChange(base::TimeTicks) OVERRIDE { }
protected:
Scheduler* m_scheduler;
};
// Tests for the scheduler infinite-looping on setNeedsCommit requests that
// happen inside a scheduledActionDrawAndSwap
TEST(SchedulerTest, RequestCommitInsideDraw)
{
SchedulerClientThatSetNeedsCommitInsideDraw client;
scoped_refptr<FakeTimeSource> timeSource(new FakeTimeSource());
scoped_ptr<Scheduler> scheduler = Scheduler::create(&client, make_scoped_ptr(new FrameRateController(timeSource)));
client.setScheduler(scheduler.get());
scheduler->setCanBeginFrame(true);
scheduler->setVisible(true);
scheduler->setCanDraw(true);
scheduler->setNeedsRedraw();
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_EQ(0, client.numDraws());
EXPECT_TRUE(timeSource->active());
timeSource->tick();
EXPECT_FALSE(timeSource->active());
EXPECT_EQ(1, client.numDraws());
EXPECT_TRUE(scheduler->commitPending());
scheduler->beginFrameComplete();
timeSource->tick();
EXPECT_EQ(2, client.numDraws());
EXPECT_FALSE(timeSource->active());
EXPECT_FALSE(scheduler->redrawPending());
}
// Tests that when a draw fails then the pending commit should not be dropped.
TEST(SchedulerTest, RequestCommitInsideFailedDraw)
{
SchedulerClientThatSetNeedsDrawInsideDraw client;
scoped_refptr<FakeTimeSource> timeSource(new FakeTimeSource());
scoped_ptr<Scheduler> scheduler = Scheduler::create(&client, make_scoped_ptr(new FrameRateController(timeSource)));
client.setScheduler(scheduler.get());
scheduler->setCanBeginFrame(true);
scheduler->setVisible(true);
scheduler->setCanDraw(true);
client.setDrawWillHappen(false);
scheduler->setNeedsRedraw();
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_TRUE(timeSource->active());
EXPECT_EQ(0, client.numDraws());
// Fail the draw.
timeSource->tick();
EXPECT_EQ(1, client.numDraws());
// We have a commit pending and the draw failed, and we didn't lose the commit request.
EXPECT_TRUE(scheduler->commitPending());
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_TRUE(timeSource->active());
// Fail the draw again.
timeSource->tick();
EXPECT_EQ(2, client.numDraws());
EXPECT_TRUE(scheduler->commitPending());
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_TRUE(timeSource->active());
// Draw successfully.
client.setDrawWillHappen(true);
timeSource->tick();
EXPECT_EQ(3, client.numDraws());
EXPECT_TRUE(scheduler->commitPending());
EXPECT_FALSE(scheduler->redrawPending());
EXPECT_FALSE(timeSource->active());
}
TEST(SchedulerTest, NoBeginFrameWhenDrawFails)
{
scoped_refptr<FakeTimeSource> timeSource(new FakeTimeSource());
SchedulerClientThatSetNeedsCommitInsideDraw client;
scoped_ptr<FakeFrameRateController> controller(new FakeFrameRateController(timeSource));
FakeFrameRateController* controllerPtr = controller.get();
scoped_ptr<Scheduler> scheduler = Scheduler::create(&client, controller.PassAs<FrameRateController>());
client.setScheduler(scheduler.get());
scheduler->setCanBeginFrame(true);
scheduler->setVisible(true);
scheduler->setCanDraw(true);
EXPECT_EQ(0, controllerPtr->numFramesPending());
scheduler->setNeedsRedraw();
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_TRUE(timeSource->active());
EXPECT_EQ(0, client.numDraws());
// Draw successfully, this starts a new frame.
timeSource->tick();
EXPECT_EQ(1, client.numDraws());
EXPECT_EQ(1, controllerPtr->numFramesPending());
scheduler->didSwapBuffersComplete();
EXPECT_EQ(0, controllerPtr->numFramesPending());
scheduler->setNeedsRedraw();
EXPECT_TRUE(scheduler->redrawPending());
EXPECT_TRUE(timeSource->active());
// Fail to draw, this should not start a frame.
client.setDrawWillHappen(false);
timeSource->tick();
EXPECT_EQ(2, client.numDraws());
EXPECT_EQ(0, controllerPtr->numFramesPending());
}
TEST(SchedulerTest, NoBeginFrameWhenSwapFailsDuringForcedCommit)
{
scoped_refptr<FakeTimeSource> timeSource(new FakeTimeSource());
FakeSchedulerClient client;
scoped_ptr<FakeFrameRateController> controller(new FakeFrameRateController(timeSource));
FakeFrameRateController* controllerPtr = controller.get();
scoped_ptr<Scheduler> scheduler = Scheduler::create(&client, controller.PassAs<FrameRateController>());
EXPECT_EQ(0, controllerPtr->numFramesPending());
// Tell the client that it will fail to swap.
client.setDrawWillHappen(true);
client.setSwapWillHappenIfDrawHappens(false);
// Get the compositor to do a scheduledActionDrawAndSwapForced.
scheduler->setNeedsRedraw();
scheduler->setNeedsForcedRedraw();
EXPECT_TRUE(client.hasAction("scheduledActionDrawAndSwapForced"));
// We should not have told the frame rate controller that we began a frame.
EXPECT_EQ(0, controllerPtr->numFramesPending());
}
} // namespace
} // namespace cc