| // Copyright 2021 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 "components/viz/service/display/display_resource_provider_skia.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <set> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/check.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/optional.h" |
| #include "build/build_config.h" |
| #include "components/viz/client/client_resource_provider.h" |
| #include "components/viz/common/resources/release_callback.h" |
| #include "components/viz/common/resources/returned_resource.h" |
| #include "components/viz/test/test_context_provider.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/khronos/GLES2/gl2.h" |
| #include "third_party/khronos/GLES2/gl2ext.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| using testing::_; |
| using testing::ByMove; |
| using testing::DoAll; |
| using testing::Return; |
| using testing::SaveArg; |
| |
| namespace viz { |
| namespace { |
| |
| class MockReleaseCallback { |
| public: |
| MOCK_METHOD2(Released, void(const gpu::SyncToken& token, bool lost)); |
| }; |
| |
| MATCHER_P(SamePtr, ptr_to_expected, "") { |
| return arg.get() == ptr_to_expected; |
| } |
| |
| static void CollectResources(std::vector<ReturnedResource>* array, |
| std::vector<ReturnedResource> returned) { |
| array->insert(array->end(), std::make_move_iterator(returned.begin()), |
| std::make_move_iterator(returned.end())); |
| } |
| |
| class MockExternalUseClient : public ExternalUseClient { |
| public: |
| MockExternalUseClient() = default; |
| MOCK_METHOD1(ReleaseImageContexts, |
| gpu::SyncToken( |
| std::vector<std::unique_ptr<ImageContext>> image_contexts)); |
| MOCK_METHOD6(CreateImageContext, |
| std::unique_ptr<ImageContext>( |
| const gpu::MailboxHolder&, |
| const gfx::Size&, |
| ResourceFormat, |
| bool, |
| const base::Optional<gpu::VulkanYCbCrInfo>& ycbcr_info, |
| sk_sp<SkColorSpace>)); |
| }; |
| |
| class DisplayResourceProviderSkiaTest : public testing::Test { |
| public: |
| DisplayResourceProviderSkiaTest() { |
| child_context_provider_ = TestContextProvider::Create(); |
| child_context_provider_->BindToCurrentThread(); |
| |
| resource_provider_ = std::make_unique<DisplayResourceProviderSkia>(); |
| |
| lock_set_.emplace(resource_provider_.get(), &client_); |
| |
| child_resource_provider_ = std::make_unique<ClientResourceProvider>(); |
| } |
| |
| ~DisplayResourceProviderSkiaTest() override { |
| child_resource_provider_->ShutdownAndReleaseAllResources(); |
| } |
| |
| static ReturnCallback GetReturnCallback( |
| std::vector<ReturnedResource>* array) { |
| return base::BindRepeating(&CollectResources, array); |
| } |
| |
| TransferableResource CreateResource(ResourceFormat format) { |
| constexpr gfx::Size size(64, 64); |
| gpu::Mailbox gpu_mailbox = gpu::Mailbox::GenerateForSharedImage(); |
| gpu::SyncToken sync_token = GenSyncToken(); |
| EXPECT_TRUE(sync_token.HasData()); |
| |
| TransferableResource gl_resource = TransferableResource::MakeGL( |
| gpu_mailbox, GL_LINEAR, GL_TEXTURE_2D, sync_token, size, |
| false /* is_overlay_candidate */); |
| gl_resource.format = format; |
| return gl_resource; |
| } |
| |
| gpu::SyncToken GenSyncToken() { |
| gpu::SyncToken sync_token(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x123), |
| next_fence_sync_++); |
| sync_token.SetVerifyFlush(); |
| return sync_token; |
| } |
| |
| protected: |
| uint64_t next_fence_sync_ = 1; |
| scoped_refptr<TestContextProvider> child_context_provider_; |
| std::unique_ptr<DisplayResourceProviderSkia> resource_provider_; |
| std::unique_ptr<ClientResourceProvider> child_resource_provider_; |
| testing::NiceMock<MockExternalUseClient> client_; |
| base::Optional<DisplayResourceProviderSkia::LockSetForExternalUse> lock_set_; |
| }; |
| |
| TEST_F(DisplayResourceProviderSkiaTest, LockForExternalUse) { |
| gpu::SyncToken sync_token1(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x123), |
| 0x42); |
| auto mailbox = gpu::Mailbox::Generate(); |
| constexpr gfx::Size size(64, 64); |
| TransferableResource gl_resource = TransferableResource::MakeGL( |
| mailbox, GL_LINEAR, GL_TEXTURE_2D, sync_token1, size, |
| false /* is_overlay_candidate */); |
| ResourceId id1 = |
| child_resource_provider_->ImportResource(gl_resource, base::DoNothing()); |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), SurfaceId()); |
| |
| // Transfer some resources to the parent. |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent( |
| {id1}, &list, |
| static_cast<RasterContextProvider*>(child_context_provider_.get())); |
| ASSERT_EQ(1u, list.size()); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1)); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| |
| ResourceId parent_id = resource_map[list.front().id]; |
| |
| auto owned_image_context = std::make_unique<ExternalUseClient::ImageContext>( |
| gpu::MailboxHolder(mailbox, sync_token1, GL_TEXTURE_2D), size, RGBA_8888, |
| /*ycbcr_info=*/base::nullopt, /*color_space=*/nullptr); |
| auto* image_context = owned_image_context.get(); |
| |
| gpu::MailboxHolder holder; |
| EXPECT_CALL(client_, CreateImageContext(_, _, _, _, _, _)) |
| .WillOnce(DoAll(SaveArg<0>(&holder), |
| Return(ByMove(std::move(owned_image_context))))); |
| |
| ExternalUseClient::ImageContext* locked_image_context = |
| lock_set_->LockResource(parent_id, /*maybe_concurrent_reads=*/true, |
| /*is_video_plane=*/false); |
| EXPECT_EQ(image_context, locked_image_context); |
| ASSERT_EQ(holder.mailbox, mailbox); |
| ASSERT_TRUE(holder.sync_token.HasData()); |
| |
| // Don't release while locked. |
| EXPECT_CALL(client_, ReleaseImageContexts(_)).Times(0); |
| // Return the resources back to the child. Nothing should happen because |
| // of the resource lock. |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| // The resource should not be returned due to the external use lock. |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| gpu::SyncToken sync_token2(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x234), |
| 0x456); |
| sync_token2.SetVerifyFlush(); |
| |
| gpu::SyncToken sync_token3(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x234), |
| 0x567); |
| sync_token3.SetVerifyFlush(); |
| // We will get a second release of |parent_id| now that we've released our |
| // external lock. |
| EXPECT_CALL(client_, ReleaseImageContexts( |
| testing::ElementsAre(SamePtr(locked_image_context)))) |
| .WillOnce(Return(sync_token3)); |
| // UnlockResources will also call DeclareUsedResourcesFromChild. |
| lock_set_->UnlockResources(sync_token2); |
| // The resource should be returned after the lock is released. |
| EXPECT_EQ(1u, returned_to_child.size()); |
| EXPECT_EQ(sync_token3, returned_to_child[0].sync_token); |
| child_resource_provider_->ReceiveReturnsFromParent( |
| std::move(returned_to_child)); |
| child_resource_provider_->RemoveImportedResource(id1); |
| } |
| |
| TEST_F(DisplayResourceProviderSkiaTest, LockForExternalUseWebView) { |
| gpu::SyncToken sync_token1(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x123), |
| 0x42); |
| auto mailbox = gpu::Mailbox::Generate(); |
| constexpr gfx::Size size(64, 64); |
| TransferableResource gl_resource = TransferableResource::MakeGL( |
| mailbox, GL_LINEAR, GL_TEXTURE_2D, sync_token1, size, |
| false /* is_overlay_candidate */); |
| ResourceId id1 = |
| child_resource_provider_->ImportResource(gl_resource, base::DoNothing()); |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), SurfaceId()); |
| |
| // Transfer some resources to the parent. |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent( |
| {id1}, &list, |
| static_cast<RasterContextProvider*>(child_context_provider_.get())); |
| ASSERT_EQ(1u, list.size()); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1)); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| |
| ResourceId parent_id = resource_map[list.front().id]; |
| |
| auto owned_image_context = std::make_unique<ExternalUseClient::ImageContext>( |
| gpu::MailboxHolder(mailbox, sync_token1, GL_TEXTURE_2D), size, RGBA_8888, |
| /*ycbcr_info=*/base::nullopt, /*color_space=*/nullptr); |
| auto* image_context = owned_image_context.get(); |
| |
| gpu::MailboxHolder holder; |
| EXPECT_CALL(client_, CreateImageContext(_, _, _, _, _, _)) |
| .WillOnce(DoAll(SaveArg<0>(&holder), |
| Return(ByMove(std::move(owned_image_context))))); |
| |
| ExternalUseClient::ImageContext* locked_image_context = |
| lock_set_->LockResource(parent_id, /*maybe_concurrent_reads=*/true, |
| /*is_video_plane=*/false); |
| EXPECT_EQ(image_context, locked_image_context); |
| ASSERT_EQ(holder.mailbox, mailbox); |
| ASSERT_TRUE(holder.sync_token.HasData()); |
| |
| // Don't release while locked. |
| EXPECT_CALL(client_, ReleaseImageContexts(_)).Times(0); |
| // Return the resources back to the child. Nothing should happen because |
| // of the resource lock. |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| // The resource should not be returned due to the external use lock. |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| // Disable access to gpu thread. |
| resource_provider_->SetAllowAccessToGPUThread(false); |
| |
| gpu::SyncToken sync_token2(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x234), |
| 0x456); |
| sync_token2.SetVerifyFlush(); |
| |
| gpu::SyncToken sync_token3(gpu::CommandBufferNamespace::GPU_IO, |
| gpu::CommandBufferId::FromUnsafeValue(0x234), |
| 0x567); |
| sync_token3.SetVerifyFlush(); |
| |
| // Without GPU thread access no ReleaseImageContexts() should happen |
| EXPECT_CALL(client_, ReleaseImageContexts(_)).Times(0); |
| // Unlock resources |
| lock_set_->UnlockResources(sync_token2); |
| // Resources should not be returned because we can't unlock them on GPU |
| // thread. |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| // We will get a second release of |parent_id| now that we've released our |
| // external lock and have access to GPU thread. |
| EXPECT_CALL(client_, ReleaseImageContexts( |
| testing::ElementsAre(SamePtr(locked_image_context)))) |
| .WillOnce(Return(sync_token3)); |
| // Enable access to GPU Thread |
| resource_provider_->SetAllowAccessToGPUThread(true); |
| |
| // The resource should be returned after the lock is released. |
| EXPECT_EQ(1u, returned_to_child.size()); |
| EXPECT_EQ(sync_token3, returned_to_child[0].sync_token); |
| child_resource_provider_->ReceiveReturnsFromParent( |
| std::move(returned_to_child)); |
| child_resource_provider_->RemoveImportedResource(id1); |
| } |
| |
| class TestFence : public ResourceFence { |
| public: |
| TestFence() = default; |
| |
| // ResourceFence implementation. |
| void Set() override {} |
| bool HasPassed() override { return passed; } |
| |
| bool passed = false; |
| |
| private: |
| ~TestFence() override = default; |
| }; |
| |
| TEST_F(DisplayResourceProviderSkiaTest, |
| ReadLockFenceStopsReturnToChildOrDelete) { |
| MockReleaseCallback release; |
| TransferableResource tran1 = CreateResource(RGBA_8888); |
| tran1.read_lock_fences_enabled = true; |
| ResourceId id1 = child_resource_provider_->ImportResource( |
| tran1, base::BindOnce(&MockReleaseCallback::Released, |
| base::Unretained(&release))); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), SurfaceId()); |
| |
| // Transfer some resources to the parent. |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent( |
| {id1}, &list, |
| static_cast<RasterContextProvider*>(child_context_provider_.get())); |
| ASSERT_EQ(1u, list.size()); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1)); |
| EXPECT_TRUE(list[0].read_lock_fences_enabled); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| |
| scoped_refptr<TestFence> fence(new TestFence); |
| resource_provider_->SetReadLockFence(fence.get()); |
| { |
| ResourceId parent_id = resource_map[list.front().id]; |
| lock_set_->LockResource(parent_id, /*maybe_concurrent_reads=*/true, |
| /*is_video_plane=*/false); |
| lock_set_->UnlockResources(GenSyncToken()); |
| } |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| fence->passed = true; |
| |
| resource_provider_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet()); |
| EXPECT_EQ(1u, returned_to_child.size()); |
| |
| child_resource_provider_->ReceiveReturnsFromParent( |
| std::move(returned_to_child)); |
| EXPECT_CALL(release, Released(_, _)); |
| child_resource_provider_->RemoveImportedResource(id1); |
| } |
| |
| TEST_F(DisplayResourceProviderSkiaTest, ReadLockFenceDestroyChild) { |
| MockReleaseCallback release; |
| |
| TransferableResource tran1 = CreateResource(RGBA_8888); |
| tran1.read_lock_fences_enabled = true; |
| ResourceId id1 = child_resource_provider_->ImportResource( |
| tran1, base::BindOnce(&MockReleaseCallback::Released, |
| base::Unretained(&release))); |
| |
| TransferableResource tran2 = CreateResource(RGBA_8888); |
| tran2.read_lock_fences_enabled = false; |
| ResourceId id2 = child_resource_provider_->ImportResource( |
| tran2, base::BindOnce(&MockReleaseCallback::Released, |
| base::Unretained(&release))); |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), SurfaceId()); |
| |
| // Transfer resources to the parent. |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent( |
| {id1, id2}, &list, |
| static_cast<RasterContextProvider*>(child_context_provider_.get())); |
| ASSERT_EQ(2u, list.size()); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id1)); |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id2)); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| |
| scoped_refptr<TestFence> fence(new TestFence); |
| resource_provider_->SetReadLockFence(fence.get()); |
| { |
| for (auto& resource : list) { |
| ResourceId parent_id = resource_map[resource.id]; |
| lock_set_->LockResource(parent_id, /*maybe_concurrent_reads=*/true, |
| /*is_video_plane=*/false); |
| } |
| lock_set_->UnlockResources(GenSyncToken()); |
| } |
| EXPECT_EQ(0u, returned_to_child.size()); |
| |
| EXPECT_EQ(2u, resource_provider_->num_resources()); |
| |
| resource_provider_->DestroyChild(child_id); |
| |
| EXPECT_EQ(0u, resource_provider_->num_resources()); |
| EXPECT_EQ(2u, returned_to_child.size()); |
| |
| // id1 should be lost and id2 should not. |
| EXPECT_EQ(returned_to_child[0].lost, returned_to_child[0].id == id1); |
| EXPECT_EQ(returned_to_child[1].lost, returned_to_child[1].id == id1); |
| |
| child_resource_provider_->ReceiveReturnsFromParent( |
| std::move(returned_to_child)); |
| EXPECT_CALL(release, Released(_, _)).Times(2); |
| child_resource_provider_->RemoveImportedResource(id1); |
| child_resource_provider_->RemoveImportedResource(id2); |
| } |
| |
| // Test that ScopedBatchReturnResources batching works. |
| TEST_F(DisplayResourceProviderSkiaTest, |
| ScopedBatchReturnResourcesPreventsReturn) { |
| MockReleaseCallback release; |
| |
| std::vector<ReturnedResource> returned_to_child; |
| int child_id = resource_provider_->CreateChild( |
| GetReturnCallback(&returned_to_child), SurfaceId()); |
| |
| // Transfer some resources to the parent. |
| constexpr size_t kTotalResources = 5; |
| constexpr size_t kLockedResources = 3; |
| constexpr size_t kUsedResources = 4; |
| ResourceId ids[kTotalResources]; |
| for (auto& id : ids) { |
| TransferableResource tran = CreateResource(RGBA_8888); |
| id = child_resource_provider_->ImportResource( |
| tran, base::BindOnce(&MockReleaseCallback::Released, |
| base::Unretained(&release))); |
| } |
| std::vector<ResourceId> resource_ids_to_transfer(ids, ids + kTotalResources); |
| |
| std::vector<TransferableResource> list; |
| child_resource_provider_->PrepareSendToParent( |
| resource_ids_to_transfer, &list, |
| static_cast<RasterContextProvider*>(child_context_provider_.get())); |
| ASSERT_EQ(kTotalResources, list.size()); |
| for (const auto& id : ids) |
| EXPECT_TRUE(child_resource_provider_->InUseByConsumer(id)); |
| |
| resource_provider_->ReceiveFromChild(child_id, list); |
| |
| // In DisplayResourceProvider's namespace, use the mapped resource id. |
| std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map = |
| resource_provider_->GetChildToParentMap(child_id); |
| std::vector< |
| std::unique_ptr<DisplayResourceProvider::ScopedReadLockSharedImage>> |
| read_locks; |
| for (size_t i = 0; i < kLockedResources; i++) { |
| ResourceId mapped_resource_id = resource_map[ids[i]]; |
| lock_set_->LockResource(mapped_resource_id, /*maybe_concurrent_reads=*/true, |
| /*is_video_plane=*/false); |
| } |
| |
| // Mark all locked resources, and one unlocked resource as used for first |
| // batch. |
| { |
| DisplayResourceProvider::ScopedBatchReturnResources returner( |
| resource_provider_.get()); |
| resource_provider_->DeclareUsedResourcesFromChild( |
| child_id, ResourceIdSet(ids, ids + kUsedResources)); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| } |
| EXPECT_EQ(1u, returned_to_child.size()); |
| child_resource_provider_->ReceiveReturnsFromParent( |
| std::move(returned_to_child)); |
| returned_to_child.clear(); |
| |
| // Return all locked resources. |
| { |
| DisplayResourceProvider::ScopedBatchReturnResources returner( |
| resource_provider_.get()); |
| resource_provider_->DeclareUsedResourcesFromChild( |
| child_id, ResourceIdSet(ids + kLockedResources, ids + kUsedResources)); |
| // Can be called multiple times while batching is enabled. This happens in |
| // practice when the same surface is visited using different paths during |
| // surface aggregation. |
| resource_provider_->DeclareUsedResourcesFromChild( |
| child_id, ResourceIdSet(ids + kLockedResources, ids + kUsedResources)); |
| lock_set_->UnlockResources(GenSyncToken()); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| } |
| EXPECT_EQ(kLockedResources, returned_to_child.size()); |
| // Returned resources that were locked share the same sync token. |
| for (const auto& resource : returned_to_child) |
| EXPECT_EQ(resource.sync_token, returned_to_child[0].sync_token); |
| |
| child_resource_provider_->ReceiveReturnsFromParent( |
| std::move(returned_to_child)); |
| returned_to_child.clear(); |
| |
| // Returns from destroying the child is also batched. |
| { |
| DisplayResourceProvider::ScopedBatchReturnResources returner( |
| resource_provider_.get()); |
| resource_provider_->DestroyChild(child_id); |
| EXPECT_EQ(0u, returned_to_child.size()); |
| } |
| EXPECT_EQ(1u, returned_to_child.size()); |
| child_resource_provider_->ReceiveReturnsFromParent( |
| std::move(returned_to_child)); |
| returned_to_child.clear(); |
| |
| EXPECT_CALL(release, Released(_, _)).Times(kTotalResources); |
| for (const auto& id : ids) |
| child_resource_provider_->RemoveImportedResource(id); |
| } |
| |
| } // namespace |
| } // namespace viz |