blob: a8c876872e915fbbc80e29e3fe3a3716b38fc389 [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 "config.h"
#include "CCDelayBasedTimeSource.h"
#include "CCSchedulerTestCommon.h"
#include "CCThread.h"
#include <gtest/gtest.h>
#include <wtf/RefPtr.h>
using namespace cc;
using namespace WTF;
using namespace WebKitTests;
namespace {
base::TimeDelta interval()
{
return base::TimeDelta::FromMicroseconds(base::Time::kMicrosecondsPerSecond / 60);
}
TEST(CCDelayBasedTimeSourceTest, TaskPostedAndTickCalled)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
EXPECT_TRUE(timer->active());
EXPECT_TRUE(thread.hasPendingTask());
timer->setNow(timer->now() + base::TimeDelta::FromMilliseconds(16));
thread.runPendingTask();
EXPECT_TRUE(timer->active());
EXPECT_TRUE(client.tickCalled());
}
TEST(CCDelayBasedTimeSource, TickNotCalledWithTaskPosted)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
EXPECT_TRUE(thread.hasPendingTask());
timer->setActive(false);
thread.runPendingTask();
EXPECT_FALSE(client.tickCalled());
}
TEST(CCDelayBasedTimeSource, StartTwiceEnqueuesOneTask)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
EXPECT_TRUE(thread.hasPendingTask());
thread.reset();
timer->setActive(true);
EXPECT_FALSE(thread.hasPendingTask());
}
TEST(CCDelayBasedTimeSource, StartWhenRunningDoesntTick)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
thread.runPendingTask();
thread.reset();
timer->setActive(true);
EXPECT_FALSE(thread.hasPendingTask());
}
// At 60Hz, when the tick returns at exactly the requested next time, make sure
// a 16ms next delay is posted.
TEST(CCDelayBasedTimeSource, NextDelaySaneWhenExactlyOnRequestedTime)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
// Run the first task, as that activates the timer and picks up a timebase.
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
timer->setNow(timer->now() + interval());
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
}
// At 60Hz, when the tick returns at slightly after the requested next time, make sure
// a 16ms next delay is posted.
TEST(CCDelayBasedTimeSource, NextDelaySaneWhenSlightlyAfterRequestedTime)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
// Run the first task, as that activates the timer and picks up a timebase.
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
timer->setNow(timer->now() + interval() + base::TimeDelta::FromMicroseconds(1));
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
}
// At 60Hz, when the tick returns at exactly 2*interval after the requested next time, make sure
// a 16ms next delay is posted.
TEST(CCDelayBasedTimeSource, NextDelaySaneWhenExactlyTwiceAfterRequestedTime)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
// Run the first task, as that activates the timer and picks up a timebase.
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
timer->setNow(timer->now() + 2 * interval());
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
}
// At 60Hz, when the tick returns at 2*interval and a bit after the requested next time, make sure
// a 16ms next delay is posted.
TEST(CCDelayBasedTimeSource, NextDelaySaneWhenSlightlyAfterTwiceRequestedTime)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
// Run the first task, as that activates the timer and picks up a timebase.
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
timer->setNow(timer->now() + 2 * interval() + base::TimeDelta::FromMicroseconds(1));
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
}
// At 60Hz, when the tick returns halfway to the next frame time, make sure
// a correct next delay value is posted.
TEST(CCDelayBasedTimeSource, NextDelaySaneWhenHalfAfterRequestedTime)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
// Run the first task, as that activates the timer and picks up a timebase.
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
timer->setNow(timer->now() + interval() + base::TimeDelta::FromMilliseconds(8));
thread.runPendingTask();
EXPECT_EQ(8, thread.pendingDelayMs());
}
// If the timebase and interval are updated with a jittery source, we want to
// make sure we do not double tick.
TEST(CCDelayBasedTimeSource, SaneHandlingOfJitteryTimebase)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
// Run the first task, as that activates the timer and picks up a timebase.
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
// Jitter timebase ~1ms late
timer->setNow(timer->now() + interval());
timer->setTimebaseAndInterval(timer->now() + base::TimeDelta::FromMilliseconds(1), interval());
thread.runPendingTask();
// Without double tick prevention, pendingDelayMs would be 1.
EXPECT_EQ(17, thread.pendingDelayMs());
// Jitter timebase ~1ms early
timer->setNow(timer->now() + interval());
timer->setTimebaseAndInterval(timer->now() - base::TimeDelta::FromMilliseconds(1), interval());
thread.runPendingTask();
EXPECT_EQ(15, thread.pendingDelayMs());
}
TEST(CCDelayBasedTimeSource, HandlesSignificantTimebaseChangesImmediately)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
// Run the first task, as that activates the timer and picks up a timebase.
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
// Tick, then shift timebase by +7ms.
timer->setNow(timer->now() + interval());
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
client.reset();
thread.runPendingTaskOnOverwrite(true);
base::TimeDelta jitter = base::TimeDelta::FromMilliseconds(7) + base::TimeDelta::FromMicroseconds(1);
timer->setTimebaseAndInterval(timer->now() + jitter, interval());
thread.runPendingTaskOnOverwrite(false);
EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled.
EXPECT_EQ(7, thread.pendingDelayMs());
// Tick, then shift timebase by -7ms.
timer->setNow(timer->now() + jitter);
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
client.reset();
thread.runPendingTaskOnOverwrite(true);
timer->setTimebaseAndInterval(base::TimeTicks() + interval(), interval());
thread.runPendingTaskOnOverwrite(false);
EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled.
EXPECT_EQ(16-7, thread.pendingDelayMs());
}
TEST(CCDelayBasedTimeSource, HanldlesSignificantIntervalChangesImmediately)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
// Run the first task, as that activates the timer and picks up a timebase.
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
// Tick, then double the interval.
timer->setNow(timer->now() + interval());
thread.runPendingTask();
EXPECT_EQ(16, thread.pendingDelayMs());
client.reset();
thread.runPendingTaskOnOverwrite(true);
timer->setTimebaseAndInterval(base::TimeTicks() + interval(), interval() * 2);
thread.runPendingTaskOnOverwrite(false);
EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled.
EXPECT_EQ(33, thread.pendingDelayMs());
// Tick, then halve the interval.
timer->setNow(timer->now() + interval() * 2);
thread.runPendingTask();
EXPECT_EQ(33, thread.pendingDelayMs());
client.reset();
thread.runPendingTaskOnOverwrite(true);
timer->setTimebaseAndInterval(base::TimeTicks() + interval() * 3, interval());
thread.runPendingTaskOnOverwrite(false);
EXPECT_FALSE(client.tickCalled()); // Make sure pending tasks were canceled.
EXPECT_EQ(16, thread.pendingDelayMs());
}
TEST(CCDelayBasedTimeSourceTest, AchievesTargetRateWithNoNoise)
{
int numIterations = 10;
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true);
double totalFrameTime = 0;
for (int i = 0; i < numIterations; ++i) {
long long delayMs = thread.pendingDelayMs();
// accumulate the "delay"
totalFrameTime += delayMs / 1000.0;
// Run the callback exactly when asked
timer->setNow(timer->now() + base::TimeDelta::FromMilliseconds(delayMs));
thread.runPendingTask();
}
double averageInterval = totalFrameTime / static_cast<double>(numIterations);
EXPECT_NEAR(1.0 / 60.0, averageInterval, 0.1);
}
TEST(CCDelayBasedTimeSource, TestDeactivateWhilePending)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
timer->setActive(true); // Should post a task.
timer->setActive(false);
timer.clear();
thread.runPendingTask(); // Should run the posted task without crashing.
}
TEST(CCDelayBasedTimeSource, TestDeactivateAndReactivateBeforeNextTickTime)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
// Should run the activate task, and pick up a new timebase.
timer->setActive(true);
thread.runPendingTask();
// Stop the timer
timer->setActive(false);
// Task will be pending anyway, run it
thread.runPendingTask();
// Start the timer again, but before the next tick time the timer previously
// planned on using. That same tick time should still be targeted.
timer->setNow(timer->now() + base::TimeDelta::FromMilliseconds(4));
timer->setActive(true);
EXPECT_EQ(12, thread.pendingDelayMs());
}
TEST(CCDelayBasedTimeSource, TestDeactivateAndReactivateAfterNextTickTime)
{
FakeCCThread thread;
FakeCCTimeSourceClient client;
RefPtr<FakeCCDelayBasedTimeSource> timer = FakeCCDelayBasedTimeSource::create(interval(), &thread);
timer->setClient(&client);
// Should run the activate task, and pick up a new timebase.
timer->setActive(true);
thread.runPendingTask();
// Stop the timer
timer->setActive(false);
// Task will be pending anyway, run it
thread.runPendingTask();
// Start the timer again, but before the next tick time the timer previously
// planned on using. That same tick time should still be targeted.
timer->setNow(timer->now() + base::TimeDelta::FromMilliseconds(20));
timer->setActive(true);
EXPECT_EQ(13, thread.pendingDelayMs());
}
}