chromium/ash/frame_sink/ui_resource_manager_unittest.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/frame_sink/ui_resource_manager.h"

#include <memory>
#include <vector>

#include "ash/frame_sink/ui_resource.h"
#include "base/test/gtest_util.h"
#include "components/viz/common/resources/resource_id.h"
#include "components/viz/common/resources/returned_resource.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "gpu/command_buffer/common/mailbox.h"
#include "ui/gfx/geometry/size.h"

namespace ash {
namespace {

constexpr UiSourceId kTestUiSourceId_1 = 1u;
constexpr UiSourceId kTestUiSourceId_2 = 2u;

std::unique_ptr<UiResource> MakeResource(const gfx::Size& resource_size,
                                         viz::SharedImageFormat format,
                                         uint32_t ui_source_id) {
  auto resource = std::make_unique<UiResource>();
  resource->ui_source_id = ui_source_id;
  resource->format = format;
  resource->resource_size = resource_size;
  resource->SetExternallyOwnedMailbox(gpu::Mailbox::Generate());
  return resource;
}

class UiResourceManagerTest : public testing::Test {
 public:
  UiResourceManagerTest() = default;
  UiResourceManagerTest(const UiResourceManagerTest&) = delete;
  UiResourceManagerTest& operator=(const UiResourceManagerTest&) = delete;

 protected:
  void SetUp() override {
    resource_manager_ = std::make_unique<UiResourceManager>();
  }

  void TearDown() override { resource_manager_->LostExportedResources(); }

  std::unique_ptr<UiResourceManager> resource_manager_;
};

TEST_F(UiResourceManagerTest, ReuseResource_NoResources) {
  viz::ResourceId resource_id = resource_manager_->FindResourceToReuse(
      gfx::Size(100, 100), viz::SinglePlaneFormat::kBGRA_8888,
      kTestUiSourceId_1);

  EXPECT_EQ(resource_id, viz::kInvalidResourceId);
}

TEST_F(UiResourceManagerTest, ReuseResource) {
  resource_manager_->OfferResource(
      MakeResource(gfx::Size(10, 10), viz::SinglePlaneFormat::kBGRA_8888,
                   kTestUiSourceId_1));

  resource_manager_->OfferResource(
      MakeResource(gfx::Size(20, 20), viz::SinglePlaneFormat::kBGRA_8888,
                   kTestUiSourceId_1));

  resource_manager_->OfferResource(
      MakeResource(gfx::Size(10, 20), viz::SinglePlaneFormat::kBGRA_8888,
                   kTestUiSourceId_2));

  resource_manager_->OfferResource(
      MakeResource(gfx::Size(10, 20), viz::SinglePlaneFormat::kBGRA_8888,
                   kTestUiSourceId_2));

  EXPECT_EQ(resource_manager_->available_resources_count(), 4u);

  // When we have no match in the currently available resources.
  viz::ResourceId resource_id = resource_manager_->FindResourceToReuse(
      gfx::Size(100, 100), viz::SinglePlaneFormat::kBGRA_8888,
      kTestUiSourceId_1);

  EXPECT_EQ(resource_id, viz::kInvalidResourceId);

  // When we have the requested resource.
  resource_id = resource_manager_->FindResourceToReuse(
      gfx::Size(10, 10), viz::SinglePlaneFormat::kBGRA_8888, kTestUiSourceId_1);

  EXPECT_NE(resource_id, viz::kInvalidResourceId);

  auto* found_resource = resource_manager_->PeekAvailableResource(resource_id);
  EXPECT_EQ(found_resource->ui_source_id, kTestUiSourceId_1);
  EXPECT_EQ(found_resource->format, viz::SinglePlaneFormat::kBGRA_8888);
  EXPECT_EQ(found_resource->resource_size, gfx::Size(10, 10));

  // When we have multiple matching resources, return any matching resource.
  resource_id = resource_manager_->FindResourceToReuse(
      gfx::Size(10, 20), viz::SinglePlaneFormat::kBGRA_8888, kTestUiSourceId_2);

  EXPECT_NE(resource_id, viz::kInvalidResourceId);
  found_resource = resource_manager_->PeekAvailableResource(resource_id);

  EXPECT_EQ(found_resource->ui_source_id, kTestUiSourceId_2);
  EXPECT_EQ(found_resource->format, viz::SinglePlaneFormat::kBGRA_8888);
  EXPECT_EQ(found_resource->resource_size, gfx::Size(10, 20));
}

TEST_F(UiResourceManagerTest, OfferResource) {
  resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());

  // As soon as we offer a resource, it is available to be used.
  EXPECT_EQ(resource_manager_->available_resources_count(), 2u);
}

using UiResourceManagerDeathTest = UiResourceManagerTest;
TEST_F(UiResourceManagerDeathTest,
       NeedToClearAllExportedResourceBeforeDeletingManager) {
  viz::ResourceId to_be_exported_resource_id =
      resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());

  resource_manager_->PrepareResourceForExport(to_be_exported_resource_id);

  // The manager cannot be deleted as we still have a exported resource.
  EXPECT_DCHECK_DEATH({ resource_manager_.reset(); });
}

TEST_F(UiResourceManagerTest, PrepareResourceForExporting_InvalidIds) {
  viz::ResourceId to_be_released_resource =
      resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());
  {
    // We cannot export a resource that we do not manage.
    auto transferable_resource =
        resource_manager_->PrepareResourceForExport(viz::ResourceId(20));
    EXPECT_TRUE(transferable_resource.is_empty());
    EXPECT_EQ(resource_manager_->exported_resources_count(), 0u);

    resource_manager_->ReleaseAvailableResource(to_be_released_resource);
  }
  {
    // We cannot export a resource that was released for the manager.
    resource_manager_->ReleaseAvailableResource(to_be_released_resource);

    auto transferable_resource =
        resource_manager_->PrepareResourceForExport(to_be_released_resource);
    EXPECT_TRUE(transferable_resource.is_empty());
    EXPECT_EQ(resource_manager_->exported_resources_count(), 0u);
  }
}

TEST_F(UiResourceManagerTest, PrepareResourceForExporting) {
  viz::ResourceId to_be_exported_resource_id =
      resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());

  EXPECT_EQ(resource_manager_->exported_resources_count(), 0u);
  EXPECT_EQ(resource_manager_->available_resources_count(), 3u);

  // The resource in now in the exported_pool.
  viz::TransferableResource transferable_resource =
      resource_manager_->PrepareResourceForExport(to_be_exported_resource_id);

  // We exported one resources leaving two resources as available.
  EXPECT_EQ(resource_manager_->exported_resources_count(), 1u);
  EXPECT_EQ(resource_manager_->available_resources_count(), 2u);

  EXPECT_EQ(transferable_resource.id, to_be_exported_resource_id);
}

TEST_F(UiResourceManagerTest, CannotExportAlreadyExportedResource) {
  viz::ResourceId to_be_exported_resource_id =
      resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());

  resource_manager_->PrepareResourceForExport(to_be_exported_resource_id);

  auto transferable_resource =
      resource_manager_->PrepareResourceForExport(to_be_exported_resource_id);
  EXPECT_TRUE(transferable_resource.is_empty());
}

TEST_F(UiResourceManagerTest, ReleaseResource_InvalidIds) {
  // We can only release a resource that we currently manage.
  const auto released_resource =
      resource_manager_->ReleaseAvailableResource(viz::ResourceId(20));
  EXPECT_FALSE(released_resource);
}

TEST_F(UiResourceManagerTest, ReleaseResource) {
  viz::ResourceId to_be_released_resource =
      resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());

  EXPECT_EQ(resource_manager_->available_resources_count(), 2u);

  auto released_resource =
      resource_manager_->ReleaseAvailableResource(to_be_released_resource);

  EXPECT_EQ(resource_manager_->available_resources_count(), 1u);
  EXPECT_EQ(released_resource->resource_id, to_be_released_resource);
}

TEST_F(UiResourceManagerTest, CannotReleaseExportedResourcesTillReclaimed) {
  viz::ResourceId to_be_exported_resource =
      resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());

  EXPECT_EQ(resource_manager_->available_resources_count(), 2u);

  resource_manager_->PrepareResourceForExport(to_be_exported_resource);

  // We cannot release an exported resource until the resource is reclaimed.
  auto released_resource =
      resource_manager_->ReleaseAvailableResource(to_be_exported_resource);

  EXPECT_FALSE(released_resource);
  EXPECT_EQ(resource_manager_->exported_resources_count(), 1u);

  std::vector<viz::ReturnedResource> returned;
  returned.emplace_back();
  returned.back().id = to_be_exported_resource;
  returned.back().count = 1;
  returned.back().lost = false;

  resource_manager_->ReclaimResources(returned);

  viz::ResourceId to_be_released_resource = to_be_exported_resource;

  // Now that we reclaimed the exported resource, we can now release it.
  released_resource =
      resource_manager_->ReleaseAvailableResource(to_be_released_resource);

  EXPECT_EQ(to_be_released_resource, released_resource->resource_id);
}

TEST_F(UiResourceManagerTest, ExportedResourcesAreLost) {
  viz::ResourceId to_be_exported_resource_1 =
      resource_manager_->OfferResource(std::make_unique<UiResource>());
  viz::ResourceId to_be_exported_resource_2 =
      resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());
  resource_manager_->OfferResource(std::make_unique<UiResource>());

  resource_manager_->PrepareResourceForExport(to_be_exported_resource_1);
  resource_manager_->PrepareResourceForExport(to_be_exported_resource_2);

  EXPECT_EQ(resource_manager_->available_resources_count(), 3u);
  EXPECT_EQ(resource_manager_->exported_resources_count(), 2u);

  // If there is no chance to reclaim back the resources we need to clear the
  // exported pool.
  resource_manager_->LostExportedResources();

  EXPECT_EQ(resource_manager_->available_resources_count(), 3u);
  EXPECT_EQ(resource_manager_->exported_resources_count(), 0u);
}

TEST_F(UiResourceManagerTest, ReclaimResources) {
  viz::ResourceId to_be_exported_resource_1 =
      resource_manager_->OfferResource(std::make_unique<UiResource>());
  viz::ResourceId to_be_exported_resource_2 =
      resource_manager_->OfferResource(std::make_unique<UiResource>());

  resource_manager_->PrepareResourceForExport(to_be_exported_resource_1);
  resource_manager_->PrepareResourceForExport(to_be_exported_resource_2);
  {
    // Returning a non-lost resource.
    std::vector<viz::ReturnedResource> returned;
    returned.emplace_back();
    returned.back().id = to_be_exported_resource_2;
    returned.back().count = 1;
    returned.back().lost = false;

    EXPECT_EQ(resource_manager_->exported_resources_count(), 2u);
    resource_manager_->ReclaimResources(returned);
    EXPECT_EQ(resource_manager_->exported_resources_count(), 1u);

    // Reclaimed resource is now available to be reused.
    EXPECT_EQ(resource_manager_->available_resources_count(), 1u);
  }

  {
    // Returning a lost resource.
    std::vector<viz::ReturnedResource> returned;
    returned.emplace_back();
    returned.back().id = to_be_exported_resource_1;
    returned.back().count = 1;
    returned.back().lost = true;

    EXPECT_EQ(resource_manager_->exported_resources_count(), 1u);

    // We have one available resource already.
    EXPECT_EQ(resource_manager_->available_resources_count(), 1u);
    resource_manager_->ReclaimResources(returned);

    // We have received the resource so it is no more exported.
    EXPECT_EQ(resource_manager_->exported_resources_count(), 0u);

    // This reclaimed resource is lost so it cannot be reused again.
    EXPECT_EQ(resource_manager_->available_resources_count(), 1u);
  }

  viz::ResourceId to_be_exported_resource_3 =
      resource_manager_->OfferResource(std::make_unique<UiResource>());

  viz::ResourceId to_be_exported_resource_4 =
      resource_manager_->OfferResource(std::make_unique<UiResource>());

  // Exporting more resources.
  resource_manager_->PrepareResourceForExport(to_be_exported_resource_3);
  resource_manager_->PrepareResourceForExport(to_be_exported_resource_4);

  {
    // Returning multiple resources.
    std::vector<viz::ReturnedResource> returned;
    returned.emplace_back();
    returned.back().id = to_be_exported_resource_3;
    returned.back().count = 1;
    returned.back().lost = true;

    returned.emplace_back();
    returned.back().id = to_be_exported_resource_4;
    returned.back().count = 1;
    returned.back().lost = false;

    EXPECT_EQ(resource_manager_->exported_resources_count(), 2u);

    // We have one available resource that was reclaimed before in the test.
    EXPECT_EQ(resource_manager_->available_resources_count(), 1u);
    resource_manager_->ReclaimResources(returned);

    // We have reclaimed all the resources.
    EXPECT_EQ(resource_manager_->exported_resources_count(), 0u);

    // One of the two exported resources was lost.
    EXPECT_EQ(resource_manager_->available_resources_count(), 2u);
  }
}

}  // namespace
}  // namespace ash