// 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/prioritized_resource.h" #include <vector> #include "cc/resources/prioritized_resource_manager.h" #include "cc/resources/resource.h" #include "cc/resources/resource_provider.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/tiled_layer_test_common.h" #include "cc/trees/single_thread_proxy.h" // For DebugScopedSetImplThread #include "testing/gtest/include/gtest/gtest.h" namespace cc { class PrioritizedResourceTest : public testing::Test { public: PrioritizedResourceTest() : texture_size_(256, 256), texture_format_(RGBA_8888), output_surface_(FakeOutputSurface::Create3d()) { DebugScopedSetImplThread impl_thread(&proxy_); CHECK(output_surface_->BindToClient(&output_surface_client_)); resource_provider_ = ResourceProvider::Create(output_surface_.get(), NULL, 0, false, 1); } virtual ~PrioritizedResourceTest() { DebugScopedSetImplThread impl_thread(&proxy_); resource_provider_.reset(); } size_t TexturesMemorySize(size_t texture_count) { return Resource::MemorySizeBytes(texture_size_, texture_format_) * texture_count; } scoped_ptr<PrioritizedResourceManager> CreateManager(size_t max_textures) { scoped_ptr<PrioritizedResourceManager> manager = PrioritizedResourceManager::Create(&proxy_); manager->SetMaxMemoryLimitBytes(TexturesMemorySize(max_textures)); return manager.Pass(); } bool ValidateTexture(PrioritizedResource* texture, bool request_late) { ResourceManagerAssertInvariants(texture->resource_manager()); if (request_late) texture->RequestLate(); ResourceManagerAssertInvariants(texture->resource_manager()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); bool success = texture->can_acquire_backing_texture(); if (success) texture->AcquireBackingTexture(resource_provider()); return success; } void PrioritizeTexturesAndBackings( PrioritizedResourceManager* resource_manager) { resource_manager->PrioritizeTextures(); ResourceManagerUpdateBackingsPriorities(resource_manager); } void ResourceManagerUpdateBackingsPriorities( PrioritizedResourceManager* resource_manager) { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->PushTexturePrioritiesToBackings(); } ResourceProvider* resource_provider() { return resource_provider_.get(); } void ResourceManagerAssertInvariants( PrioritizedResourceManager* resource_manager) { #ifndef NDEBUG DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->AssertInvariants(); #endif } bool TextureBackingIsAbovePriorityCutoff(PrioritizedResource* texture) { return texture->backing()-> was_above_priority_cutoff_at_last_priority_update(); } size_t EvictedBackingCount(PrioritizedResourceManager* resource_manager) { return resource_manager->evicted_backings_.size(); } std::vector<unsigned> BackingResources( PrioritizedResourceManager* resource_manager) { std::vector<unsigned> resources; for (PrioritizedResourceManager::BackingList::iterator it = resource_manager->backings_.begin(); it != resource_manager->backings_.end(); ++it) resources.push_back((*it)->id()); return resources; } protected: FakeProxy proxy_; const gfx::Size texture_size_; const ResourceFormat texture_format_; FakeOutputSurfaceClient output_surface_client_; scoped_ptr<OutputSurface> output_surface_; scoped_ptr<ResourceProvider> resource_provider_; }; namespace { TEST_F(PrioritizedResourceTest, RequestTextureExceedingMaxLimit) { const size_t kMaxTextures = 8; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); // Create textures for double our memory limit. scoped_ptr<PrioritizedResource> textures[kMaxTextures * 2]; for (size_t i = 0; i < kMaxTextures * 2; ++i) textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); // Set decreasing priorities for (size_t i = 0; i < kMaxTextures * 2; ++i) textures[i]->set_request_priority(100 + i); // Only lower half should be available. PrioritizeTexturesAndBackings(resource_manager.get()); EXPECT_TRUE(ValidateTexture(textures[0].get(), false)); EXPECT_TRUE(ValidateTexture(textures[7].get(), false)); EXPECT_FALSE(ValidateTexture(textures[8].get(), false)); EXPECT_FALSE(ValidateTexture(textures[15].get(), false)); // Set increasing priorities for (size_t i = 0; i < kMaxTextures * 2; ++i) textures[i]->set_request_priority(100 - i); // Only upper half should be available. PrioritizeTexturesAndBackings(resource_manager.get()); EXPECT_FALSE(ValidateTexture(textures[0].get(), false)); EXPECT_FALSE(ValidateTexture(textures[7].get(), false)); EXPECT_TRUE(ValidateTexture(textures[8].get(), false)); EXPECT_TRUE(ValidateTexture(textures[15].get(), false)); EXPECT_EQ(TexturesMemorySize(kMaxTextures), resource_manager->MemoryAboveCutoffBytes()); EXPECT_LE(resource_manager->MemoryUseBytes(), resource_manager->MemoryAboveCutoffBytes()); EXPECT_EQ(TexturesMemorySize(2*kMaxTextures), resource_manager->MaxMemoryNeededBytes()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, ChangeMemoryLimits) { const size_t kMaxTextures = 8; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) { textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); } for (size_t i = 0; i < kMaxTextures; ++i) textures[i]->set_request_priority(100 + i); // Set max limit to 8 textures resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(8)); PrioritizeTexturesAndBackings(resource_manager.get()); for (size_t i = 0; i < kMaxTextures; ++i) ValidateTexture(textures[i].get(), false); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemory(resource_provider()); } EXPECT_EQ(TexturesMemorySize(8), resource_manager->MemoryAboveCutoffBytes()); EXPECT_LE(resource_manager->MemoryUseBytes(), resource_manager->MemoryAboveCutoffBytes()); // Set max limit to 5 textures resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(5)); PrioritizeTexturesAndBackings(resource_manager.get()); for (size_t i = 0; i < kMaxTextures; ++i) EXPECT_EQ(ValidateTexture(textures[i].get(), false), i < 5); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemory(resource_provider()); } EXPECT_EQ(TexturesMemorySize(5), resource_manager->MemoryAboveCutoffBytes()); EXPECT_LE(resource_manager->MemoryUseBytes(), resource_manager->MemoryAboveCutoffBytes()); EXPECT_EQ(TexturesMemorySize(kMaxTextures), resource_manager->MaxMemoryNeededBytes()); // Set max limit to 4 textures resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(4)); PrioritizeTexturesAndBackings(resource_manager.get()); for (size_t i = 0; i < kMaxTextures; ++i) EXPECT_EQ(ValidateTexture(textures[i].get(), false), i < 4); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemory(resource_provider()); } EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryAboveCutoffBytes()); EXPECT_LE(resource_manager->MemoryUseBytes(), resource_manager->MemoryAboveCutoffBytes()); EXPECT_EQ(TexturesMemorySize(kMaxTextures), resource_manager->MaxMemoryNeededBytes()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, ReduceWastedMemory) { const size_t kMaxTextures = 20; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) { textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); } for (size_t i = 0; i < kMaxTextures; ++i) textures[i]->set_request_priority(100 + i); // Set the memory limit to the max number of textures. resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(kMaxTextures)); PrioritizeTexturesAndBackings(resource_manager.get()); // Create backings and textures for all of the textures. for (size_t i = 0; i < kMaxTextures; ++i) { ValidateTexture(textures[i].get(), false); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); uint8_t image[4] = {0}; textures[i]->SetPixels(resource_provider_.get(), image, gfx::Rect(1, 1), gfx::Rect(1, 1), gfx::Vector2d()); } } { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemory(resource_provider()); } // 20 textures have backings allocated. EXPECT_EQ(TexturesMemorySize(20), resource_manager->MemoryUseBytes()); // Destroy one texture, not enough is wasted to cause cleanup. textures[0] = scoped_ptr<PrioritizedResource>(); PrioritizeTexturesAndBackings(resource_manager.get()); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->UpdateBackingsState(resource_provider()); resource_manager->ReduceWastedMemory(resource_provider()); } EXPECT_EQ(TexturesMemorySize(20), resource_manager->MemoryUseBytes()); // Destroy half the textures, leaving behind the backings. Now a cleanup // should happen. for (size_t i = 0; i < kMaxTextures / 2; ++i) textures[i] = scoped_ptr<PrioritizedResource>(); PrioritizeTexturesAndBackings(resource_manager.get()); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->UpdateBackingsState(resource_provider()); resource_manager->ReduceWastedMemory(resource_provider()); } EXPECT_GT(TexturesMemorySize(20), resource_manager->MemoryUseBytes()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, InUseNotWastedMemory) { const size_t kMaxTextures = 20; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) { textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); } for (size_t i = 0; i < kMaxTextures; ++i) textures[i]->set_request_priority(100 + i); // Set the memory limit to the max number of textures. resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(kMaxTextures)); PrioritizeTexturesAndBackings(resource_manager.get()); // Create backings and textures for all of the textures. for (size_t i = 0; i < kMaxTextures; ++i) { ValidateTexture(textures[i].get(), false); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); uint8_t image[4] = {0}; textures[i]->SetPixels(resource_provider_.get(), image, gfx::Rect(1, 1), gfx::Rect(1, 1), gfx::Vector2d()); } } { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemory(resource_provider()); } // 20 textures have backings allocated. EXPECT_EQ(TexturesMemorySize(20), resource_manager->MemoryUseBytes()); // Send half the textures to a parent compositor. ResourceProvider::ResourceIdArray to_send; TransferableResourceArray transferable; for (size_t i = 0; i < kMaxTextures / 2; ++i) to_send.push_back(textures[i]->resource_id()); resource_provider_->PrepareSendToParent(to_send, &transferable); // Destroy half the textures, leaving behind the backings. The backings are // sent to a parent compositor though, so they should not be considered wasted // and a cleanup should not happen. for (size_t i = 0; i < kMaxTextures / 2; ++i) textures[i] = scoped_ptr<PrioritizedResource>(); PrioritizeTexturesAndBackings(resource_manager.get()); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->UpdateBackingsState(resource_provider()); resource_manager->ReduceWastedMemory(resource_provider()); } EXPECT_EQ(TexturesMemorySize(20), resource_manager->MemoryUseBytes()); // Receive the textures back from the parent compositor. Now a cleanup should // happen. ReturnedResourceArray returns; TransferableResource::ReturnResources(transferable, &returns); resource_provider_->ReceiveReturnsFromParent(returns); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->UpdateBackingsState(resource_provider()); resource_manager->ReduceWastedMemory(resource_provider()); } EXPECT_GT(TexturesMemorySize(20), resource_manager->MemoryUseBytes()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, ChangePriorityCutoff) { const size_t kMaxTextures = 8; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) { textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); } for (size_t i = 0; i < kMaxTextures; ++i) textures[i]->set_request_priority(100 + i); // Set the cutoff to drop two textures. Try to request_late on all textures, // and make sure that request_late doesn't work on a texture with equal // priority to the cutoff. resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(8)); resource_manager->SetExternalPriorityCutoff(106); PrioritizeTexturesAndBackings(resource_manager.get()); for (size_t i = 0; i < kMaxTextures; ++i) EXPECT_EQ(ValidateTexture(textures[i].get(), true), i < 6); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemory(resource_provider()); } EXPECT_EQ(TexturesMemorySize(6), resource_manager->MemoryAboveCutoffBytes()); EXPECT_LE(resource_manager->MemoryUseBytes(), resource_manager->MemoryAboveCutoffBytes()); // Set the cutoff to drop two more textures. resource_manager->SetExternalPriorityCutoff(104); PrioritizeTexturesAndBackings(resource_manager.get()); for (size_t i = 0; i < kMaxTextures; ++i) EXPECT_EQ(ValidateTexture(textures[i].get(), false), i < 4); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemory(resource_provider()); } EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryAboveCutoffBytes()); // Do a one-time eviction for one more texture based on priority cutoff resource_manager->UnlinkAndClearEvictedBackings(); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemoryOnImplThread( TexturesMemorySize(8), 104, resource_provider()); EXPECT_EQ(0u, EvictedBackingCount(resource_manager.get())); resource_manager->ReduceMemoryOnImplThread( TexturesMemorySize(8), 103, resource_provider()); EXPECT_EQ(1u, EvictedBackingCount(resource_manager.get())); } resource_manager->UnlinkAndClearEvictedBackings(); EXPECT_EQ(TexturesMemorySize(3), resource_manager->MemoryUseBytes()); // Re-allocate the the texture after the one-time drop. PrioritizeTexturesAndBackings(resource_manager.get()); for (size_t i = 0; i < kMaxTextures; ++i) EXPECT_EQ(ValidateTexture(textures[i].get(), false), i < 4); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemory(resource_provider()); } EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryAboveCutoffBytes()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, EvictingTexturesInParent) { const size_t kMaxTextures = 8; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); scoped_ptr<PrioritizedResource> textures[kMaxTextures]; unsigned texture_resource_ids[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) { textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); textures[i]->set_request_priority(100 + i); } PrioritizeTexturesAndBackings(resource_manager.get()); for (size_t i = 0; i < kMaxTextures; ++i) { EXPECT_TRUE(ValidateTexture(textures[i].get(), true)); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); uint8_t image[4] = {0}; textures[i]->SetPixels(resource_provider_.get(), image, gfx::Rect(1, 1), gfx::Rect(1, 1), gfx::Vector2d()); } } { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemory(resource_provider()); } EXPECT_EQ(TexturesMemorySize(8), resource_manager->MemoryAboveCutoffBytes()); for (size_t i = 0; i < 8; ++i) texture_resource_ids[i] = textures[i]->resource_id(); // Evict four textures. It will be the last four. { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemoryOnImplThread( TexturesMemorySize(4), 200, resource_provider()); EXPECT_EQ(4u, EvictedBackingCount(resource_manager.get())); // The last four backings are evicted. std::vector<unsigned> remaining = BackingResources(resource_manager.get()); EXPECT_TRUE(std::find(remaining.begin(), remaining.end(), texture_resource_ids[0]) != remaining.end()); EXPECT_TRUE(std::find(remaining.begin(), remaining.end(), texture_resource_ids[1]) != remaining.end()); EXPECT_TRUE(std::find(remaining.begin(), remaining.end(), texture_resource_ids[2]) != remaining.end()); EXPECT_TRUE(std::find(remaining.begin(), remaining.end(), texture_resource_ids[3]) != remaining.end()); } resource_manager->UnlinkAndClearEvictedBackings(); EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryUseBytes()); // Re-allocate the the texture after the eviction. PrioritizeTexturesAndBackings(resource_manager.get()); for (size_t i = 0; i < kMaxTextures; ++i) { EXPECT_TRUE(ValidateTexture(textures[i].get(), true)); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); uint8_t image[4] = {0}; textures[i]->SetPixels(resource_provider_.get(), image, gfx::Rect(1, 1), gfx::Rect(1, 1), gfx::Vector2d()); } } { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemory(resource_provider()); } EXPECT_EQ(TexturesMemorySize(8), resource_manager->MemoryAboveCutoffBytes()); // Send the last two of the textures to a parent compositor. ResourceProvider::ResourceIdArray to_send; TransferableResourceArray transferable; for (size_t i = 6; i < 8; ++i) to_send.push_back(textures[i]->resource_id()); resource_provider_->PrepareSendToParent(to_send, &transferable); // Set the last two textures to be tied for prioity with the two // before them. Being sent to the parent will break the tie. textures[4]->set_request_priority(100 + 4); textures[5]->set_request_priority(100 + 5); textures[6]->set_request_priority(100 + 4); textures[7]->set_request_priority(100 + 5); for (size_t i = 0; i < 8; ++i) texture_resource_ids[i] = textures[i]->resource_id(); // Drop all the textures. Now we have backings that can be recycled. for (size_t i = 0; i < 8; ++i) textures[0].reset(); PrioritizeTexturesAndBackings(resource_manager.get()); // The next commit finishes. { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->UpdateBackingsState(resource_provider()); } // Evict four textures. It would be the last four again, except that 2 of them // are sent to the parent, so they are evicted last. { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ReduceMemoryOnImplThread( TexturesMemorySize(4), 200, resource_provider()); EXPECT_EQ(4u, EvictedBackingCount(resource_manager.get())); // The last 2 backings remain this time. std::vector<unsigned> remaining = BackingResources(resource_manager.get()); EXPECT_TRUE(std::find(remaining.begin(), remaining.end(), texture_resource_ids[6]) == remaining.end()); EXPECT_TRUE(std::find(remaining.begin(), remaining.end(), texture_resource_ids[7]) == remaining.end()); } resource_manager->UnlinkAndClearEvictedBackings(); EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryUseBytes()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, ResourceManagerPartialUpdateTextures) { const size_t kMaxTextures = 4; const size_t kNumTextures = 4; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); scoped_ptr<PrioritizedResource> textures[kNumTextures]; scoped_ptr<PrioritizedResource> more_textures[kNumTextures]; for (size_t i = 0; i < kNumTextures; ++i) { textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); more_textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); } for (size_t i = 0; i < kNumTextures; ++i) textures[i]->set_request_priority(200 + i); PrioritizeTexturesAndBackings(resource_manager.get()); // Allocate textures which are currently high priority. EXPECT_TRUE(ValidateTexture(textures[0].get(), false)); EXPECT_TRUE(ValidateTexture(textures[1].get(), false)); EXPECT_TRUE(ValidateTexture(textures[2].get(), false)); EXPECT_TRUE(ValidateTexture(textures[3].get(), false)); EXPECT_TRUE(textures[0]->have_backing_texture()); EXPECT_TRUE(textures[1]->have_backing_texture()); EXPECT_TRUE(textures[2]->have_backing_texture()); EXPECT_TRUE(textures[3]->have_backing_texture()); for (size_t i = 0; i < kNumTextures; ++i) more_textures[i]->set_request_priority(100 + i); PrioritizeTexturesAndBackings(resource_manager.get()); // Textures are now below cutoff. EXPECT_FALSE(ValidateTexture(textures[0].get(), false)); EXPECT_FALSE(ValidateTexture(textures[1].get(), false)); EXPECT_FALSE(ValidateTexture(textures[2].get(), false)); EXPECT_FALSE(ValidateTexture(textures[3].get(), false)); // But they are still valid to use. EXPECT_TRUE(textures[0]->have_backing_texture()); EXPECT_TRUE(textures[1]->have_backing_texture()); EXPECT_TRUE(textures[2]->have_backing_texture()); EXPECT_TRUE(textures[3]->have_backing_texture()); // Higher priority textures are finally needed. EXPECT_TRUE(ValidateTexture(more_textures[0].get(), false)); EXPECT_TRUE(ValidateTexture(more_textures[1].get(), false)); EXPECT_TRUE(ValidateTexture(more_textures[2].get(), false)); EXPECT_TRUE(ValidateTexture(more_textures[3].get(), false)); // Lower priority have been fully evicted. EXPECT_FALSE(textures[0]->have_backing_texture()); EXPECT_FALSE(textures[1]->have_backing_texture()); EXPECT_FALSE(textures[2]->have_backing_texture()); EXPECT_FALSE(textures[3]->have_backing_texture()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, ResourceManagerPrioritiesAreEqual) { const size_t kMaxTextures = 16; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) { textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); } // All 16 textures have the same priority except 2 higher priority. for (size_t i = 0; i < kMaxTextures; ++i) textures[i]->set_request_priority(100); textures[0]->set_request_priority(99); textures[1]->set_request_priority(99); // Set max limit to 8 textures resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(8)); PrioritizeTexturesAndBackings(resource_manager.get()); // The two high priority textures should be available, others should not. for (size_t i = 0; i < 2; ++i) EXPECT_TRUE(ValidateTexture(textures[i].get(), false)); for (size_t i = 2; i < kMaxTextures; ++i) EXPECT_FALSE(ValidateTexture(textures[i].get(), false)); EXPECT_EQ(TexturesMemorySize(2), resource_manager->MemoryAboveCutoffBytes()); EXPECT_LE(resource_manager->MemoryUseBytes(), resource_manager->MemoryAboveCutoffBytes()); // Manually reserving textures should only succeed on the higher priority // textures, and on remaining textures up to the memory limit. for (size_t i = 0; i < 8; i++) EXPECT_TRUE(ValidateTexture(textures[i].get(), true)); for (size_t i = 9; i < kMaxTextures; i++) EXPECT_FALSE(ValidateTexture(textures[i].get(), true)); EXPECT_EQ(TexturesMemorySize(8), resource_manager->MemoryAboveCutoffBytes()); EXPECT_LE(resource_manager->MemoryUseBytes(), resource_manager->MemoryAboveCutoffBytes()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, ResourceManagerDestroyedFirst) { scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(1); scoped_ptr<PrioritizedResource> texture = resource_manager->CreateTexture(texture_size_, texture_format_); // Texture is initially invalid, but it will become available. EXPECT_FALSE(texture->have_backing_texture()); texture->set_request_priority(100); PrioritizeTexturesAndBackings(resource_manager.get()); EXPECT_TRUE(ValidateTexture(texture.get(), false)); EXPECT_TRUE(texture->can_acquire_backing_texture()); EXPECT_TRUE(texture->have_backing_texture()); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } resource_manager.reset(); EXPECT_FALSE(texture->can_acquire_backing_texture()); EXPECT_FALSE(texture->have_backing_texture()); } TEST_F(PrioritizedResourceTest, TextureMovedToNewManager) { scoped_ptr<PrioritizedResourceManager> resource_manager_one = CreateManager(1); scoped_ptr<PrioritizedResourceManager> resource_manager_two = CreateManager(1); scoped_ptr<PrioritizedResource> texture = resource_manager_one->CreateTexture(texture_size_, texture_format_); // Texture is initially invalid, but it will become available. EXPECT_FALSE(texture->have_backing_texture()); texture->set_request_priority(100); PrioritizeTexturesAndBackings(resource_manager_one.get()); EXPECT_TRUE(ValidateTexture(texture.get(), false)); EXPECT_TRUE(texture->can_acquire_backing_texture()); EXPECT_TRUE(texture->have_backing_texture()); texture->SetTextureManager(NULL); { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager_one->ClearAllMemory(resource_provider()); } resource_manager_one.reset(); EXPECT_FALSE(texture->can_acquire_backing_texture()); EXPECT_FALSE(texture->have_backing_texture()); texture->SetTextureManager(resource_manager_two.get()); PrioritizeTexturesAndBackings(resource_manager_two.get()); EXPECT_TRUE(ValidateTexture(texture.get(), false)); EXPECT_TRUE(texture->can_acquire_backing_texture()); EXPECT_TRUE(texture->have_backing_texture()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager_two->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, RenderSurfacesReduceMemoryAvailableOutsideRootSurface) { const size_t kMaxTextures = 8; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); // Half of the memory is taken by surfaces (with high priority place-holder) scoped_ptr<PrioritizedResource> render_surface_place_holder = resource_manager->CreateTexture(texture_size_, texture_format_); render_surface_place_holder->SetToSelfManagedMemoryPlaceholder( TexturesMemorySize(4)); render_surface_place_holder->set_request_priority( PriorityCalculator::RenderSurfacePriority()); // Create textures to fill our memory limit. scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) { textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); } // Set decreasing non-visible priorities outside root surface. for (size_t i = 0; i < kMaxTextures; ++i) textures[i]->set_request_priority(100 + i); // Only lower half should be available. PrioritizeTexturesAndBackings(resource_manager.get()); EXPECT_TRUE(ValidateTexture(textures[0].get(), false)); EXPECT_TRUE(ValidateTexture(textures[3].get(), false)); EXPECT_FALSE(ValidateTexture(textures[4].get(), false)); EXPECT_FALSE(ValidateTexture(textures[7].get(), false)); // Set increasing non-visible priorities outside root surface. for (size_t i = 0; i < kMaxTextures; ++i) textures[i]->set_request_priority(100 - i); // Only upper half should be available. PrioritizeTexturesAndBackings(resource_manager.get()); EXPECT_FALSE(ValidateTexture(textures[0].get(), false)); EXPECT_FALSE(ValidateTexture(textures[3].get(), false)); EXPECT_TRUE(ValidateTexture(textures[4].get(), false)); EXPECT_TRUE(ValidateTexture(textures[7].get(), false)); EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryAboveCutoffBytes()); EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryForSelfManagedTextures()); EXPECT_LE(resource_manager->MemoryUseBytes(), resource_manager->MemoryAboveCutoffBytes()); EXPECT_EQ(TexturesMemorySize(8), resource_manager->MaxMemoryNeededBytes()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, RenderSurfacesReduceMemoryAvailableForRequestLate) { const size_t kMaxTextures = 8; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); // Half of the memory is taken by surfaces (with high priority place-holder) scoped_ptr<PrioritizedResource> render_surface_place_holder = resource_manager->CreateTexture(texture_size_, texture_format_); render_surface_place_holder->SetToSelfManagedMemoryPlaceholder( TexturesMemorySize(4)); render_surface_place_holder->set_request_priority( PriorityCalculator::RenderSurfacePriority()); // Create textures to fill our memory limit. scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) { textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); } // Set equal priorities. for (size_t i = 0; i < kMaxTextures; ++i) textures[i]->set_request_priority(100); // The first four to be requested late will be available. PrioritizeTexturesAndBackings(resource_manager.get()); for (unsigned i = 0; i < kMaxTextures; ++i) EXPECT_FALSE(ValidateTexture(textures[i].get(), false)); for (unsigned i = 0; i < kMaxTextures; i += 2) EXPECT_TRUE(ValidateTexture(textures[i].get(), true)); for (unsigned i = 1; i < kMaxTextures; i += 2) EXPECT_FALSE(ValidateTexture(textures[i].get(), true)); EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryAboveCutoffBytes()); EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryForSelfManagedTextures()); EXPECT_LE(resource_manager->MemoryUseBytes(), resource_manager->MemoryAboveCutoffBytes()); EXPECT_EQ(TexturesMemorySize(8), resource_manager->MaxMemoryNeededBytes()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, WhenRenderSurfaceNotAvailableTexturesAlsoNotAvailable) { const size_t kMaxTextures = 8; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); // Half of the memory is taken by surfaces (with high priority place-holder) scoped_ptr<PrioritizedResource> render_surface_place_holder = resource_manager->CreateTexture(texture_size_, texture_format_); render_surface_place_holder->SetToSelfManagedMemoryPlaceholder( TexturesMemorySize(4)); render_surface_place_holder->set_request_priority( PriorityCalculator::RenderSurfacePriority()); // Create textures to fill our memory limit. scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); // Set 6 visible textures in the root surface, and 2 in a child surface. for (size_t i = 0; i < 6; ++i) { textures[i]-> set_request_priority(PriorityCalculator::VisiblePriority(true)); } for (size_t i = 6; i < 8; ++i) { textures[i]-> set_request_priority(PriorityCalculator::VisiblePriority(false)); } PrioritizeTexturesAndBackings(resource_manager.get()); // Unable to request_late textures in the child surface. EXPECT_FALSE(ValidateTexture(textures[6].get(), true)); EXPECT_FALSE(ValidateTexture(textures[7].get(), true)); // Root surface textures are valid. for (size_t i = 0; i < 6; ++i) EXPECT_TRUE(ValidateTexture(textures[i].get(), false)); EXPECT_EQ(TexturesMemorySize(6), resource_manager->MemoryAboveCutoffBytes()); EXPECT_EQ(TexturesMemorySize(2), resource_manager->MemoryForSelfManagedTextures()); EXPECT_LE(resource_manager->MemoryUseBytes(), resource_manager->MemoryAboveCutoffBytes()); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, RequestLateBackingsSorting) { const size_t kMaxTextures = 8; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(kMaxTextures)); // Create textures to fill our memory limit. scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); // Set equal priorities, and allocate backings for all textures. for (size_t i = 0; i < kMaxTextures; ++i) textures[i]->set_request_priority(100); PrioritizeTexturesAndBackings(resource_manager.get()); for (unsigned i = 0; i < kMaxTextures; ++i) EXPECT_TRUE(ValidateTexture(textures[i].get(), false)); // Drop the memory limit and prioritize (none will be above the threshold, // but they still have backings because ReduceMemory hasn't been called). resource_manager->SetMaxMemoryLimitBytes( TexturesMemorySize(kMaxTextures / 2)); PrioritizeTexturesAndBackings(resource_manager.get()); // Push half of them back over the limit. for (size_t i = 0; i < kMaxTextures; i += 2) EXPECT_TRUE(textures[i]->RequestLate()); // Push the priorities to the backings array and sort the backings array ResourceManagerUpdateBackingsPriorities(resource_manager.get()); // Assert that the backings list be sorted with the below-limit backings // before the above-limit backings. ResourceManagerAssertInvariants(resource_manager.get()); // Make sure that we have backings for all of the textures. for (size_t i = 0; i < kMaxTextures; ++i) EXPECT_TRUE(textures[i]->have_backing_texture()); // Make sure that only the request_late textures are above the priority // cutoff for (size_t i = 0; i < kMaxTextures; i += 2) EXPECT_TRUE(TextureBackingIsAbovePriorityCutoff(textures[i].get())); for (size_t i = 1; i < kMaxTextures; i += 2) EXPECT_FALSE(TextureBackingIsAbovePriorityCutoff(textures[i].get())); DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } TEST_F(PrioritizedResourceTest, ClearUploadsToEvictedResources) { const size_t kMaxTextures = 4; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(kMaxTextures)); // Create textures to fill our memory limit. scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); // Set equal priorities, and allocate backings for all textures. for (size_t i = 0; i < kMaxTextures; ++i) textures[i]->set_request_priority(100); PrioritizeTexturesAndBackings(resource_manager.get()); for (unsigned i = 0; i < kMaxTextures; ++i) EXPECT_TRUE(ValidateTexture(textures[i].get(), false)); ResourceUpdateQueue queue; DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); for (size_t i = 0; i < kMaxTextures; ++i) { const ResourceUpdate upload = ResourceUpdate::Create( textures[i].get(), NULL, gfx::Rect(), gfx::Rect(), gfx::Vector2d()); queue.AppendFullUpload(upload); } // Make sure that we have backings for all of the textures. for (size_t i = 0; i < kMaxTextures; ++i) EXPECT_TRUE(textures[i]->have_backing_texture()); queue.ClearUploadsToEvictedResources(); EXPECT_EQ(4u, queue.FullUploadSize()); resource_manager->ReduceMemoryOnImplThread( TexturesMemorySize(1), PriorityCalculator::AllowEverythingCutoff(), resource_provider()); queue.ClearUploadsToEvictedResources(); EXPECT_EQ(1u, queue.FullUploadSize()); resource_manager->ReduceMemoryOnImplThread( 0, PriorityCalculator::AllowEverythingCutoff(), resource_provider()); queue.ClearUploadsToEvictedResources(); EXPECT_EQ(0u, queue.FullUploadSize()); } TEST_F(PrioritizedResourceTest, UsageStatistics) { const size_t kMaxTextures = 5; scoped_ptr<PrioritizedResourceManager> resource_manager = CreateManager(kMaxTextures); scoped_ptr<PrioritizedResource> textures[kMaxTextures]; for (size_t i = 0; i < kMaxTextures; ++i) { textures[i] = resource_manager->CreateTexture(texture_size_, texture_format_); } textures[0]-> set_request_priority(PriorityCalculator::AllowVisibleOnlyCutoff() - 1); textures[1]-> set_request_priority(PriorityCalculator::AllowVisibleOnlyCutoff()); textures[2]->set_request_priority( PriorityCalculator::AllowVisibleAndNearbyCutoff() - 1); textures[3]-> set_request_priority(PriorityCalculator::AllowVisibleAndNearbyCutoff()); textures[4]->set_request_priority( PriorityCalculator::AllowVisibleAndNearbyCutoff() + 1); // Set max limit to 2 textures. resource_manager->SetMaxMemoryLimitBytes(TexturesMemorySize(2)); PrioritizeTexturesAndBackings(resource_manager.get()); // The first two textures should be available, others should not. for (size_t i = 0; i < 2; ++i) EXPECT_TRUE(ValidateTexture(textures[i].get(), false)); for (size_t i = 2; i < kMaxTextures; ++i) EXPECT_FALSE(ValidateTexture(textures[i].get(), false)); // Validate the statistics. { DebugScopedSetImplThread impl_thread(&proxy_); EXPECT_EQ(TexturesMemorySize(2), resource_manager->MemoryUseBytes()); EXPECT_EQ(TexturesMemorySize(1), resource_manager->MemoryVisibleBytes()); EXPECT_EQ(TexturesMemorySize(3), resource_manager->MemoryVisibleAndNearbyBytes()); } // Re-prioritize the textures, but do not push the values to backings. textures[0]-> set_request_priority(PriorityCalculator::AllowVisibleOnlyCutoff() - 1); textures[1]-> set_request_priority(PriorityCalculator::AllowVisibleOnlyCutoff() - 1); textures[2]-> set_request_priority(PriorityCalculator::AllowVisibleOnlyCutoff() - 1); textures[3]->set_request_priority( PriorityCalculator::AllowVisibleAndNearbyCutoff() - 1); textures[4]-> set_request_priority(PriorityCalculator::AllowVisibleAndNearbyCutoff()); resource_manager->PrioritizeTextures(); // Verify that we still see the old values. { DebugScopedSetImplThread impl_thread(&proxy_); EXPECT_EQ(TexturesMemorySize(2), resource_manager->MemoryUseBytes()); EXPECT_EQ(TexturesMemorySize(1), resource_manager->MemoryVisibleBytes()); EXPECT_EQ(TexturesMemorySize(3), resource_manager->MemoryVisibleAndNearbyBytes()); } // Push priorities to backings, and verify we see the new values. { DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->PushTexturePrioritiesToBackings(); EXPECT_EQ(TexturesMemorySize(2), resource_manager->MemoryUseBytes()); EXPECT_EQ(TexturesMemorySize(3), resource_manager->MemoryVisibleBytes()); EXPECT_EQ(TexturesMemorySize(4), resource_manager->MemoryVisibleAndNearbyBytes()); } DebugScopedSetImplThreadAndMainThreadBlocked impl_thread_and_main_thread_blocked(&proxy_); resource_manager->ClearAllMemory(resource_provider()); } } // namespace } // namespace cc