blob: 3e3e304a97359aa5642188f65aaa1aeea884f2ef [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// 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 <optional>
#include <set>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.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/common/resources/transferable_resource.h"
#include "components/viz/test/test_context_provider.h"
#include "gpu/command_buffer/client/client_shared_image.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 "third_party/skia/include/core/SkColorSpace.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/gpu_fence_handle.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_METHOD4(
CreateImageContext,
std::unique_ptr<ImageContext>(const TransferableResource& resource,
bool,
bool,
uint32_t));
};
class DisplayResourceProviderSkiaTest : public testing::Test {
public:
DisplayResourceProviderSkiaTest() {
child_context_provider_ = TestContextProvider::Create();
child_context_provider_->BindToCurrentSequence();
child_resource_provider_ = std::make_unique<ClientResourceProvider>();
}
~DisplayResourceProviderSkiaTest() override {
child_resource_provider_->ShutdownAndReleaseAllResources();
}
void SetUp() override {
resource_provider_ = std::make_unique<DisplayResourceProviderSkia>();
lock_set_.emplace(resource_provider_.get(), &client_);
}
void TearDown() override {
resource_provider_.reset();
lock_set_.reset();
}
static ReturnCallback GetReturnCallback(
std::vector<ReturnedResource>* array) {
return base::BindRepeating(&CollectResources, array);
}
TransferableResource CreateResource() {
gpu::SyncToken sync_token = GenSyncToken();
EXPECT_TRUE(sync_token.HasData());
return TransferableResource::Make(
gpu::ClientSharedImage::CreateForTesting(),
TransferableResource::ResourceSource::kTest, sync_token);
}
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_;
std::optional<DisplayResourceProviderSkia::LockSetForExternalUse> lock_set_;
};
TEST_F(DisplayResourceProviderSkiaTest, LockForExternalUse) {
auto gl_resource = CreateResource();
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];
EXPECT_CALL(client_, CreateImageContext(_, _, _, _))
.WillOnce([&](const TransferableResource& resource,
bool maybe_concurrent_reads, bool raw_draw_if_possible,
uint32_t client_id) {
return std::make_unique<ExternalUseClient::ImageContext>(resource);
});
ExternalUseClient::ImageContext* locked_image_context =
lock_set_->LockResource(parent_id, /*maybe_concurrent_reads=*/true,
/*is_video_plane=*/false);
ASSERT_EQ(locked_image_context->mailbox(), gl_resource.mailbox());
ASSERT_EQ(locked_image_context->sync_token(), gl_resource.sync_token());
// 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 = GenSyncToken();
gpu::SyncToken sync_token3 = GenSyncToken();
// 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) {
auto gl_resource = CreateResource();
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];
EXPECT_CALL(client_, CreateImageContext(_, _, _, _))
.WillOnce([&](const TransferableResource& resource,
bool maybe_concurrent_reads, bool raw_draw_if_possible,
uint32_t client_id) {
return std::make_unique<ExternalUseClient::ImageContext>(resource);
});
ExternalUseClient::ImageContext* locked_image_context =
lock_set_->LockResource(parent_id, /*maybe_concurrent_reads=*/true,
/*is_video_plane=*/false);
ASSERT_EQ(gl_resource.mailbox(), locked_image_context->mailbox());
ASSERT_EQ(gl_resource.sync_token(), locked_image_context->sync_token());
// 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 = GenSyncToken();
gpu::SyncToken sync_token3 = GenSyncToken();
// 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 TestGpuCommandsCompletedFence : public ResourceFence {
public:
explicit TestGpuCommandsCompletedFence(
DisplayResourceProviderSkia* resource_provider)
: ResourceFence(resource_provider) {}
// ResourceFence implementation.
bool HasPassed() override { return passed_; }
gfx::GpuFenceHandle GetGpuFenceHandle() override { NOTREACHED(); }
void Signal() {
passed_ = true;
FencePassed();
}
private:
~TestGpuCommandsCompletedFence() override = default;
bool passed_ = false;
base::WeakPtr<DisplayResourceProvider> resource_provider_;
};
class TestReleaseFence : public ResourceFence {
public:
explicit TestReleaseFence(DisplayResourceProviderSkia* resource_provider)
: ResourceFence(resource_provider) {}
// ResourceFence implementation.
bool HasPassed() override { return release_fence_.has_value(); }
gfx::GpuFenceHandle GetGpuFenceHandle() override {
return HasPassed() ? release_fence_->Clone() : gfx::GpuFenceHandle();
}
void SetReleaseFence(gfx::GpuFenceHandle release_fence) {
release_fence_ = std::move(release_fence);
FencePassed();
}
private:
~TestReleaseFence() override = default;
std::optional<gfx::GpuFenceHandle> release_fence_;
base::WeakPtr<DisplayResourceProvider> resource_provider_;
};
TEST_F(DisplayResourceProviderSkiaTest,
ResourceFenceStopsReturnToChildOrDelete) {
const std::vector<TransferableResource::SynchronizationType>
kSynchronizationTypes = {
TransferableResource::SynchronizationType::kGpuCommandsCompleted,
TransferableResource::SynchronizationType::kReleaseFence};
for (auto sync_type : kSynchronizationTypes) {
MockReleaseCallback release;
TransferableResource tran1 = CreateResource();
tran1.synchronization_type = sync_type;
ResourceId id1 = child_resource_provider_->ImportResource(
tran1, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
TransferableResource tran2 = CreateResource();
ASSERT_EQ(tran2.synchronization_type,
TransferableResource::SynchronizationType::kSyncToken);
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<ResourceFence> fence;
TestGpuCommandsCompletedFence* gpu_commands_completed_fence = nullptr;
TestReleaseFence* release_fence = nullptr;
if (sync_type ==
TransferableResource::SynchronizationType::kGpuCommandsCompleted) {
fence = base::MakeRefCounted<TestGpuCommandsCompletedFence>(
resource_provider_.get());
gpu_commands_completed_fence =
static_cast<TestGpuCommandsCompletedFence*>(fence.get());
resource_provider_->SetGpuCommandsCompletedFence(fence.get());
} else {
ASSERT_EQ(TransferableResource::SynchronizationType::kReleaseFence,
sync_type);
fence = base::MakeRefCounted<TestReleaseFence>(resource_provider_.get());
release_fence = static_cast<TestReleaseFence*>(fence.get());
resource_provider_->SetReleaseFence(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_->DeclareUsedResourcesFromChild(child_id,
ResourceIdSet());
EXPECT_EQ(1u, resource_provider_->num_resources());
EXPECT_EQ(1u, returned_to_child.size());
// Signalling the resource fence should return the resources automatically.
if (gpu_commands_completed_fence) {
gpu_commands_completed_fence->Signal();
} else {
gfx::GpuFenceHandle fake_handle;
#if BUILDFLAG(IS_POSIX)
const int32_t kFenceFd = dup(1);
fake_handle.Adopt(base::ScopedFD(kFenceFd));
#endif
release_fence->SetReleaseFence(std::move(fake_handle));
}
EXPECT_EQ(0u, resource_provider_->num_resources());
EXPECT_EQ(2u, returned_to_child.size());
#if BUILDFLAG(IS_POSIX)
EXPECT_TRUE(returned_to_child[0].release_fence.is_null());
if (release_fence)
EXPECT_FALSE(returned_to_child[1].release_fence.is_null());
else
EXPECT_TRUE(returned_to_child[1].release_fence.is_null());
#endif
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_F(DisplayResourceProviderSkiaTest, ResourceFenceDestroyChild) {
const std::vector<TransferableResource::SynchronizationType>
kSynchronizationTypes = {
TransferableResource::SynchronizationType::kGpuCommandsCompleted,
TransferableResource::SynchronizationType::kReleaseFence};
for (auto sync_type : kSynchronizationTypes) {
MockReleaseCallback release;
TransferableResource tran1 = CreateResource();
tran1.synchronization_type = sync_type;
ResourceId id1 = child_resource_provider_->ImportResource(
tran1, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
TransferableResource tran2 = CreateResource();
ASSERT_EQ(tran2.synchronization_type,
TransferableResource::SynchronizationType::kSyncToken);
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<ResourceFence> fence;
TestGpuCommandsCompletedFence* gpu_commands_completed_fence = nullptr;
TestReleaseFence* release_fence = nullptr;
if (sync_type ==
TransferableResource::SynchronizationType::kGpuCommandsCompleted) {
fence = base::MakeRefCounted<TestGpuCommandsCompletedFence>(
resource_provider_.get());
gpu_commands_completed_fence =
static_cast<TestGpuCommandsCompletedFence*>(fence.get());
resource_provider_->SetGpuCommandsCompletedFence(fence.get());
} else {
ASSERT_EQ(TransferableResource::SynchronizationType::kReleaseFence,
sync_type);
fence = base::MakeRefCounted<TestReleaseFence>(resource_provider_.get());
release_fence = static_cast<TestReleaseFence*>(fence.get());
resource_provider_->SetReleaseFence(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);
// fence signalling should be noop.
if (gpu_commands_completed_fence) {
gpu_commands_completed_fence->Signal();
} else {
gfx::GpuFenceHandle fake_handle;
#if BUILDFLAG(IS_POSIX)
const int32_t kFenceFd = dup(1);
fake_handle.Adopt(base::ScopedFD(kFenceFd));
#endif
release_fence->SetReleaseFence(std::move(fake_handle));
}
EXPECT_EQ(0u, resource_provider_->num_resources());
EXPECT_EQ(2u, returned_to_child.size());
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_F(DisplayResourceProviderSkiaTest, ResourceFenceOutlivesResourceProvider) {
MockReleaseCallback release;
TransferableResource tran1 = CreateResource();
tran1.synchronization_type =
TransferableResource::SynchronizationType::kGpuCommandsCompleted;
ResourceId id1 = child_resource_provider_->ImportResource(
tran1, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
TransferableResource tran2 = CreateResource();
tran2.synchronization_type =
TransferableResource::SynchronizationType::kReleaseFence;
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<TestGpuCommandsCompletedFence> gpu_commands_completed_fence =
base::MakeRefCounted<TestGpuCommandsCompletedFence>(
resource_provider_.get());
resource_provider_->SetGpuCommandsCompletedFence(
gpu_commands_completed_fence.get());
scoped_refptr<TestReleaseFence> release_fence =
base::MakeRefCounted<TestReleaseFence>(resource_provider_.get());
resource_provider_->SetReleaseFence(release_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_->DeclareUsedResourcesFromChild(child_id, ResourceIdSet());
EXPECT_EQ(0u, returned_to_child.size());
EXPECT_EQ(2u, resource_provider_->num_resources());
resource_provider_.reset();
EXPECT_EQ(2u, returned_to_child.size());
// Signalling the dangling resource fence should not crash.
if (gpu_commands_completed_fence) {
gpu_commands_completed_fence->Signal();
} else {
gfx::GpuFenceHandle fake_handle;
#if BUILDFLAG(IS_POSIX)
const int32_t kFenceFd = dup(1);
fake_handle.Adopt(base::ScopedFD(kFenceFd));
#endif
release_fence->SetReleaseFence(std::move(fake_handle));
}
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();
id = child_resource_provider_->ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
}
std::vector<ResourceId> resource_ids_to_transfer(
ids, UNSAFE_TODO(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[UNSAFE_TODO(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, UNSAFE_TODO(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(UNSAFE_TODO(ids + kLockedResources),
UNSAFE_TODO(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(UNSAFE_TODO(ids + kLockedResources),
UNSAFE_TODO(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