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