blob: 66746b859bda9402e5bd50114fa4764af485d316 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gpu/command_buffer/client/shared_image_pool.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "components/viz/test/test_context_provider.h"
#include "gpu/command_buffer/client/shared_image_interface.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::AtLeast;
namespace gpu {
class SharedImagePoolTest : public testing::Test {
public:
SharedImagePoolTest() = default;
~SharedImagePoolTest() override = default;
protected:
void SetUp() override {
test_sii_ = base::MakeRefCounted<gpu::TestSharedImageInterface>();
}
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
scoped_refptr<gpu::TestSharedImageInterface> test_sii_;
};
// Extending ClientImage for testing.
class TestClientImage : public ClientImage {
public:
explicit TestClientImage(scoped_refptr<ClientSharedImage> shared_image)
: ClientImage(std::move(shared_image)) {
static int i = 0;
id = ++i;
}
int id = 0;
protected:
friend class base::RefCounted<TestClientImage>;
~TestClientImage() override = default;
};
// Class used for showcasing complex usage of SharedImagePool with additional
// metadata in ClientImage.
class ExtendedClientImage : public ClientImage {
public:
int extra_metadata = 0;
explicit ExtendedClientImage(scoped_refptr<ClientSharedImage> shared_image)
: ClientImage(std::move(shared_image)) {}
void SetMetadata(int metadata) { extra_metadata = metadata; }
protected:
friend class base::RefCounted<ExtendedClientImage>;
~ExtendedClientImage() override = default;
};
// Test for verifying if shared image and creation sync token have been created.
TEST_F(SharedImagePoolTest, VerifyImage) {
ImageInfo info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
auto pool =
SharedImagePool<ClientImage>::Create(info, test_sii_, "SIPoolTest");
auto image = pool->GetImage();
// Verify shared image is created.
EXPECT_TRUE(image->GetSharedImage() != nullptr);
// Verify the SyncToken has data.
EXPECT_TRUE(image->GetSyncToken().HasData());
}
// Test for verifying releasing and recycling images in the pool.
TEST_F(SharedImagePoolTest, ReleaseAndRecycleImage) {
ImageInfo info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
auto pool =
SharedImagePool<TestClientImage>::Create(info, test_sii_, "SIPoolTest");
auto image1 = pool->GetImage();
auto image1_id = image1->id;
pool->ReleaseImage(std::move(image1));
auto recycled_image = pool->GetImage();
auto recycled_image_id = recycled_image->id;
// Check if the recycled image is the same as the one released.
EXPECT_EQ(recycled_image_id, image1_id);
}
// Test the pool's behavior when it reaches maximum capacity.
TEST_F(SharedImagePoolTest, MaxPoolSizeBehavior) {
ImageInfo info = {
gfx::Size(1024, 768), viz::SinglePlaneFormat::kRGBA_8888, {}};
const auto max_pool_size = 1;
auto pool = SharedImagePool<TestClientImage>::Create(
info, test_sii_, "SIPoolTest", max_pool_size);
auto image1 = pool->GetImage();
auto image2 = pool->GetImage();
auto image2_id = image2->id;
pool->ReleaseImage(std::move(image1));
// This image should not be stored as the pool is full.
pool->ReleaseImage(std::move(image2));
auto retrieved_image = pool->GetImage();
auto retrieved_image_id = retrieved_image->id;
// The second image should not be the same as retrieved image.
EXPECT_NE(retrieved_image_id, image2_id);
}
// Test for verifying maximum pool size behavior.
TEST_F(SharedImagePoolTest, MaxPoolSizeEnforcement) {
ImageInfo info = {
gfx::Size(1024, 768), viz::SinglePlaneFormat::kRGBA_8888, {}};
const size_t max_pool_size = 1;
auto pool = SharedImagePool<ClientImage>::Create(info, test_sii_,
"SIPoolTest", max_pool_size);
auto image1 = pool->GetImage();
auto image2 = pool->GetImage();
pool->ReleaseImage(std::move(image1));
// Should trigger destruction of one image.
pool->ReleaseImage(std::move(image2));
// Pool should not exceed max size.
EXPECT_EQ(pool->GetPoolSizeForTesting(), max_pool_size);
}
// Test for verifying release sync token behaviour.
TEST_F(SharedImagePoolTest, TokenConsistencyOnReuse) {
ImageInfo info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
auto pool =
SharedImagePool<ClientImage>::Create(info, test_sii_, "SIPoolTest");
auto image = pool->GetImage();
gpu::SyncToken release_token = test_sii_->GenUnverifiedSyncToken();
image->SetReleaseSyncToken(release_token);
pool->ReleaseImage(std::move(image));
auto reused_image = pool->GetImage();
EXPECT_EQ(reused_image->GetSyncToken(), release_token);
}
// Test for verifying release sync token behaviour.
TEST_F(SharedImagePoolTest, ProperTokenHandlingBeforeReuse) {
ImageInfo info = {
gfx::Size(800, 600), viz::SinglePlaneFormat::kRGBA_8888, {}};
const size_t max_pool_size = 1;
auto pool = SharedImagePool<ClientImage>::Create(info, test_sii_,
"SIPoolTest", max_pool_size);
auto image1 = pool->GetImage();
auto image2 = pool->GetImage();
// |image1| will be cached in the pool.
gpu::SyncToken release_token1 = test_sii_->GenUnverifiedSyncToken();
image1->SetReleaseSyncToken(release_token1);
pool->ReleaseImage(std::move(image1));
// |image2| will be destroyed since the cache is full.
gpu::SyncToken release_token2 = test_sii_->GenUnverifiedSyncToken();
image2->SetReleaseSyncToken(release_token2);
pool->ReleaseImage(std::move(image2));
// |image3| will be re-used from the pool.
auto image3 = pool->GetImage();
EXPECT_EQ(image3->GetSyncToken(), release_token1);
EXPECT_TRUE(image3->GetSyncToken().HasData());
}
// Test for showcasing complex client usage via ExtendedClientImage.
TEST_F(SharedImagePoolTest, ComplexClientUsage) {
ImageInfo info = {
gfx::Size(1280, 720), viz::SinglePlaneFormat::kRGBA_8888, {}};
auto pool = SharedImagePool<ExtendedClientImage>::Create(info, test_sii_,
"SIPoolTest");
scoped_refptr<ExtendedClientImage> extended_client_image = pool->GetImage();
EXPECT_TRUE(extended_client_image);
// Sets additional metadata.
extended_client_image->SetMetadata(42);
// Verify metadata is set and retrieved correctly
EXPECT_EQ(extended_client_image->extra_metadata, 42);
// Verify that the SyncToken within the image is valid.
EXPECT_TRUE(extended_client_image->GetSyncToken().HasData());
}
// Test to ensure that images from a pool with one configuration are not reused
// in a pool with a different configuration.
TEST_F(SharedImagePoolTest, DiscardImageFromDifferentPool) {
ImageInfo pool1_info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
ImageInfo pool2_info = {
gfx::Size(800, 600), viz::SinglePlaneFormat::kRGBA_8888, {}};
auto pool1 =
SharedImagePool<ClientImage>::Create(pool1_info, test_sii_, "SIPoolTest");
auto pool2 = SharedImagePool<ClientImage>::Create(pool2_info, test_sii_.get(),
"SIPoolTest");
auto image_from_pool1 = pool1->GetImage();
EXPECT_TRUE(image_from_pool1);
// Attempt to release this image into pool2 which has different ImageInfo.
pool2->ReleaseImage(std::move(image_from_pool1));
// Check that pool2 has not accepted the image from pool1 due to mismatched
// ImageInfo.
EXPECT_EQ(pool2->GetPoolSizeForTesting(), size_t(0));
}
// Test Reconfigure method of SharedImagePool.
TEST_F(SharedImagePoolTest, ReconfigurePool) {
// Initial ImageInfo.
ImageInfo initial_info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
// Create the pool with an initial configuration.
auto pool = SharedImagePool<ClientImage>::Create(
initial_info, test_sii_.get(), "SIPoolTest", /*max_pool_size=*/2);
// Create a different ImageInfo.
ImageInfo new_info = {
gfx::Size(1280, 720), viz::SinglePlaneFormat::kBGRA_8888, {}};
// Reconfigure with a different ImageInfo.
pool->Reconfigure(new_info);
// Verify that the pool was reconfigured.
EXPECT_EQ(pool->GetImageInfo(), new_info);
// The pool should now return images with the new configuration.
auto image = pool->GetImage();
ASSERT_TRUE(image);
EXPECT_EQ(image->GetSharedImage()->size(), new_info.size);
EXPECT_EQ(image->GetSharedImage()->format(), new_info.format);
// Attempt to reconfigure with the same ImageInfo, which should be a no-op.
pool->Reconfigure(new_info);
// Ensure the ImageInfo remains unchanged.
EXPECT_EQ(pool->GetImageInfo(), new_info);
// Confirm that images created after reconfiguration still follow the new
// configuration
auto new_image = pool->GetImage();
ASSERT_TRUE(new_image);
EXPECT_EQ(new_image->GetSharedImage()->size(), new_info.size);
EXPECT_EQ(new_image->GetSharedImage()->format(), new_info.format);
}
// Test for setting the release sync token in ClientImage.
TEST_F(SharedImagePoolTest, SetReleaseSyncToken) {
ImageInfo info = {
gfx::Size(1024, 768), viz::SinglePlaneFormat::kRGBA_8888, {}};
auto pool =
SharedImagePool<TestClientImage>::Create(info, test_sii_, "SIPoolTest");
// Create a new ClientImage object from the pool.
auto client_image = pool->GetImage();
// Verify that the client image was successfully created.
EXPECT_TRUE(client_image != nullptr);
// Create a dummy SyncToken to simulate release.
SyncToken release_sync_token(gpu::CommandBufferNamespace::GPU_IO,
gpu::CommandBufferId::FromUnsafeValue(1), 12345);
// Set the release SyncToken.
client_image->SetReleaseSyncToken(release_sync_token);
// Verify that the release SyncToken was set correctly.
EXPECT_EQ(client_image->GetSyncToken(), release_sync_token);
}
// Test that SharedImagePool creates a mappable shared image when buffer_usage
// is set.
TEST_F(SharedImagePoolTest, CreatesMappableSharedImageWhenBufferUsageIsSet) {
// Define ImageInfo with buffer_usage set.
ImageInfo info = {gfx::Size(100, 100),
viz::SinglePlaneFormat::kRGBA_8888,
{},
gfx::BufferUsage::GPU_READ};
auto pool =
SharedImagePool<ClientImage>::Create(info, test_sii_, "SIPoolTest");
// Expect CreateSharedImage to be called with buffer_usage specified.
EXPECT_CALL(*test_sii_,
DoCreateSharedImage(
gfx::Size(100, 100), viz::SinglePlaneFormat::kRGBA_8888,
gpu::kNullSurfaceHandle, gfx::BufferUsage::GPU_READ));
scoped_refptr<ClientImage> image = pool->GetImage();
EXPECT_NE(image, nullptr);
EXPECT_EQ(image->GetSharedImage()->buffer_usage(),
gfx::BufferUsage::GPU_READ);
}
TEST_F(SharedImagePoolTest, DoesNotReuseSharedImageWithDifferentBufferUsage) {
// Define ImageInfo with initial buffer_usage.
ImageInfo info = {gfx::Size(100, 100),
viz::SinglePlaneFormat::kRGBA_8888,
{},
gfx::BufferUsage::GPU_READ};
auto pool =
SharedImagePool<ClientImage>::Create(info, test_sii_, "SIPoolTest");
// Expect CreateSharedImage to be called with initial buffer_usage.
EXPECT_CALL(*test_sii_,
DoCreateSharedImage(
gfx::Size(100, 100), viz::SinglePlaneFormat::kRGBA_8888,
gpu::kNullSurfaceHandle, gfx::BufferUsage::GPU_READ))
.Times(1);
// First image created with GPU_READ usage.
scoped_refptr<ClientImage> image1 = pool->GetImage();
EXPECT_NE(image1, nullptr);
EXPECT_EQ(image1->GetSharedImage()->buffer_usage(),
gfx::BufferUsage::GPU_READ);
// Release the first image back to the pool.
pool->ReleaseImage(std::move(image1));
// Change buffer usage to GPU_READ_CPU_READ_WRITE.
info.buffer_usage = gfx::BufferUsage::GPU_READ_CPU_READ_WRITE;
pool->Reconfigure(info);
// Expect CreateSharedImage to be called again with new
// buffer_usage.
EXPECT_CALL(*test_sii_,
DoCreateSharedImage(gfx::Size(100, 100),
viz::SinglePlaneFormat::kRGBA_8888,
gpu::kNullSurfaceHandle,
gfx::BufferUsage::GPU_READ_CPU_READ_WRITE))
.Times(1);
// Second image created with GPU_READ_CPU_READ_WRITE usage. It should be a new
// image.
scoped_refptr<ClientImage> image2 = pool->GetImage();
EXPECT_NE(image2, nullptr);
EXPECT_EQ(image2->GetSharedImage()->buffer_usage(),
gfx::BufferUsage::GPU_READ_CPU_READ_WRITE);
// Ensure the new image is different from the first one due to different
// buffer usage.
EXPECT_NE(image1, image2);
}
// Test for verifying the reclaim timer is started when an image is released.
TEST_F(SharedImagePoolTest, ReclaimTimerStartedOnRelease) {
ImageInfo info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
constexpr auto kExpirationTime = base::Seconds(30);
auto pool = SharedImagePool<ClientImage>::Create(
info, test_sii_, "SIPoolTest",
/*max_pool_size=*/std::nullopt, kExpirationTime);
auto image = pool->GetImage();
pool->ReleaseImage(std::move(image));
EXPECT_TRUE(pool->IsReclaimTimerRunningForTesting());
}
// Test for verifying the reclaim timer is not started when expiration time is
// not set.
TEST_F(SharedImagePoolTest, ReclaimTimerNotStartedWhenExpirationTimeNotSet) {
ImageInfo info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
auto pool = SharedImagePool<ClientImage>::Create(
info, test_sii_, "SIPoolTest", /*max_pool_size=*/std::nullopt,
/*unused_resource_expiration_time=*/std::nullopt);
auto image = pool->GetImage();
pool->ReleaseImage(std::move(image));
EXPECT_FALSE(pool->IsReclaimTimerRunningForTesting());
}
// Test for verifying the reclaim timer is not started when the pool is empty.
TEST_F(SharedImagePoolTest, ReclaimTimerNotStartedWhenPoolIsEmpty) {
ImageInfo info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
constexpr auto kExpirationTime = base::Seconds(30);
auto pool = SharedImagePool<ClientImage>::Create(
info, test_sii_, "SIPoolTest",
/*max_pool_size=*/std::nullopt, kExpirationTime);
EXPECT_FALSE(pool->IsReclaimTimerRunningForTesting());
}
// Test for verifying that unused resources are reclaimed after expiration.
TEST_F(SharedImagePoolTest, UnusedResourcesReclaimedAfterExpiration) {
ImageInfo info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
constexpr auto kExpirationTime = base::Seconds(30);
auto pool = SharedImagePool<ClientImage>::Create(
info, test_sii_, "SIPoolTest",
/*max_pool_size=*/std::nullopt, kExpirationTime);
auto image1 = pool->GetImage();
pool->ReleaseImage(std::move(image1));
EXPECT_EQ(pool->GetPoolSizeForTesting(), 1u);
// Advance time past the expiration time.
task_environment_.FastForwardBy(kExpirationTime + base::Seconds(1));
// Verify that the image is reclaimed.
EXPECT_EQ(pool->GetPoolSizeForTesting(), 0u);
}
// Test for verifying that unused resources are not reclaimed before expiration.
TEST_F(SharedImagePoolTest, UnusedResourcesNotReclaimedBeforeExpiration) {
ImageInfo info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
constexpr auto kExpirationTime = base::Seconds(30);
auto pool = SharedImagePool<ClientImage>::Create(
info, test_sii_, "SIPoolTest",
/*max_pool_size=*/std::nullopt, kExpirationTime);
auto image1 = pool->GetImage();
pool->ReleaseImage(std::move(image1));
EXPECT_EQ(pool->GetPoolSizeForTesting(), 1u);
// Advance time to just before the expiration time.
task_environment_.FastForwardBy(kExpirationTime - base::Seconds(1));
// Verify that the image is not reclaimed.
EXPECT_EQ(pool->GetPoolSizeForTesting(), 1u);
}
// Test to verify that only resources older than the expiration time are
// reclaimed.
TEST_F(SharedImagePoolTest, OnlyResourcesOlderThanExpirationAreReclaimed) {
ImageInfo info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
constexpr auto kExpirationTime = base::Seconds(30);
auto pool = SharedImagePool<ClientImage>::Create(
info, test_sii_, "SIPoolTest",
/*max_pool_size=*/std::nullopt, kExpirationTime);
auto image1 = pool->GetImage();
pool->ReleaseImage(std::move(image1));
// Advance time to half the expiration time.
task_environment_.FastForwardBy(kExpirationTime / 2);
auto image2 = pool->GetImage();
pool->ReleaseImage(std::move(image2));
// Advance time past the expiration time.
task_environment_.FastForwardBy(kExpirationTime / 2 + base::Seconds(1));
// Verify that only the first image is reclaimed.
EXPECT_EQ(pool->GetPoolSizeForTesting(), 1u);
}
// Test to verify that the reclaim timer is restarted after reclaiming
// resources.
TEST_F(SharedImagePoolTest, ReclaimTimerRestartedAfterReclaiming) {
ImageInfo info = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
constexpr auto kExpirationTime = base::Seconds(30);
auto pool = SharedImagePool<ClientImage>::Create(
info, test_sii_, "SIPoolTest",
/*max_pool_size=*/std::nullopt, kExpirationTime);
auto image1 = pool->GetImage();
pool->ReleaseImage(std::move(image1));
// Advance time past the expiration time.
task_environment_.FastForwardBy(kExpirationTime + base::Seconds(1));
// Verify that the image is reclaimed.
EXPECT_EQ(pool->GetPoolSizeForTesting(), 0u);
auto image2 = pool->GetImage();
pool->ReleaseImage(std::move(image2));
EXPECT_EQ(pool->GetPoolSizeForTesting(), 1u);
EXPECT_TRUE(pool->IsReclaimTimerRunningForTesting());
}
// Test to check that images created by different pools have unique pool IDs.
TEST_F(SharedImagePoolTest, DifferentPoolsHaveDifferentPoolIds) {
ImageInfo info1 = {
gfx::Size(1920, 1080), viz::SinglePlaneFormat::kRGBA_8888, {}};
auto pool1 =
SharedImagePool<ClientImage>::Create(info1, test_sii_, "SIPoolTest");
ImageInfo info2 = {
gfx::Size(200, 200), viz::SinglePlaneFormat::kBGRA_8888, {}};
auto pool2 =
SharedImagePool<ClientImage>::Create(info2, test_sii_, "SIPoolTest");
auto image_from_first_pool = pool1->GetImage();
auto image_from_second_pool = pool2->GetImage();
ASSERT_NE(image_from_first_pool, nullptr);
ASSERT_NE(image_from_second_pool, nullptr);
// Verify that pool IDs are different for images from different pools.
EXPECT_NE(image_from_first_pool->GetPoolIdForTesting(),
image_from_second_pool->GetPoolIdForTesting());
}
} // namespace gpu