| // 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); | |
| EXPECT_EQ(image->GetSharedImage()->alpha_type(), new_info.alpha_type); | |
| EXPECT_EQ(image->GetSharedImage()->is_software(), new_info.is_software); | |
| // 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 |