#include "gpu/command_buffer/service/service_discardable_manager.h"
#include <memory>
#include "base/memory/raw_ptr.h"
#include "gpu/command_buffer/client/client_test_helper.h"
#include "gpu/command_buffer/service/gles2_cmd_decoder_mock.h"
#include "gpu/command_buffer/service/gpu_service_test.h"
#include "gpu/command_buffer/service/gpu_tracer.h"
#include "gpu/command_buffer/service/mailbox_manager_impl.h"
#include "gpu/command_buffer/service/memory_tracking.h"
#include "gpu/command_buffer/service/mocks.h"
#include "gpu/command_buffer/service/shared_image/shared_image_manager.h"
#include "gpu/command_buffer/service/test_helper.h"
#include "gpu/command_buffer/service/texture_manager.h"
#include "gpu/config/gpu_preferences.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gl/gl_mock.h"
#include "ui/gl/gl_switches.h"
using ::testing::Pointee;
using ::testing::_;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::InSequence;
namespace gpu {
namespace gles2 {
namespace {
void CreateLockedHandlesForTesting(
std::unique_ptr<ServiceDiscardableHandle>* service_handle,
std::unique_ptr<ClientDiscardableHandle>* client_handle) {
const size_t kShmemSize = sizeof(uint32_t);
base::UnsafeSharedMemoryRegion shared_mem =
base::UnsafeSharedMemoryRegion::Create(kShmemSize);
base::WritableSharedMemoryMapping shared_mem_mapping = shared_mem.Map();
scoped_refptr<gpu::Buffer> buffer = MakeBufferFromSharedMemory(
std::move(shared_mem), std::move(shared_mem_mapping));
*client_handle = std::make_unique<ClientDiscardableHandle>(buffer, 0, 0);
*service_handle = std::make_unique<ServiceDiscardableHandle>(buffer, 0, 0);
}
ServiceDiscardableHandle CreateLockedServiceHandleForTesting() {
std::unique_ptr<ServiceDiscardableHandle> service_handle;
std::unique_ptr<ClientDiscardableHandle> client_handle;
CreateLockedHandlesForTesting(&service_handle, &client_handle);
return *service_handle;
}
class MockDestructionObserver : public TextureManager::DestructionObserver {
public:
MOCK_METHOD1(OnTextureManagerDestroying, void(TextureManager* manager));
MOCK_METHOD1(OnTextureRefDestroying, void(TextureRef* ref));
};
static const uint32_t kSmallTextureDim = 16;
static const size_t kSmallTextureSize = 4 * kSmallTextureDim * kSmallTextureDim;
}
class ServiceDiscardableManagerTest : public GpuServiceTest {
public:
ServiceDiscardableManagerTest() : discardable_manager_(GpuPreferences()) {}
~ServiceDiscardableManagerTest() override = default;
protected:
void SetUp() override {
GpuServiceTest::SetUp();
decoder_ = std::make_unique<MockGLES2Decoder>(
&client_, &command_buffer_service_, &outputter_);
feature_info_ = new FeatureInfo();
context_group_ = scoped_refptr<ContextGroup>(new ContextGroup(
gpu_preferences_, false,
&mailbox_manager_, nullptr,
nullptr,
nullptr, feature_info_,
false, nullptr,
GpuFeatureInfo(), &discardable_manager_,
nullptr, &shared_image_manager_));
TestHelper::SetupContextGroupInitExpectations(
gl_.get(), DisallowedFeatures(), "", "", CONTEXT_TYPE_OPENGLES2, false);
context_group_->Initialize(decoder_.get(), CONTEXT_TYPE_OPENGLES2,
DisallowedFeatures());
texture_manager_ = context_group_->texture_manager();
texture_manager_->AddObserver(&destruction_observer_);
}
void TearDown() override {
EXPECT_CALL(destruction_observer_, OnTextureManagerDestroying(_))
.RetiresOnSaturation();
EXPECT_CALL(*gl_, DeleteTextures(TextureManager::kNumDefaultTextures, _));
context_group_->Destroy(decoder_.get(), true);
context_group_ = nullptr;
EXPECT_EQ(0u, discardable_manager_.NumCacheEntriesForTesting());
GpuServiceTest::TearDown();
}
void ExpectUnlockedTextureDeletion(uint32_t client_id) {
TextureRef* ref = discardable_manager_.UnlockedTextureRefForTesting(
client_id, texture_manager_);
ExpectTextureRefDeletion(ref);
}
void ExpectTextureDeletion(uint32_t client_id) {
TextureRef* ref = texture_manager_->GetTexture(client_id);
ExpectTextureRefDeletion(ref);
}
void ExpectTextureRefDeletion(TextureRef* ref) {
EXPECT_NE(nullptr, ref);
ref->AddObserver();
EXPECT_CALL(destruction_observer_, OnTextureRefDestroying(ref))
.WillOnce(Invoke([](TextureRef* ref) { ref->RemoveObserver(); }));
EXPECT_CALL(*gl_, DeleteTextures(1, Pointee(ref->service_id())))
.RetiresOnSaturation();
}
MailboxManagerImpl mailbox_manager_;
TraceOutputter outputter_;
ServiceDiscardableManager discardable_manager_;
SharedImageManager shared_image_manager_;
GpuPreferences gpu_preferences_;
scoped_refptr<FeatureInfo> feature_info_;
MockDestructionObserver destruction_observer_;
raw_ptr<TextureManager> texture_manager_;
FakeCommandBufferServiceBase command_buffer_service_;
FakeDecoderClient client_;
std::unique_ptr<MockGLES2Decoder> decoder_;
scoped_refptr<gles2::ContextGroup> context_group_;
};
TEST_F(ServiceDiscardableManagerTest, BasicUsage) {
const GLuint kClientId = 1;
const GLuint kServiceId = 2;
texture_manager_->CreateTexture(kClientId, kServiceId);
auto handle = CreateLockedServiceHandleForTesting();
discardable_manager_.InsertLockedTexture(kClientId, kSmallTextureSize,
texture_manager_, handle);
EXPECT_EQ(1u, discardable_manager_.NumCacheEntriesForTesting());
EXPECT_TRUE(discardable_manager_.IsEntryLockedForTesting(kClientId,
texture_manager_));
EXPECT_NE(nullptr, texture_manager_->GetTexture(kClientId));
gles2::TextureRef* texture_to_unbind;
EXPECT_TRUE(discardable_manager_.UnlockTexture(kClientId, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_FALSE(discardable_manager_.IsEntryLockedForTesting(kClientId,
texture_manager_));
EXPECT_EQ(nullptr, texture_manager_->GetTexture(kClientId));
discardable_manager_.LockTexture(kClientId, texture_manager_);
EXPECT_NE(nullptr, texture_manager_->GetTexture(kClientId));
ExpectTextureDeletion(kClientId);
texture_manager_->RemoveTexture(kClientId);
EXPECT_EQ(0u, discardable_manager_.NumCacheEntriesForTesting());
}
TEST_F(ServiceDiscardableManagerTest, DeleteAtShutdown) {
for (int i = 1; i <= 8; ++i) {
texture_manager_->CreateTexture(i, i);
auto handle = CreateLockedServiceHandleForTesting();
discardable_manager_.InsertLockedTexture(i, kSmallTextureSize,
texture_manager_, handle);
if (i % 2) {
TextureRef* texture_to_unbind;
EXPECT_TRUE(discardable_manager_.UnlockTexture(i, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
}
}
for (int i = 1; i <= 8; ++i) {
if (i % 2) {
ExpectUnlockedTextureDeletion(i);
} else {
ExpectTextureDeletion(i);
}
}
}
TEST_F(ServiceDiscardableManagerTest, UnlockInvalid) {
const GLuint kClientId = 1;
gles2::TextureRef* texture_to_unbind;
EXPECT_FALSE(discardable_manager_.UnlockTexture(kClientId, texture_manager_,
&texture_to_unbind));
EXPECT_EQ(nullptr, texture_to_unbind);
}
TEST_F(ServiceDiscardableManagerTest, Limits) {
const size_t cache_size_limit = 4 * 1024 * 1024;
const size_t texture_size = cache_size_limit / 4;
const size_t large_texture_size = 3 * texture_size;
discardable_manager_.SetCacheSizeLimitForTesting(cache_size_limit);
for (int i = 1; i < 5; ++i) {
texture_manager_->CreateTexture(i, i);
auto handle = CreateLockedServiceHandleForTesting();
discardable_manager_.InsertLockedTexture(i, texture_size, texture_manager_,
handle);
}
gles2::TextureRef* texture_to_unbind;
EXPECT_TRUE(discardable_manager_.UnlockTexture(3, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_TRUE(discardable_manager_.UnlockTexture(1, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_TRUE(discardable_manager_.UnlockTexture(2, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_TRUE(discardable_manager_.UnlockTexture(4, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
{
InSequence s;
ExpectUnlockedTextureDeletion(3);
ExpectUnlockedTextureDeletion(1);
ExpectUnlockedTextureDeletion(2);
ExpectUnlockedTextureDeletion(4);
}
for (int i = 5; i < 9; ++i) {
texture_manager_->CreateTexture(i, i);
auto handle = CreateLockedServiceHandleForTesting();
discardable_manager_.InsertLockedTexture(i, texture_size, texture_manager_,
handle);
}
Mock::VerifyAndClearExpectations(gl_.get());
Mock::VerifyAndClearExpectations(&destruction_observer_);
EXPECT_TRUE(discardable_manager_.UnlockTexture(5, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_TRUE(discardable_manager_.UnlockTexture(6, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_TRUE(discardable_manager_.UnlockTexture(8, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_TRUE(discardable_manager_.UnlockTexture(7, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
{
InSequence s;
ExpectUnlockedTextureDeletion(5);
ExpectUnlockedTextureDeletion(6);
ExpectUnlockedTextureDeletion(8);
}
texture_manager_->CreateTexture(9, 9);
auto handle = CreateLockedServiceHandleForTesting();
discardable_manager_.InsertLockedTexture(9, large_texture_size,
texture_manager_, handle);
ExpectTextureDeletion(9);
ExpectUnlockedTextureDeletion(7);
}
TEST_F(ServiceDiscardableManagerTest, TextureSizeChanged) {
const GLuint kClientId = 1;
const GLuint kServiceId = 2;
texture_manager_->CreateTexture(kClientId, kServiceId);
TextureRef* texture_ref = texture_manager_->GetTexture(kClientId);
auto handle = CreateLockedServiceHandleForTesting();
discardable_manager_.InsertLockedTexture(kClientId, 0, texture_manager_,
handle);
EXPECT_EQ(0u, discardable_manager_.TotalSizeForTesting());
texture_manager_->SetTarget(texture_ref, GL_TEXTURE_2D);
texture_manager_->SetLevelInfo(texture_ref, GL_TEXTURE_2D, 0, GL_RGBA,
kSmallTextureDim, kSmallTextureDim, 1, 0,
GL_RGBA, GL_UNSIGNED_BYTE,
gfx::Rect(kSmallTextureDim, kSmallTextureDim));
EXPECT_EQ(kSmallTextureSize, discardable_manager_.TotalSizeForTesting());
ExpectTextureDeletion(kClientId);
}
TEST_F(ServiceDiscardableManagerTest, OwnershipOnUnlock) {
const GLuint kClientId = 1;
const GLuint kServiceId = 2;
std::unique_ptr<ServiceDiscardableHandle> service_handle;
std::unique_ptr<ClientDiscardableHandle> client_handle;
CreateLockedHandlesForTesting(&service_handle, &client_handle);
texture_manager_->CreateTexture(kClientId, kServiceId);
discardable_manager_.InsertLockedTexture(kClientId, kSmallTextureSize,
texture_manager_, *service_handle);
client_handle->Lock();
TextureRef* texture_to_unbind;
discardable_manager_.UnlockTexture(kClientId, texture_manager_,
&texture_to_unbind);
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_TRUE(discardable_manager_.IsEntryLockedForTesting(kClientId,
texture_manager_));
discardable_manager_.LockTexture(kClientId, texture_manager_);
discardable_manager_.UnlockTexture(kClientId, texture_manager_,
&texture_to_unbind);
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_FALSE(discardable_manager_.IsEntryLockedForTesting(kClientId,
texture_manager_));
client_handle->Lock();
discardable_manager_.LockTexture(kClientId, texture_manager_);
client_handle->Lock();
discardable_manager_.LockTexture(kClientId, texture_manager_);
discardable_manager_.UnlockTexture(kClientId, texture_manager_,
&texture_to_unbind);
EXPECT_EQ(nullptr, texture_to_unbind);
EXPECT_TRUE(discardable_manager_.IsEntryLockedForTesting(kClientId,
texture_manager_));
discardable_manager_.UnlockTexture(kClientId, texture_manager_,
&texture_to_unbind);
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_FALSE(discardable_manager_.IsEntryLockedForTesting(kClientId,
texture_manager_));
ExpectUnlockedTextureDeletion(kClientId);
}
TEST_F(ServiceDiscardableManagerTest, BindGeneratedTextureLock) {
const GLuint kClientId = 1;
const GLuint kServiceId = 2;
const GLuint kGeneratedServiceId = 3;
texture_manager_->CreateTexture(kClientId, kServiceId);
auto handle = CreateLockedServiceHandleForTesting();
discardable_manager_.InsertLockedTexture(kClientId, kSmallTextureSize,
texture_manager_, handle);
gles2::TextureRef* texture_to_unbind;
EXPECT_TRUE(discardable_manager_.UnlockTexture(kClientId, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_EQ(nullptr, texture_manager_->GetTexture(kClientId));
texture_manager_->CreateTexture(kClientId, kGeneratedServiceId);
TextureRef* generated_texture_ref = texture_manager_->GetTexture(kClientId);
ExpectUnlockedTextureDeletion(kClientId);
discardable_manager_.LockTexture(kClientId, texture_manager_);
EXPECT_EQ(generated_texture_ref, texture_manager_->GetTexture(kClientId));
ExpectTextureDeletion(kClientId);
texture_manager_->RemoveTexture(kClientId);
EXPECT_EQ(0u, discardable_manager_.NumCacheEntriesForTesting());
}
TEST_F(ServiceDiscardableManagerTest, BindGeneratedTextureInitialization) {
const GLuint kClientId = 1;
const GLuint kServiceId = 2;
const GLuint kGeneratedServiceId = 3;
texture_manager_->CreateTexture(kClientId, kServiceId);
auto handle = CreateLockedServiceHandleForTesting();
discardable_manager_.InsertLockedTexture(kClientId, kSmallTextureSize,
texture_manager_, handle);
gles2::TextureRef* texture_to_unbind;
EXPECT_TRUE(discardable_manager_.UnlockTexture(kClientId, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_EQ(nullptr, texture_manager_->GetTexture(kClientId));
texture_manager_->CreateTexture(kClientId, kGeneratedServiceId);
TextureRef* generated_texture_ref = texture_manager_->GetTexture(kClientId);
ExpectUnlockedTextureDeletion(kClientId);
discardable_manager_.InsertLockedTexture(kClientId, kSmallTextureSize,
texture_manager_, handle);
EXPECT_EQ(generated_texture_ref, texture_manager_->GetTexture(kClientId));
ExpectTextureDeletion(kClientId);
}
TEST_F(ServiceDiscardableManagerTest, BindGeneratedTextureSizeChange) {
const GLuint kClientId = 1;
const GLuint kServiceId = 2;
const GLuint kGeneratedServiceId = 3;
texture_manager_->CreateTexture(kClientId, kServiceId);
auto handle = CreateLockedServiceHandleForTesting();
discardable_manager_.InsertLockedTexture(kClientId, 0, texture_manager_,
handle);
gles2::TextureRef* texture_to_unbind;
EXPECT_TRUE(discardable_manager_.UnlockTexture(kClientId, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_EQ(nullptr, texture_manager_->GetTexture(kClientId));
texture_manager_->CreateTexture(kClientId, kGeneratedServiceId);
TextureRef* generated_texture_ref = texture_manager_->GetTexture(kClientId);
EXPECT_EQ(0u, discardable_manager_.TotalSizeForTesting());
texture_manager_->SetTarget(generated_texture_ref, GL_TEXTURE_2D);
texture_manager_->SetLevelInfo(generated_texture_ref, GL_TEXTURE_2D, 0,
GL_RGBA, kSmallTextureDim, kSmallTextureDim, 1,
0, GL_RGBA, GL_UNSIGNED_BYTE,
gfx::Rect(kSmallTextureDim, kSmallTextureDim));
EXPECT_EQ(kSmallTextureSize, discardable_manager_.TotalSizeForTesting());
ExpectUnlockedTextureDeletion(kClientId);
ExpectTextureDeletion(kClientId);
}
TEST_F(ServiceDiscardableManagerTest, MemoryPressure) {
const size_t cache_size_limit = 4 * 1024 * 1024;
const size_t texture_size = cache_size_limit / 4;
discardable_manager_.SetCacheSizeLimitForTesting(cache_size_limit);
for (int i = 1; i < 5; ++i) {
texture_manager_->CreateTexture(i, i);
auto handle = CreateLockedServiceHandleForTesting();
discardable_manager_.InsertLockedTexture(i, texture_size, texture_manager_,
handle);
}
discardable_manager_.HandleMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
gles2::TextureRef* texture_to_unbind;
EXPECT_TRUE(discardable_manager_.UnlockTexture(3, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
ExpectUnlockedTextureDeletion(3);
discardable_manager_.HandleMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
EXPECT_TRUE(discardable_manager_.UnlockTexture(1, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_TRUE(discardable_manager_.UnlockTexture(2, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
EXPECT_TRUE(discardable_manager_.UnlockTexture(4, texture_manager_,
&texture_to_unbind));
EXPECT_NE(nullptr, texture_to_unbind);
{
InSequence s;
ExpectUnlockedTextureDeletion(1);
ExpectUnlockedTextureDeletion(2);
}
discardable_manager_.HandleMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
ExpectUnlockedTextureDeletion(4);
discardable_manager_.HandleMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
}
}
}