blob: bab7b181f1480f1b7ddf0c598c978f73a30a55de [file] [log] [blame]
// Copyright 2017 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/client/client_resource_provider.h"
#include <algorithm>
#include <array>
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "components/viz/common/features.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 "components/viz/test/viz_test_suite.h"
#include "gpu/command_buffer/client/client_shared_image.h"
#include "gpu/command_buffer/client/raster_interface.h"
#include "gpu/command_buffer/common/shared_image_usage.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Each;
namespace viz {
namespace {
// Mocks ResourceFlushCallback
class MockFlushCallback {
public:
MOCK_METHOD0(FlushCallback, void());
};
class ClientResourceProviderTest : public testing::TestWithParam<bool> {
protected:
ClientResourceProviderTest()
: use_gpu_(GetParam()),
context_provider_(TestContextProvider::Create()),
bound_(context_provider_->BindToCurrentSequence()) {
DCHECK_EQ(bound_, gpu::ContextResult::kSuccess);
}
// Some tests want to override feature flags that are checked at construction
// time. Allow them to recreate the provider.
void InitProvider() {
// VizTestSuite::task_environment is set for us. We use the default
// TaskRunner for the Main thread. We then create a second TaskRunner which
// is built on a separate thread for the Compositor thread.
provider_ = std::make_unique<ClientResourceProvider>(
base::SingleThreadTaskRunner::GetCurrentDefault(),
base::ThreadPool::CreateSequencedTaskRunner({}),
base::BindRepeating(&MockFlushCallback::FlushCallback,
base::Unretained(&mock_flush_callback_)));
}
void SetUp() override { InitProvider(); }
void TearDown() override { provider_ = nullptr; }
gpu::SyncToken GenSyncToken() {
static int next_release = 1;
return gpu::SyncToken(gpu::CommandBufferNamespace::GPU_IO,
gpu::CommandBufferId::FromUnsafeValue(0x123),
next_release++);
}
TransferableResource MakeTransferableResource() {
auto shared_image =
use_gpu() ? gpu::ClientSharedImage::CreateForTesting()
: gpu::ClientSharedImage::CreateSoftwareForTesting();
return TransferableResource::Make(
std::move(shared_image), TransferableResource::ResourceSource::kTest,
GenSyncToken());
}
bool use_gpu() const { return use_gpu_; }
ClientResourceProvider& provider() const { return *provider_; }
RasterContextProvider* context_provider() const {
return context_provider_.get();
}
void DestroyProvider() {
provider_->ShutdownAndReleaseAllResources();
provider_ = nullptr;
}
void ExpectFlush() { EXPECT_CALL(mock_flush_callback_, FlushCallback()); }
private:
MockFlushCallback mock_flush_callback_;
bool use_gpu_;
scoped_refptr<TestContextProvider> context_provider_;
gpu::ContextResult bound_;
std::unique_ptr<ClientResourceProvider> provider_;
};
INSTANTIATE_TEST_SUITE_P(ClientResourceProviderTests,
ClientResourceProviderTest,
::testing::Values(false, true));
class MockReleaseCallback {
public:
MOCK_METHOD2(Released, void(const gpu::SyncToken& token, bool lost));
MOCK_METHOD3(ReleasedWithId,
void(ResourceId id, const gpu::SyncToken& token, bool lost));
MOCK_METHOD0(Evicted, void());
};
TEST_P(ClientResourceProviderTest, TransferableResourceReleased) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId id = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// The local id is different.
EXPECT_NE(id, tran.id);
// The same SyncToken that was sent is returned when the resource was never
// exported. The SyncToken may be from any context, and the ReleaseCallback
// may need to wait on it before interacting with the resource on its context.
EXPECT_CALL(release, Released(tran.sync_token(), false));
provider().RemoveImportedResource(id);
}
TEST_P(ClientResourceProviderTest, TransferableResourceSendToParent) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId id = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Export the resource.
std::vector<ResourceId> to_send = {id};
std::vector<TransferableResource> exported;
provider().PrepareSendToParent(to_send, &exported, context_provider());
ASSERT_EQ(exported.size(), 1u);
// Exported resource matches except for the id which was mapped
// to the local ResourceProvider, and the sync token should be
// verified if it's a gpu resource.
gpu::SyncToken verified_sync_token = tran.sync_token();
if (!tran.is_software)
verified_sync_token.SetVerifyFlush();
EXPECT_EQ(exported[0].id, id);
EXPECT_EQ(exported[0].is_software, tran.is_software);
EXPECT_EQ(exported[0].size, tran.size);
EXPECT_EQ(exported[0].mailbox(), tran.mailbox());
EXPECT_EQ(exported[0].sync_token(), verified_sync_token);
EXPECT_EQ(exported[0].texture_target(), tran.texture_target());
// Exported resources are not released when removed, until the export returns.
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().RemoveImportedResource(id);
// Return the resource, with a sync token if using gpu.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
returned.back().sync_token = GenSyncToken();
returned.back().count = 1;
returned.back().lost = false;
// The sync token is given to the ReleaseCallback.
EXPECT_CALL(release, Released(returned[0].sync_token, false));
provider().ReceiveReturnsFromParent(std::move(returned));
}
TEST_P(ClientResourceProviderTest, TransferableResourceSendTwoToParent) {
auto tran = std::to_array<TransferableResource>(
{MakeTransferableResource(), MakeTransferableResource()});
ResourceId id1 = provider().ImportResource(tran[0], base::DoNothing());
ResourceId id2 = provider().ImportResource(tran[1], base::DoNothing());
// Export the resource.
std::vector<ResourceId> to_send = {id1, id2};
std::vector<TransferableResource> exported;
provider().PrepareSendToParent(to_send, &exported, context_provider());
ASSERT_EQ(exported.size(), 2u);
// Exported resource matches except for the id which was mapped
// to the local ResourceProvider, and the sync token should be
// verified if it's a gpu resource.
for (int i = 0; i < 2; ++i) {
gpu::SyncToken verified_sync_token = tran[i].sync_token();
if (!tran[i].is_software)
verified_sync_token.SetVerifyFlush();
EXPECT_EQ(exported[i].id, to_send[i]);
EXPECT_EQ(exported[i].is_software, tran[i].is_software);
EXPECT_EQ(exported[i].size, tran[i].size);
EXPECT_EQ(exported[i].mailbox(), tran[i].mailbox());
EXPECT_EQ(exported[i].sync_token(), verified_sync_token);
EXPECT_EQ(exported[i].texture_target(), tran[i].texture_target());
}
provider().RemoveImportedResource(id1);
provider().RemoveImportedResource(id2);
DestroyProvider();
}
TEST_P(ClientResourceProviderTest, TransferableResourceSendToParentTwoTimes) {
TransferableResource tran = MakeTransferableResource();
ResourceId id = provider().ImportResource(tran, base::DoNothing());
// Export the resource.
std::vector<ResourceId> to_send = {id};
std::vector<TransferableResource> exported;
provider().PrepareSendToParent(to_send, &exported, context_provider());
ASSERT_EQ(exported.size(), 1u);
EXPECT_EQ(exported[0].id, id);
// Return the resource, with a sync token if using gpu.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
if (use_gpu())
returned.back().sync_token = GenSyncToken();
returned.back().count = 1;
returned.back().lost = false;
provider().ReceiveReturnsFromParent(std::move(returned));
// Then export again, it still sends.
exported.clear();
provider().PrepareSendToParent(to_send, &exported, context_provider());
ASSERT_EQ(exported.size(), 1u);
EXPECT_EQ(exported[0].id, id);
provider().RemoveImportedResource(id);
DestroyProvider();
}
TEST_P(ClientResourceProviderTest,
TransferableResourceLostOnShutdownIfExported) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId id = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Export the resource.
std::vector<ResourceId> to_send = {id};
std::vector<TransferableResource> exported;
provider().PrepareSendToParent(to_send, &exported, context_provider());
provider().RemoveImportedResource(id);
EXPECT_CALL(release, Released(_, true));
DestroyProvider();
}
TEST_P(ClientResourceProviderTest, TransferableResourceSendToParentManyUnsent) {
MockReleaseCallback release;
// 5 resources to have 2 before and 2 after the one being sent and returned,
// to verify the data structures are treated correctly when removing from the
// middle. 1 after is not enough as that one will replace the resource being
// removed and misses some edge cases. We want another resource after that in
// the structure to verify it is also not treated incorrectly.
struct Data {
TransferableResource tran;
ResourceId id;
};
std::array<Data, 5> data;
for (int i = 0; i < 5; ++i) {
data[i].tran = MakeTransferableResource();
data[i].id = provider().ImportResource(
data[i].tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
}
std::sort(std::begin(data), std::end(data),
[](const Data& a, const Data& b) { return a.id < b.id; });
// Export the resource.
std::vector<ResourceId> to_send = {data[2].id};
std::vector<TransferableResource> exported;
provider().PrepareSendToParent(to_send, &exported, context_provider());
ASSERT_EQ(exported.size(), 1u);
// Exported resource matches except for the id which was mapped
// to the local ResourceProvider, and the sync token should be
// verified if it's a gpu resource.
gpu::SyncToken verified_sync_token = data[2].tran.sync_token();
if (!data[2].tran.is_software)
verified_sync_token.SetVerifyFlush();
// Exported resources are not released when removed, until the export returns.
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().RemoveImportedResource(data[2].id);
// Return the resource, with a sync token if using gpu.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
returned.back().sync_token = GenSyncToken();
returned.back().count = 1;
returned.back().lost = false;
// The sync token is given to the ReleaseCallback.
EXPECT_CALL(release, Released(returned[0].sync_token, false));
provider().ReceiveReturnsFromParent(std::move(returned));
EXPECT_CALL(release, Released(_, false)).Times(4);
provider().RemoveImportedResource(data[0].id);
provider().RemoveImportedResource(data[1].id);
provider().RemoveImportedResource(data[3].id);
provider().RemoveImportedResource(data[4].id);
}
TEST_P(ClientResourceProviderTest, TransferableResourceRemovedAfterReturn) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId id = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Export the resource.
std::vector<ResourceId> to_send = {id};
std::vector<TransferableResource> exported;
provider().PrepareSendToParent(to_send, &exported, context_provider());
// Return the resource. This does not release the resource back to
// the client.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
returned.back().sync_token = GenSyncToken();
returned.back().count = 1;
returned.back().lost = false;
EXPECT_CALL(release, Released(_, _)).Times(0);
auto sync_token = returned.back().sync_token;
provider().ReceiveReturnsFromParent(std::move(returned));
testing::Mock::VerifyAndClearExpectations(&release);
// Once removed, the resource is released.
EXPECT_CALL(release, Released(sync_token, false));
provider().RemoveImportedResource(id);
}
TEST_P(ClientResourceProviderTest, TransferableResourceExportedTwice) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId id = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Export the resource once.
std::vector<ResourceId> to_send = {id};
std::vector<TransferableResource> exported;
provider().PrepareSendToParent(to_send, &exported, context_provider());
// Exported resources are not released when removed, until all exports are
// returned.
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().RemoveImportedResource(id);
// Export the resource twice.
exported = {};
provider().PrepareSendToParent(to_send, &exported, context_provider());
{
// Return the resource the first time.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
if (use_gpu())
returned.back().sync_token = GenSyncToken();
returned.back().count = 1;
returned.back().lost = false;
provider().ReceiveReturnsFromParent(std::move(returned));
}
{
// And a second time, with a different sync token. Now the ReleaseCallback
// can happen, using the latest sync token.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
returned.back().sync_token = GenSyncToken();
returned.back().count = 1;
returned.back().lost = false;
EXPECT_CALL(release, Released(returned[0].sync_token, false));
provider().ReceiveReturnsFromParent(std::move(returned));
}
}
TEST_P(ClientResourceProviderTest, TransferableResourceReturnedTwiceAtOnce) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId id = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Export the resource once.
std::vector<ResourceId> to_send = {id};
std::vector<TransferableResource> exported;
provider().PrepareSendToParent(to_send, &exported, context_provider());
// Exported resources are not released when removed, until all exports are
// returned.
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().RemoveImportedResource(id);
// Export the resource twice.
exported = {};
provider().PrepareSendToParent(to_send, &exported, context_provider());
// Return both exports at once.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
returned.back().sync_token = GenSyncToken();
returned.back().count = 2;
returned.back().lost = false;
// When returned, the ReleaseCallback can happen, using the latest sync token.
EXPECT_CALL(release, Released(returned[0].sync_token, false));
provider().ReceiveReturnsFromParent(std::move(returned));
}
TEST_P(ClientResourceProviderTest, TransferableResourceLostOnReturn) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId id = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Export the resource once.
std::vector<ResourceId> to_send = {id};
std::vector<TransferableResource> exported;
provider().PrepareSendToParent(to_send, &exported, context_provider());
// Exported resources are not released when removed, until all exports are
// returned.
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().RemoveImportedResource(id);
// Export the resource twice.
exported = {};
provider().PrepareSendToParent(to_send, &exported, context_provider());
{
// Return the resource the first time, not lost.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
returned.back().count = 1;
returned.back().lost = false;
provider().ReceiveReturnsFromParent(std::move(returned));
}
{
// Return a second time, as lost. The ReturnCallback should report it
// lost.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
returned.back().count = 1;
returned.back().lost = true;
EXPECT_CALL(release, Released(_, true));
provider().ReceiveReturnsFromParent(std::move(returned));
}
}
TEST_P(ClientResourceProviderTest, TransferableResourceLostOnFirstReturn) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId id = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Export the resource once.
std::vector<ResourceId> to_send = {id};
std::vector<TransferableResource> exported;
provider().PrepareSendToParent(to_send, &exported, context_provider());
// Exported resources are not released when removed, until all exports are
// returned.
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().RemoveImportedResource(id);
// Export the resource twice.
exported = {};
provider().PrepareSendToParent(to_send, &exported, context_provider());
{
// Return the resource the first time, marked as lost.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
returned.back().count = 1;
returned.back().lost = true;
provider().ReceiveReturnsFromParent(std::move(returned));
}
{
// Return a second time, not lost. The first lost signal should not be lost.
std::vector<ReturnedResource> returned;
returned.emplace_back();
returned.back().id = exported[0].id;
returned.back().count = 1;
returned.back().lost = false;
EXPECT_CALL(release, Released(_, true));
provider().ReceiveReturnsFromParent(std::move(returned));
}
}
TEST_P(ClientResourceProviderTest, ReturnedSyncTokensArePassedToClient) {
// SyncTokens are gpu-only.
if (!use_gpu())
return;
MockReleaseCallback release;
auto* sii = context_provider()->SharedImageInterface();
// NOTE: The parameters passed below are largely arbitrary, as the contents of
// the SharedImage are not actually accessed in any way in this test.
// Nonetheless, set SharedImage usage to model an SI that is written via
// raster and then read by the display compositor (e.g., for canvas), in order
// to match a use case that would actually be put into a TransferableResource
// in production.
auto shared_image = sii->CreateSharedImage(
{SinglePlaneFormat::kRGBA_8888, gfx::Size(1, 1), gfx::ColorSpace(),
gpu::SHARED_IMAGE_USAGE_RASTER_WRITE |
gpu::SHARED_IMAGE_USAGE_DISPLAY_READ,
"TestLabel"},
gpu::kNullSurfaceHandle);
auto tran = TransferableResource::Make(
shared_image, TransferableResource::ResourceSource::kTest,
shared_image->creation_sync_token());
ResourceId resource = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
EXPECT_TRUE(tran.sync_token().HasData());
// All the logic below assumes that the sync token releases are all positive.
EXPECT_LT(0u, tran.sync_token().release_count());
// Transfer the resource, expect the sync points to be consistent.
std::vector<TransferableResource> list;
provider().PrepareSendToParent({resource}, &list, context_provider());
ASSERT_EQ(1u, list.size());
// PrepareSendToParent supposed to verify SyncToken.
auto verified_sync_token = tran.sync_token();
verified_sync_token.SetVerifyFlush();
EXPECT_EQ(verified_sync_token, list[0].sync_token());
EXPECT_EQ(shared_image->mailbox(), list[0].mailbox());
// Receive the resource, then delete it, expect the SyncTokens to be
// consistent.
provider().ReceiveReturnsFromParent(
TransferableResource::ReturnResources(list));
gpu::SyncToken returned_sync_token;
EXPECT_CALL(release, Released(_, false))
.WillOnce(testing::SaveArg<0>(&returned_sync_token));
provider().RemoveImportedResource(resource);
EXPECT_EQ(returned_sync_token.release_count(),
list[0].sync_token().release_count());
}
TEST_P(ClientResourceProviderTest, LostResourcesAreReturnedLost) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId resource = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Transfer the resource to the parent.
std::vector<TransferableResource> list;
provider().PrepareSendToParent({resource}, &list, context_provider());
EXPECT_EQ(1u, list.size());
// Receive it back marked lost.
std::vector<ReturnedResource> returned_to_child;
returned_to_child.push_back(list[0].ToReturnedResource());
returned_to_child.back().lost = true;
provider().ReceiveReturnsFromParent(std::move(returned_to_child));
// Delete the resource in the child. Expect the resource to be lost.
EXPECT_CALL(release, Released(_, true));
provider().RemoveImportedResource(resource);
}
TEST_P(ClientResourceProviderTest, ShutdownLosesExportedResources) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId resource = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Transfer the resource to the parent.
std::vector<TransferableResource> list;
provider().PrepareSendToParent({resource}, &list, context_provider());
EXPECT_EQ(1u, list.size());
// Remove it in the ClientResourceProvider, but since it's exported it's not
// returned yet.
provider().RemoveImportedResource(resource);
// Destroy the ClientResourceProvider, the resource is returned lost.
EXPECT_CALL(release, Released(_, true));
DestroyProvider();
}
TEST_P(ClientResourceProviderTest, ReleaseExportedResources) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId resource = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Transfer the resource to the parent.
std::vector<TransferableResource> list;
provider().PrepareSendToParent({resource}, &list, context_provider());
EXPECT_EQ(1u, list.size());
// Remove it in the ClientResourceProvider, but since it's exported it's not
// returned yet.
provider().RemoveImportedResource(resource);
// Drop any exported resources. They are returned lost for gpu compositing,
// since gpu resources are modified (in their metadata) while being used by
// the parent.
EXPECT_CALL(release, Released(_, use_gpu()));
provider().ReleaseAllExportedResources(use_gpu());
EXPECT_CALL(release, Released(_, _)).Times(0);
}
TEST_P(ClientResourceProviderTest, ReleaseExportedResourcesThenRemove) {
MockReleaseCallback release;
TransferableResource tran = MakeTransferableResource();
ResourceId resource = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)));
// Transfer the resource to the parent.
std::vector<TransferableResource> list;
provider().PrepareSendToParent({resource}, &list, context_provider());
EXPECT_EQ(1u, list.size());
// Drop any exported resources. They are now considered lost for gpu
// compositing, since gpu resources are modified (in their metadata) while
// being used by the parent.
provider().ReleaseAllExportedResources(use_gpu());
EXPECT_CALL(release, Released(_, use_gpu()));
// Remove it in the ClientResourceProvider, it was exported so wouldn't be
// released here, except that we dropped the export above.
provider().RemoveImportedResource(resource);
EXPECT_CALL(release, Released(_, _)).Times(0);
}
TEST_P(ClientResourceProviderTest, ReleaseMultipleResources) {
MockReleaseCallback release;
// Make 5 resources, put them in a non-sorted order.
std::array<ResourceId, 5> resources;
for (int i = 0; i < 5; ++i) {
TransferableResource tran = MakeTransferableResource();
resources[i] = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::ReleasedWithId,
base::Unretained(&release), ResourceId(i)));
}
// Transfer some resources to the parent, but not in the sorted order.
std::vector<TransferableResource> list;
provider().PrepareSendToParent({resources[2], resources[0], resources[4]},
&list, context_provider());
EXPECT_EQ(3u, list.size());
// Receive them back. Since these are not in the same order they were
// inserted originally, we verify the ClientResourceProvider can handle
// resources being returned (in a group) that are not at the front/back
// of its internal storage. IOW This shouldn't crash or corrupt state.
std::vector<ReturnedResource> returned_to_child;
returned_to_child.push_back(list[0].ToReturnedResource());
returned_to_child.push_back(list[1].ToReturnedResource());
returned_to_child.push_back(list[2].ToReturnedResource());
provider().ReceiveReturnsFromParent(std::move(returned_to_child));
// Remove them from the ClientResourceProvider, they should be returned as
// they're no longer exported.
EXPECT_CALL(release, ReleasedWithId(kInvalidResourceId, _, false));
provider().RemoveImportedResource(resources[0]);
EXPECT_CALL(release, ReleasedWithId(ResourceId(2), _, false));
provider().RemoveImportedResource(resources[2]);
EXPECT_CALL(release, ReleasedWithId(ResourceId(4), _, false));
provider().RemoveImportedResource(resources[4]);
// These were never exported.
EXPECT_CALL(release, ReleasedWithId(ResourceId(1), _, false));
EXPECT_CALL(release, ReleasedWithId(ResourceId(3), _, false));
provider().RemoveImportedResource(resources[1]);
provider().RemoveImportedResource(resources[3]);
EXPECT_CALL(release, Released(_, false)).Times(0);
}
TEST_P(ClientResourceProviderTest, ReleaseMultipleResourcesBeforeReturn) {
MockReleaseCallback release;
// Make 5 resources, put them in a non-sorted order.
std::array<ResourceId, 5> resources;
for (int i = 0; i < 5; ++i) {
TransferableResource tran = MakeTransferableResource();
resources[i] = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::ReleasedWithId,
base::Unretained(&release), ResourceId(i)));
}
// Transfer some resources to the parent, but not in the sorted order.
std::vector<TransferableResource> list;
provider().PrepareSendToParent({resources[2], resources[0], resources[4]},
&list, context_provider());
EXPECT_EQ(3u, list.size());
// Remove the exported resources from the ClientResourceProvider, they should
// not yet be returned, since they are exported.
provider().RemoveImportedResource(resources[0]);
provider().RemoveImportedResource(resources[2]);
provider().RemoveImportedResource(resources[4]);
// Receive them back now. Since these are not in the same order they were
// inserted originally, we verify the ClientResourceProvider can handle
// resources being returned (in a group) that are not at the front/back
// of its internal storage. IOW This shouldn't crash or corrupt state.
std::vector<ReturnedResource> returned_to_child;
returned_to_child.push_back(list[0].ToReturnedResource());
returned_to_child.push_back(list[1].ToReturnedResource());
returned_to_child.push_back(list[2].ToReturnedResource());
// The resources are returned immediately since they were previously removed.
EXPECT_CALL(release, ReleasedWithId(kInvalidResourceId, _, false));
EXPECT_CALL(release, ReleasedWithId(ResourceId(2), _, false));
EXPECT_CALL(release, ReleasedWithId(ResourceId(4), _, false));
provider().ReceiveReturnsFromParent(std::move(returned_to_child));
// These were never exported.
EXPECT_CALL(release, ReleasedWithId(ResourceId(1), _, false));
EXPECT_CALL(release, ReleasedWithId(ResourceId(3), _, false));
provider().RemoveImportedResource(resources[1]);
provider().RemoveImportedResource(resources[3]);
EXPECT_CALL(release, Released(_, false)).Times(0);
}
TEST_P(ClientResourceProviderTest, ReturnDuplicateResourceBeforeRemove) {
MockReleaseCallback release;
// Make 5 resources, put them in a non-sorted order.
std::array<ResourceId, 5> resources;
for (int i = 0; i < 5; ++i) {
TransferableResource tran = MakeTransferableResource();
resources[i] = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::ReleasedWithId,
base::Unretained(&release), ResourceId(i)));
}
// Transfer a resource to the parent, do it twice.
std::vector<TransferableResource> list;
provider().PrepareSendToParent({resources[2]}, &list, context_provider());
list.clear();
provider().PrepareSendToParent({resources[2]}, &list, context_provider());
// Receive the resource back. It's possible that the parent may return
// the same ResourceId multiple times in the same message, which we test
// for here.
std::vector<ReturnedResource> returned_to_child;
returned_to_child.push_back(list[0].ToReturnedResource());
returned_to_child.push_back(list[0].ToReturnedResource());
provider().ReceiveReturnsFromParent(std::move(returned_to_child));
// Remove it from the ClientResourceProvider, it should be returned as
// it's no longer exported.
EXPECT_CALL(release, ReleasedWithId(ResourceId(2), _, false));
provider().RemoveImportedResource(resources[2]);
// These were never exported.
EXPECT_CALL(release, ReleasedWithId(kInvalidResourceId, _, false));
EXPECT_CALL(release, ReleasedWithId(ResourceId(1), _, false));
EXPECT_CALL(release, ReleasedWithId(ResourceId(3), _, false));
EXPECT_CALL(release, ReleasedWithId(ResourceId(4), _, false));
provider().RemoveImportedResource(resources[0]);
provider().RemoveImportedResource(resources[1]);
provider().RemoveImportedResource(resources[3]);
provider().RemoveImportedResource(resources[4]);
EXPECT_CALL(release, Released(_, false)).Times(0);
}
TEST_P(ClientResourceProviderTest, ReturnDuplicateResourceAfterRemove) {
MockReleaseCallback release;
// Make 5 resources, put them in a non-sorted order.
std::array<ResourceId, 5> resources;
for (int i = 0; i < 5; ++i) {
TransferableResource tran = MakeTransferableResource();
resources[i] = provider().ImportResource(
tran, base::BindOnce(&MockReleaseCallback::ReleasedWithId,
base::Unretained(&release), ResourceId(i)));
}
// Transfer a resource to the parent, do it twice.
std::vector<TransferableResource> list;
provider().PrepareSendToParent({resources[2]}, &list, context_provider());
list.clear();
provider().PrepareSendToParent({resources[2]}, &list, context_provider());
// Remove it from the ClientResourceProvider, it should not be returned yet
// as it's still exported.
provider().RemoveImportedResource(resources[2]);
// Receive the resource back. It's possible that the parent may return
// the same ResourceId multiple times in the same message, which we test
// for here.
std::vector<ReturnedResource> returned_to_child;
returned_to_child.push_back(list[0].ToReturnedResource());
returned_to_child.push_back(list[0].ToReturnedResource());
// Once no longer exported, since it was removed earlier, it will be returned
// immediately.
EXPECT_CALL(release, ReleasedWithId(ResourceId(2), _, false));
provider().ReceiveReturnsFromParent(std::move(returned_to_child));
// These were never exported.
EXPECT_CALL(release, ReleasedWithId(kInvalidResourceId, _, false));
EXPECT_CALL(release, ReleasedWithId(ResourceId(1), _, false));
EXPECT_CALL(release, ReleasedWithId(ResourceId(3), _, false));
EXPECT_CALL(release, ReleasedWithId(ResourceId(4), _, false));
provider().RemoveImportedResource(resources[0]);
provider().RemoveImportedResource(resources[1]);
provider().RemoveImportedResource(resources[3]);
provider().RemoveImportedResource(resources[4]);
EXPECT_CALL(release, Released(_, false)).Times(0);
}
// Tests that resources which are locked, are released after eviction has
// occurred, once they are also returned.
TEST_P(ClientResourceProviderTest, EvictionUnlocksResources) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures({features::kEvictionUnlocksResources},
{});
// Mark visible so eviction path is not inadvertently triggered.
provider().SetVisible(true);
MockReleaseCallback release;
TransferableResource resource = MakeTransferableResource();
ResourceId id =
provider().ImportResource(resource,
base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)),
ReleaseCallback(),
base::BindOnce(&MockReleaseCallback::Evicted,
base::Unretained(&release)));
std::vector<TransferableResource> list;
provider().PrepareSendToParent({id}, &list, context_provider());
EXPECT_EQ(list.size(), 1u);
EXPECT_EQ(list[0].id, id);
// Eviction alone should not delete the resource, nor have called the Eviction
// callback.
EXPECT_CALL(release, Evicted).Times(0);
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().SetEvicted(true);
EXPECT_EQ(provider().num_resources_for_testing(), 1u);
// Becoming hidden and evicted should trigger the callback. The resource
// should not be deleted yet, until it has bene returned.
EXPECT_CALL(release, Evicted).Times(1);
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().SetVisible(false);
EXPECT_EQ(provider().num_resources_for_testing(), 1u);
// Clients are expected to call RemoveImportedResource in response to Evicted.
// However being exported, should not trigger a Released callback.
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().RemoveImportedResource(id);
// Returning of resources should release the previously locked resource.
std::vector<ReturnedResource> returned;
returned.push_back(list[0].ToReturnedResource());
EXPECT_CALL(release, Released(_, false));
provider().ReceiveReturnsFromParent(std::move(returned));
EXPECT_EQ(provider().num_resources_for_testing(), 0u);
}
// Tests that when resources are returned in the middle of an eviction process,
// that we release any locked resources once the eviction has completed.
TEST_P(ClientResourceProviderTest,
LockedReturnedResourcesReleasedDuringEviction) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures({features::kEvictionUnlocksResources},
{});
// Mark visible so eviction path is not inadvertently triggered.
provider().SetVisible(true);
MockReleaseCallback release;
TransferableResource resource = MakeTransferableResource();
ResourceId id =
provider().ImportResource(resource,
base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)),
ReleaseCallback(),
base::BindOnce(&MockReleaseCallback::Evicted,
base::Unretained(&release)));
std::vector<TransferableResource> list;
provider().PrepareSendToParent({id}, &list, context_provider());
EXPECT_EQ(list.size(), 1u);
EXPECT_EQ(list[0].id, id);
// Eviction alone should not delete the resource, nor have called the Eviction
// callback.
EXPECT_CALL(release, Evicted).Times(0);
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().SetEvicted(true);
EXPECT_EQ(provider().num_resources_for_testing(), 1u);
// Returning of resources will not return them while we are still visible.
std::vector<ReturnedResource> returned;
returned.push_back(list[0].ToReturnedResource());
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().ReceiveReturnsFromParent(std::move(returned));
EXPECT_EQ(provider().num_resources_for_testing(), 1u);
// Becoming hidden and evicted should trigger the callback. The resources
// having previously been returned, should now be released.
EXPECT_CALL(release, Evicted).Times(1);
provider().SetVisible(false);
// Clients are expected to call RemoveImportedResource in response to Evicted.
// Being no longer exported, this should call Released.
EXPECT_CALL(release, Released(_, false));
provider().RemoveImportedResource(id);
EXPECT_EQ(provider().num_resources_for_testing(), 0u);
}
// Tests that when a Client has removed a resource, which has not yet been
// returned, that we do not notify the Client of eviction.
TEST_P(ClientResourceProviderTest, RemovedEvictedResourcesDoNotNotifyClient) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures({features::kEvictionUnlocksResources},
{});
// Mark visible so eviction path is not inadvertently triggered.
provider().SetVisible(true);
MockReleaseCallback release;
TransferableResource resource = MakeTransferableResource();
ResourceId id =
provider().ImportResource(resource,
base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)),
ReleaseCallback(),
base::BindOnce(&MockReleaseCallback::Evicted,
base::Unretained(&release)));
std::vector<TransferableResource> list;
provider().PrepareSendToParent({id}, &list, context_provider());
EXPECT_EQ(list.size(), 1u);
EXPECT_EQ(list[0].id, id);
EXPECT_EQ(provider().num_resources_for_testing(), 1u);
// After removing the resource should still exist, as it has yet to be
// returned.
provider().RemoveImportedResource(id);
EXPECT_EQ(provider().num_resources_for_testing(), 1u);
// Eviction alone should not delete the resource, nor have called the Eviction
// callback.
EXPECT_CALL(release, Evicted).Times(0);
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().SetEvicted(true);
EXPECT_EQ(provider().num_resources_for_testing(), 1u);
// Becoming hidden and evicted would normally call `Evicted` however it
// should not, since the Client removed it earlier. The resource should not be
// deleted yet, until it has bene returned.
EXPECT_CALL(release, Evicted).Times(0);
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().SetVisible(false);
EXPECT_EQ(provider().num_resources_for_testing(), 1u);
// Returning of resources should release the previously locked resource.
std::vector<ReturnedResource> returned;
returned.push_back(list[0].ToReturnedResource());
EXPECT_CALL(release, Released(_, false));
provider().ReceiveReturnsFromParent(std::move(returned));
EXPECT_EQ(provider().num_resources_for_testing(), 0u);
}
// Tests that when we provide Main thread callbacks, that they are ran after
// the resources have returned. Also confirming that we Flush resources once
// callbacks are processed.
TEST_P(ClientResourceProviderTest, EvictionNotifiesMainAndFlushes) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kEvictionUnlocksResources,
features::kBatchMainThreadReleaseCallbacks},
{});
// Recreate to support `features::kBatchMainThreadReleaseCallbacks`.
InitProvider();
// Mark visible so eviction path is not inadvertently triggered.
provider().SetVisible(true);
MockReleaseCallback release;
TransferableResource resource = MakeTransferableResource();
ResourceId id =
provider().ImportResource(resource, ReleaseCallback(),
base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)),
base::BindOnce(&MockReleaseCallback::Evicted,
base::Unretained(&release)));
std::vector<TransferableResource> list;
provider().PrepareSendToParent({id}, &list, context_provider());
EXPECT_EQ(list.size(), 1u);
EXPECT_EQ(list[0].id, id);
// Eviction alone should not delete the resource, nor have called the Eviction
// callback.
EXPECT_CALL(release, Evicted).Times(0);
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().SetEvicted(true);
EXPECT_EQ(provider().num_resources_for_testing(), 1u);
// Becoming hidden and evicted should trigger the callback. The resource
// should not be deleted yet, until it has bene returned.
EXPECT_CALL(release, Evicted).Times(1);
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().SetVisible(false);
EXPECT_EQ(provider().num_resources_for_testing(), 1u);
// Clients are expected to call RemoveImportedResource in response to Evicted.
// However being exported, should not trigger a Released callback.
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().RemoveImportedResource(id);
// Returning of resources will enqueue main callbacks. The internal resource
// should have been deleted.
std::vector<ReturnedResource> returned;
returned.push_back(list[0].ToReturnedResource());
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().ReceiveReturnsFromParent(std::move(returned));
EXPECT_EQ(provider().num_resources_for_testing(), 0u);
// The enqueued Released callback should be invoked, along with the Flush.
EXPECT_CALL(release, Released(_, false));
ExpectFlush();
VizTestSuite::RunUntilIdle();
}
// Tests that when we are using
// `ClientResourceProvider::ScopedBatchResourcesRelease` that callbacks are not
// immediately ran when we remove resources. Confirming that they are ran once
// the scope is exited.
TEST_P(ClientResourceProviderTest, BatchedCallbacksDoNotFireImmediately) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures({features::kBatchResourceRelease}, {});
// Mark visible so eviction path is not inadvertently triggered.
provider().SetVisible(true);
// We only import the resource and do not `PrepareSendToParent`. As `exported`
// resources are not removed by `RemoveImportedResource`.
MockReleaseCallback release;
TransferableResource resource = MakeTransferableResource();
ResourceId id =
provider().ImportResource(resource, ReleaseCallback(),
base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)),
base::BindOnce(&MockReleaseCallback::Evicted,
base::Unretained(&release)));
{
// We use `ScopedBatchResourcesRelease` to prevent the immediate callbacks.
ClientResourceProvider::ScopedBatchResourcesRelease batch =
provider().CreateScopedBatchResourcesRelease();
// Zero callbacks for the removal, they should run when `batch` leaves
// scoped.
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().RemoveImportedResource(id);
// Leaving scope should lead to `batch` triggering the callback
EXPECT_CALL(release, Released(_, _));
}
ExpectFlush();
VizTestSuite::RunUntilIdle();
}
// Ensures that while batching callbacks, that `ClientResourceProvider` being
// destroyed before the `ClientResourceProvider::ScopedBatchResourcesRelease`
// ensures the callbacks are ran.
TEST_P(ClientResourceProviderTest,
BatchedCallbacksFireUponProviderDestruction) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures({features::kBatchResourceRelease}, {});
// Mark visible so eviction path is not inadvertently triggered.
provider().SetVisible(true);
// We only import the resource and do not `PrepareSendToParent`. As `exported`
// resources are not removed by `RemoveImportedResource`.
MockReleaseCallback release;
TransferableResource resource = MakeTransferableResource();
ResourceId id =
provider().ImportResource(resource, ReleaseCallback(),
base::BindOnce(&MockReleaseCallback::Released,
base::Unretained(&release)),
base::BindOnce(&MockReleaseCallback::Evicted,
base::Unretained(&release)));
// We use `ScopedBatchResourcesRelease` to prevent the immediate callbacks.
ClientResourceProvider::ScopedBatchResourcesRelease batch =
provider().CreateScopedBatchResourcesRelease();
// Zero callbacks for the removal, they should run when `batch` leaves
// scoped.
EXPECT_CALL(release, Released(_, _)).Times(0);
provider().RemoveImportedResource(id);
// Destroying `provider` should run the callback.
EXPECT_CALL(release, Released(_, _));
DestroyProvider();
ExpectFlush();
VizTestSuite::RunUntilIdle();
}
} // namespace
} // namespace viz