// Copyright 2012 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/resources/resource_update_controller.h"
#include "base/test/test_simple_task_runner.h"
#include "cc/resources/prioritized_resource_manager.h"
#include "cc/test/fake_output_surface.h"
#include "cc/test/fake_output_surface_client.h"
#include "cc/test/fake_proxy.h"
#include "cc/test/scheduler_test_common.h"
#include "cc/test/test_shared_bitmap_manager.h"
#include "cc/test/test_web_graphics_context_3d.h"
#include "cc/test/tiled_layer_test_common.h"
#include "cc/trees/single_thread_proxy.h" // For DebugScopedSetImplThread
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/khronos/GLES2/gl2ext.h"
using testing::Test;
namespace cc {
namespace {
const int kFlushPeriodFull = 4;
const int kFlushPeriodPartial = kFlushPeriodFull;
class ResourceUpdateControllerTest;
class WebGraphicsContext3DForUploadTest : public TestWebGraphicsContext3D {
public:
explicit WebGraphicsContext3DForUploadTest(ResourceUpdateControllerTest* test)
: test_(test) {}
virtual void flush() OVERRIDE;
virtual void shallowFlushCHROMIUM() OVERRIDE;
virtual void texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
const void* pixels) OVERRIDE;
virtual void getQueryObjectuivEXT(GLuint id, GLenum pname, GLuint* value)
OVERRIDE;
private:
ResourceUpdateControllerTest* test_;
};
class ResourceUpdateControllerTest : public Test {
public:
ResourceUpdateControllerTest()
: proxy_(),
queue_(make_scoped_ptr(new ResourceUpdateQueue)),
resource_manager_(PrioritizedResourceManager::Create(&proxy_)),
query_results_available_(0),
full_upload_count_expected_(0),
partial_count_expected_(0),
total_upload_count_expected_(0),
max_upload_count_per_update_(0),
num_consecutive_flushes_(0),
num_dangling_uploads_(0),
num_total_uploads_(0),
num_total_flushes_(0) {}
virtual ~ResourceUpdateControllerTest() {
DebugScopedSetImplThreadAndMainThreadBlocked
impl_thread_and_main_thread_blocked(&proxy_);
resource_manager_->ClearAllMemory(resource_provider_.get());
}
public:
void OnFlush() {
// Check for back-to-back flushes.
EXPECT_EQ(0, num_consecutive_flushes_) << "Back-to-back flushes detected.";
num_dangling_uploads_ = 0;
num_consecutive_flushes_++;
num_total_flushes_++;
}
void OnUpload() {
// Check for too many consecutive uploads
if (num_total_uploads_ < full_upload_count_expected_) {
EXPECT_LT(num_dangling_uploads_, kFlushPeriodFull)
<< "Too many consecutive full uploads detected.";
} else {
EXPECT_LT(num_dangling_uploads_, kFlushPeriodPartial)
<< "Too many consecutive partial uploads detected.";
}
num_consecutive_flushes_ = 0;
num_dangling_uploads_++;
num_total_uploads_++;
}
bool IsQueryResultAvailable() {
if (!query_results_available_)
return false;
query_results_available_--;
return true;
}
protected:
virtual void SetUp() {
bitmap_.allocN32Pixels(300, 150);
for (int i = 0; i < 4; i++) {
textures_[i] = PrioritizedResource::Create(resource_manager_.get(),
gfx::Size(300, 150),
RGBA_8888);
textures_[i]->
set_request_priority(PriorityCalculator::VisiblePriority(true));
}
resource_manager_->PrioritizeTextures();
output_surface_ = FakeOutputSurface::Create3d(
scoped_ptr<TestWebGraphicsContext3D>(
new WebGraphicsContext3DForUploadTest(this)));
CHECK(output_surface_->BindToClient(&output_surface_client_));
shared_bitmap_manager_.reset(new TestSharedBitmapManager());
resource_provider_ = ResourceProvider::Create(
output_surface_.get(), shared_bitmap_manager_.get(), 0, false, 1,
false);
}
void AppendFullUploadsOfIndexedTextureToUpdateQueue(int count,
int texture_index) {
full_upload_count_expected_ += count;
total_upload_count_expected_ += count;
const gfx::Rect rect(0, 0, 300, 150);
const ResourceUpdate upload = ResourceUpdate::Create(
textures_[texture_index].get(), &bitmap_, rect, rect, gfx::Vector2d());
for (int i = 0; i < count; i++)
queue_->AppendFullUpload(upload);
}
void AppendFullUploadsToUpdateQueue(int count) {
AppendFullUploadsOfIndexedTextureToUpdateQueue(count, 0);
}
void AppendPartialUploadsOfIndexedTextureToUpdateQueue(int count,
int texture_index) {
partial_count_expected_ += count;
total_upload_count_expected_ += count;
const gfx::Rect rect(0, 0, 100, 100);
const ResourceUpdate upload = ResourceUpdate::Create(
textures_[texture_index].get(), &bitmap_, rect, rect, gfx::Vector2d());
for (int i = 0; i < count; i++)
queue_->AppendPartialUpload(upload);
}
void AppendPartialUploadsToUpdateQueue(int count) {
AppendPartialUploadsOfIndexedTextureToUpdateQueue(count, 0);
}
void SetMaxUploadCountPerUpdate(int count) {
max_upload_count_per_update_ = count;
}
void UpdateTextures() {
DebugScopedSetImplThreadAndMainThreadBlocked
impl_thread_and_main_thread_blocked(&proxy_);
scoped_ptr<ResourceUpdateController> update_controller =
ResourceUpdateController::Create(NULL,
proxy_.ImplThreadTaskRunner(),
queue_.Pass(),
resource_provider_.get());
update_controller->Finalize();
}
void MakeQueryResultAvailable() { query_results_available_++; }
protected:
// Classes required to interact and test the ResourceUpdateController
FakeProxy proxy_;
FakeOutputSurfaceClient output_surface_client_;
scoped_ptr<OutputSurface> output_surface_;
scoped_ptr<SharedBitmapManager> shared_bitmap_manager_;
scoped_ptr<ResourceProvider> resource_provider_;
scoped_ptr<ResourceUpdateQueue> queue_;
scoped_ptr<PrioritizedResource> textures_[4];
scoped_ptr<PrioritizedResourceManager> resource_manager_;
SkBitmap bitmap_;
int query_results_available_;
// Properties / expectations of this test
int full_upload_count_expected_;
int partial_count_expected_;
int total_upload_count_expected_;
int max_upload_count_per_update_;
// Dynamic properties of this test
int num_consecutive_flushes_;
int num_dangling_uploads_;
int num_total_uploads_;
int num_total_flushes_;
};
void WebGraphicsContext3DForUploadTest::flush() { test_->OnFlush(); }
void WebGraphicsContext3DForUploadTest::shallowFlushCHROMIUM() {
test_->OnFlush();
}
void WebGraphicsContext3DForUploadTest::texSubImage2D(GLenum target,
GLint level,
GLint xoffset,
GLint yoffset,
GLsizei width,
GLsizei height,
GLenum format,
GLenum type,
const void* pixels) {
test_->OnUpload();
}
void WebGraphicsContext3DForUploadTest::getQueryObjectuivEXT(GLuint id,
GLenum pname,
GLuint* params) {
if (pname == GL_QUERY_RESULT_AVAILABLE_EXT)
*params = test_->IsQueryResultAvailable();
}
// ZERO UPLOADS TESTS
TEST_F(ResourceUpdateControllerTest, ZeroUploads) {
AppendFullUploadsToUpdateQueue(0);
AppendPartialUploadsToUpdateQueue(0);
UpdateTextures();
EXPECT_EQ(0, num_total_flushes_);
EXPECT_EQ(0, num_total_uploads_);
}
// ONE UPLOAD TESTS
TEST_F(ResourceUpdateControllerTest, OneFullUpload) {
AppendFullUploadsToUpdateQueue(1);
AppendPartialUploadsToUpdateQueue(0);
UpdateTextures();
EXPECT_EQ(1, num_total_flushes_);
EXPECT_EQ(1, num_total_uploads_);
EXPECT_EQ(0, num_dangling_uploads_)
<< "Last upload wasn't followed by a flush.";
}
TEST_F(ResourceUpdateControllerTest, OnePartialUpload) {
AppendFullUploadsToUpdateQueue(0);
AppendPartialUploadsToUpdateQueue(1);
UpdateTextures();
EXPECT_EQ(1, num_total_flushes_);
EXPECT_EQ(1, num_total_uploads_);
EXPECT_EQ(0, num_dangling_uploads_)
<< "Last upload wasn't followed by a flush.";
}
TEST_F(ResourceUpdateControllerTest, OneFullOnePartialUpload) {
AppendFullUploadsToUpdateQueue(1);
AppendPartialUploadsToUpdateQueue(1);
UpdateTextures();
EXPECT_EQ(1, num_total_flushes_);
EXPECT_EQ(2, num_total_uploads_);
EXPECT_EQ(0, num_dangling_uploads_)
<< "Last upload wasn't followed by a flush.";
}
// This class of tests upload a number of textures that is a multiple
// of the flush period.
const int full_upload_flush_multipler = 7;
const int full_count = full_upload_flush_multipler * kFlushPeriodFull;
const int partial_upload_flush_multipler = 11;
const int partial_count =
partial_upload_flush_multipler * kFlushPeriodPartial;
TEST_F(ResourceUpdateControllerTest, ManyFullUploads) {
AppendFullUploadsToUpdateQueue(full_count);
AppendPartialUploadsToUpdateQueue(0);
UpdateTextures();
EXPECT_EQ(full_upload_flush_multipler, num_total_flushes_);
EXPECT_EQ(full_count, num_total_uploads_);
EXPECT_EQ(0, num_dangling_uploads_)
<< "Last upload wasn't followed by a flush.";
}
TEST_F(ResourceUpdateControllerTest, ManyPartialUploads) {
AppendFullUploadsToUpdateQueue(0);
AppendPartialUploadsToUpdateQueue(partial_count);
UpdateTextures();
EXPECT_EQ(partial_upload_flush_multipler, num_total_flushes_);
EXPECT_EQ(partial_count, num_total_uploads_);
EXPECT_EQ(0, num_dangling_uploads_)
<< "Last upload wasn't followed by a flush.";
}
TEST_F(ResourceUpdateControllerTest, ManyFullManyPartialUploads) {
AppendFullUploadsToUpdateQueue(full_count);
AppendPartialUploadsToUpdateQueue(partial_count);
UpdateTextures();
EXPECT_EQ(full_upload_flush_multipler + partial_upload_flush_multipler,
num_total_flushes_);
EXPECT_EQ(full_count + partial_count, num_total_uploads_);
EXPECT_EQ(0, num_dangling_uploads_)
<< "Last upload wasn't followed by a flush.";
}
class FakeResourceUpdateControllerClient
: public ResourceUpdateControllerClient {
public:
FakeResourceUpdateControllerClient() { Reset(); }
void Reset() { ready_to_finalize_called_ = false; }
bool ReadyToFinalizeCalled() const { return ready_to_finalize_called_; }
virtual void ReadyToFinalizeTextureUpdates() OVERRIDE {
ready_to_finalize_called_ = true;
}
protected:
bool ready_to_finalize_called_;
};
class FakeResourceUpdateController : public ResourceUpdateController {
public:
static scoped_ptr<FakeResourceUpdateController> Create(
ResourceUpdateControllerClient* client,
base::TestSimpleTaskRunner* task_runner,
scoped_ptr<ResourceUpdateQueue> queue,
ResourceProvider* resource_provider) {
return make_scoped_ptr(new FakeResourceUpdateController(
client, task_runner, queue.Pass(), resource_provider));
}
void SetNow(base::TimeTicks time) { now_ = time; }
base::TimeTicks Now() const { return now_; }
void SetUpdateTextureTime(base::TimeDelta time) {
update_textures_time_ = time;
}
virtual base::TimeTicks UpdateMoreTexturesCompletionTime() OVERRIDE {
size_t total_updates =
resource_provider_->NumBlockingUploads() + update_more_textures_size_;
return now_ + total_updates * update_textures_time_;
}
void SetUpdateMoreTexturesSize(size_t size) {
update_more_textures_size_ = size;
}
virtual size_t UpdateMoreTexturesSize() const OVERRIDE {
return update_more_textures_size_;
}
protected:
FakeResourceUpdateController(ResourceUpdateControllerClient* client,
base::TestSimpleTaskRunner* task_runner,
scoped_ptr<ResourceUpdateQueue> queue,
ResourceProvider* resource_provider)
: ResourceUpdateController(
client, task_runner, queue.Pass(), resource_provider),
resource_provider_(resource_provider),
update_more_textures_size_(0) {}
ResourceProvider* resource_provider_;
base::TimeTicks now_;
base::TimeDelta update_textures_time_;
size_t update_more_textures_size_;
};
static void RunPendingTask(base::TestSimpleTaskRunner* task_runner,
FakeResourceUpdateController* controller) {
EXPECT_TRUE(task_runner->HasPendingTask());
controller->SetNow(controller->Now() + task_runner->NextPendingTaskDelay());
task_runner->RunPendingTasks();
}
TEST_F(ResourceUpdateControllerTest, UpdateMoreTextures) {
FakeResourceUpdateControllerClient client;
scoped_refptr<base::TestSimpleTaskRunner> task_runner =
new base::TestSimpleTaskRunner;
SetMaxUploadCountPerUpdate(1);
AppendFullUploadsToUpdateQueue(3);
AppendPartialUploadsToUpdateQueue(0);
DebugScopedSetImplThreadAndMainThreadBlocked
impl_thread_and_main_thread_blocked(&proxy_);
scoped_ptr<FakeResourceUpdateController> controller(
FakeResourceUpdateController::Create(&client,
task_runner.get(),
queue_.Pass(),
resource_provider_.get()));
controller->SetNow(controller->Now() + base::TimeDelta::FromMilliseconds(1));
controller->SetUpdateTextureTime(base::TimeDelta::FromMilliseconds(100));
controller->SetUpdateMoreTexturesSize(1);
// Not enough time for any updates.
controller->PerformMoreUpdates(controller->Now() +
base::TimeDelta::FromMilliseconds(90));
EXPECT_FALSE(task_runner->HasPendingTask());
controller->SetUpdateTextureTime(base::TimeDelta::FromMilliseconds(100));
controller->SetUpdateMoreTexturesSize(1);
// Only enough time for 1 update.
controller->PerformMoreUpdates(controller->Now() +
base::TimeDelta::FromMilliseconds(120));
EXPECT_FALSE(task_runner->HasPendingTask());
EXPECT_EQ(1, num_total_uploads_);
// Complete one upload.
MakeQueryResultAvailable();
controller->SetUpdateTextureTime(base::TimeDelta::FromMilliseconds(100));
controller->SetUpdateMoreTexturesSize(1);
// Enough time for 2 updates.
controller->PerformMoreUpdates(controller->Now() +
base::TimeDelta::FromMilliseconds(220));
RunPendingTask(task_runner.get(), controller.get());
EXPECT_FALSE(task_runner->HasPendingTask());
EXPECT_TRUE(client.ReadyToFinalizeCalled());
EXPECT_EQ(3, num_total_uploads_);
}
TEST_F(ResourceUpdateControllerTest, NoMoreUpdates) {
FakeResourceUpdateControllerClient client;
scoped_refptr<base::TestSimpleTaskRunner> task_runner =
new base::TestSimpleTaskRunner;
SetMaxUploadCountPerUpdate(1);
AppendFullUploadsToUpdateQueue(2);
AppendPartialUploadsToUpdateQueue(0);
DebugScopedSetImplThreadAndMainThreadBlocked
impl_thread_and_main_thread_blocked(&proxy_);
scoped_ptr<FakeResourceUpdateController> controller(
FakeResourceUpdateController::Create(&client,
task_runner.get(),
queue_.Pass(),
resource_provider_.get()));
controller->SetNow(controller->Now() + base::TimeDelta::FromMilliseconds(1));
controller->SetUpdateTextureTime(base::TimeDelta::FromMilliseconds(100));
controller->SetUpdateMoreTexturesSize(1);
// Enough time for 3 updates but only 2 necessary.
controller->PerformMoreUpdates(controller->Now() +
base::TimeDelta::FromMilliseconds(310));
RunPendingTask(task_runner.get(), controller.get());
EXPECT_FALSE(task_runner->HasPendingTask());
EXPECT_TRUE(client.ReadyToFinalizeCalled());
EXPECT_EQ(2, num_total_uploads_);
client.Reset();
controller->SetUpdateTextureTime(base::TimeDelta::FromMilliseconds(100));
controller->SetUpdateMoreTexturesSize(1);
// Enough time for updates but no more updates left.
controller->PerformMoreUpdates(controller->Now() +
base::TimeDelta::FromMilliseconds(310));
// ReadyToFinalizeTextureUpdates should only be called once.
EXPECT_FALSE(task_runner->HasPendingTask());
EXPECT_FALSE(client.ReadyToFinalizeCalled());
EXPECT_EQ(2, num_total_uploads_);
}
TEST_F(ResourceUpdateControllerTest, UpdatesCompleteInFiniteTime) {
FakeResourceUpdateControllerClient client;
scoped_refptr<base::TestSimpleTaskRunner> task_runner =
new base::TestSimpleTaskRunner;
SetMaxUploadCountPerUpdate(1);
AppendFullUploadsToUpdateQueue(2);
AppendPartialUploadsToUpdateQueue(0);
DebugScopedSetImplThreadAndMainThreadBlocked
impl_thread_and_main_thread_blocked(&proxy_);
scoped_ptr<FakeResourceUpdateController> controller(
FakeResourceUpdateController::Create(&client,
task_runner.get(),
queue_.Pass(),
resource_provider_.get()));
controller->SetNow(controller->Now() + base::TimeDelta::FromMilliseconds(1));
controller->SetUpdateTextureTime(base::TimeDelta::FromMilliseconds(500));
controller->SetUpdateMoreTexturesSize(1);
for (int i = 0; i < 100; i++) {
if (client.ReadyToFinalizeCalled())
break;
// Not enough time for any updates.
controller->PerformMoreUpdates(controller->Now() +
base::TimeDelta::FromMilliseconds(400));
if (task_runner->HasPendingTask())
RunPendingTask(task_runner.get(), controller.get());
}
EXPECT_FALSE(task_runner->HasPendingTask());
EXPECT_TRUE(client.ReadyToFinalizeCalled());
EXPECT_EQ(2, num_total_uploads_);
}
} // namespace
} // namespace cc