// 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