chromium/components/viz/service/display/overlay_dc_unittest.cc

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

#include <stddef.h>

#include <memory>
#include <utility>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "cc/paint/filter_operation.h"
#include "cc/paint/filter_operations.h"
#include "cc/test/fake_output_surface_client.h"
#include "components/viz/client/client_resource_provider.h"
#include "components/viz/common/display/renderer_settings.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/quads/aggregated_render_pass.h"
#include "components/viz/common/quads/aggregated_render_pass_draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/texture_draw_quad.h"
#include "components/viz/common/resources/transferable_resource.h"
#include "components/viz/service/display/aggregated_frame.h"
#include "components/viz/service/display/dc_layer_overlay.h"
#include "components/viz/service/display/display_resource_provider_skia.h"
#include "components/viz/service/display/output_surface.h"
#include "components/viz/service/display/overlay_candidate.h"
#include "components/viz/service/display/overlay_processor_win.h"
#include "components/viz/test/fake_skia_output_surface.h"
#include "components/viz/test/overlay_candidate_matchers.h"
#include "components/viz/test/test_context_provider.h"
#include "gpu/config/gpu_finch_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/linear_gradient.h"
#include "ui/gfx/geometry/mask_filter_info.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rrect_f.h"
#include "ui/gfx/hdr_metadata.h"
#include "ui/gfx/video_types.h"

using testing::_;
using testing::Mock;

namespace viz {
namespace {

const gfx::Rect kOverlayRect(0, 0, 256, 256);
const gfx::Rect kOverlayBottomRightRect(128, 128, 128, 128);

// An arbitrary render pass ID that can be treated as the implicit root pass ID
// by the test suites and helper functions.
const AggregatedRenderPassId kDefaultRootPassId{1};

std::unique_ptr<AggregatedRenderPass> CreateRenderPass(
    AggregatedRenderPassId render_pass_id = kDefaultRootPassId) {
  gfx::Rect output_rect(0, 0, 256, 256);

  auto pass = std::make_unique<AggregatedRenderPass>();
  pass->SetNew(render_pass_id, output_rect, output_rect, gfx::Transform());

  SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState();
  shared_state->opacity = 1.f;
  return pass;
}

static ResourceId CreateResourceInLayerTree(
    ClientResourceProvider* child_resource_provider,
    const gfx::Size& size,
    const gfx::ColorSpace& color_space,
    const gfx::HDRMetadata& hdr_metadata,
    SharedImageFormat format,
    bool is_overlay_candidate) {
  auto resource = TransferableResource::MakeGpu(
      gpu::Mailbox::Generate(), GL_TEXTURE_2D, gpu::SyncToken(), size, format,
      is_overlay_candidate);
  resource.color_space = color_space;
  resource.hdr_metadata = hdr_metadata;

  ResourceId resource_id =
      child_resource_provider->ImportResource(resource, base::DoNothing());

  return resource_id;
}

ResourceId CreateResource(DisplayResourceProvider* parent_resource_provider,
                          ClientResourceProvider* child_resource_provider,
                          RasterContextProvider* child_context_provider,
                          const gfx::Size& size,
                          const gfx::ColorSpace& color_space,
                          const gfx::HDRMetadata& hdr_metadata,
                          SharedImageFormat format,
                          bool is_overlay_candidate) {
  ResourceId resource_id =
      CreateResourceInLayerTree(child_resource_provider, size, color_space,
                                hdr_metadata, format, is_overlay_candidate);

  int child_id =
      parent_resource_provider->CreateChild(base::DoNothing(), SurfaceId());

  // Transfer resource to the parent.
  std::vector<ResourceId> resource_ids_to_transfer;
  resource_ids_to_transfer.push_back(resource_id);
  std::vector<TransferableResource> list;
  child_resource_provider->PrepareSendToParent(resource_ids_to_transfer, &list,
                                               child_context_provider);
  parent_resource_provider->ReceiveFromChild(child_id, list);

  // Delete it in the child so it won't be leaked, and will be released once
  // returned from the parent.
  child_resource_provider->RemoveImportedResource(resource_id);

  // In DisplayResourceProvider's namespace, use the mapped resource id.
  std::unordered_map<ResourceId, ResourceId, ResourceIdHasher> resource_map =
      parent_resource_provider->GetChildToParentMap(child_id);
  return resource_map[list[0].id];
}

SolidColorDrawQuad* CreateSolidColorQuadAt(
    const SharedQuadState* shared_quad_state,
    SkColor4f color,
    AggregatedRenderPass* render_pass,
    const gfx::Rect& rect) {
  SolidColorDrawQuad* quad =
      render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
  quad->SetNew(shared_quad_state, rect, rect, color, false);
  return quad;
}

TextureDrawQuad* CreateTextureQuadAt(
    DisplayResourceProvider* parent_resource_provider,
    ClientResourceProvider* child_resource_provider,
    RasterContextProvider* child_context_provider,
    const SharedQuadState* shared_quad_state,
    AggregatedRenderPass* render_pass,
    const gfx::Rect& rect,
    bool is_overlay_candidate = true) {
  ResourceId resource_id = CreateResource(
      parent_resource_provider, child_resource_provider, child_context_provider,
      rect.size(), gfx::ColorSpace(), gfx::HDRMetadata(),
      SinglePlaneFormat::kRGBA_8888, is_overlay_candidate);
  auto* quad = render_pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
  quad->SetNew(shared_quad_state, rect, /*visible_rect=*/rect,
               /*needs_blending=*/false, resource_id, /*premultiplied=*/true,
               /*top_left=*/gfx::PointF(0, 0),
               /*bottom_right=*/gfx::PointF(1, 1),
               /*background=*/SkColors::kBlack, /*flipped=*/false,
               /*nearest=*/false, /*secure_output=*/false,
               gfx::ProtectedVideoType::kClear);
  return quad;
}

void CreateOpaqueQuadAt(DisplayResourceProvider* resource_provider,
                        const SharedQuadState* shared_quad_state,
                        AggregatedRenderPass* render_pass,
                        const gfx::Rect& rect,
                        SkColor4f color) {
  DCHECK(color.isOpaque());
  auto* color_quad = render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
  color_quad->SetNew(shared_quad_state, rect, rect, color, false);
}

TextureDrawQuad* CreateFullscreenCandidateYUVTextureQuad(
    DisplayResourceProvider* parent_resource_provider,
    ClientResourceProvider* child_resource_provider,
    RasterContextProvider* child_context_provider,
    const SharedQuadState* shared_quad_state,
    AggregatedRenderPass* render_pass,
    const gfx::ColorSpace& color_space = gfx::ColorSpace(),
    const gfx::HDRMetadata& hdr_metadata = gfx::HDRMetadata(),
    SharedImageFormat format = SinglePlaneFormat::kRGBA_8888) {
  gfx::Rect rect = render_pass->output_rect;
  gfx::Size resource_size_in_pixels = rect.size();
  bool is_overlay_candidate = true;
  ResourceId resource_id =
      CreateResource(parent_resource_provider, child_resource_provider,
                     child_context_provider, resource_size_in_pixels,
                     color_space, hdr_metadata, format, is_overlay_candidate);

  auto* overlay_quad = render_pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
  overlay_quad->SetNew(shared_quad_state, rect, /*visible_rect=*/rect,
                       /*needs_blending=*/false, resource_id,
                       /*premultiplied=*/true,
                       /*top_left=*/gfx::PointF(0, 0),
                       /*bottom_right=*/gfx::PointF(1, 1),
                       /*background=*/SkColors::kBlack, /*flipped=*/false,
                       /*nearest=*/false, /*secure_output=*/false,
                       gfx::ProtectedVideoType::kClear);
  // Content is video frame type.
  overlay_quad->is_video_frame = true;

  return overlay_quad;
}

AggregatedRenderPassDrawQuad* CreateRenderPassDrawQuadAt(
    AggregatedRenderPass* render_pass,
    const SharedQuadState* shared_quad_state,
    const gfx::Rect& rect,
    AggregatedRenderPassId render_pass_id) {
  AggregatedRenderPassDrawQuad* quad =
      render_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
  quad->SetNew(shared_quad_state, rect, rect, render_pass_id, ResourceId(2),
               gfx::RectF(), gfx::Size(), gfx::Vector2dF(1, 1), gfx::PointF(),
               gfx::RectF(), false, 1.f);
  return quad;
}

SkM44 GetIdentityColorMatrix() {
  return SkM44();
}

class OverlayProcessorTestBase : public testing::Test {
 protected:
  OverlayProcessorTestBase() {
    std::vector<base::test::FeatureRef> enabled_features;
    std::vector<base::test::FeatureRef> disabled_features;

    // With DisableVideoOverlayIfMoving, videos are required to be stable for a
    // certain number of frames to be considered for overlay promotion. This
    // complicates tests since it adds behavior dependent on the number of times
    // |Process| is called.
    disabled_features.push_back(features::kDisableVideoOverlayIfMoving);

    feature_list_.InitWithFeatures(enabled_features, disabled_features);
  }

  void SetUp() override {
    output_surface_ = FakeSkiaOutputSurface::Create3d();
    output_surface_->BindToClient(&output_surface_client_);

    resource_provider_ = std::make_unique<DisplayResourceProviderSkia>();
    lock_set_for_external_use_.emplace(resource_provider_.get(),
                                       output_surface_.get());

    child_provider_ = TestContextProvider::Create();
    child_provider_->BindToCurrentSequence();
    child_resource_provider_ = std::make_unique<ClientResourceProvider>();
  }

  void TearDown() override {
    child_resource_provider_->ShutdownAndReleaseAllResources();
    child_resource_provider_ = nullptr;
    child_provider_ = nullptr;
    lock_set_for_external_use_.reset();
    resource_provider_ = nullptr;
    output_surface_ = nullptr;
  }

  base::test::ScopedFeatureList feature_list_;
  std::unique_ptr<FakeSkiaOutputSurface> output_surface_;
  cc::FakeOutputSurfaceClient output_surface_client_;
  std::unique_ptr<DisplayResourceProviderSkia> resource_provider_;
  std::optional<DisplayResourceProviderSkia::LockSetForExternalUse>
      lock_set_for_external_use_;
  scoped_refptr<TestContextProvider> child_provider_;
  std::unique_ptr<ClientResourceProvider> child_resource_provider_;
};

class DCLayerOverlayProcessorTest : public OverlayProcessorTestBase {
 protected:
  void InitializeDCLayerOverlayProcessor(int allowed_yuv_overlay_count = 1) {
    CHECK(!dc_layer_overlay_processor_);
    dc_layer_overlay_processor_ = std::make_unique<DCLayerOverlayProcessor>(
        allowed_yuv_overlay_count,
        /*skip_initialization_for_testing=*/true);

    dc_layer_overlay_processor_
        ->set_frames_since_last_qualified_multi_overlays_for_testing(5);
  }

  void TearDown() override {
    dc_layer_overlay_processor_.reset();
    OverlayProcessorTestBase::TearDown();
  }

  DCLayerOverlayProcessor::RenderPassOverlayData ProcessRootPassForOverlays(
      const AggregatedRenderPassList* render_passes,
      const OverlayProcessorInterface::FilterOperationsMap& render_pass_filters,
      const OverlayProcessorInterface::FilterOperationsMap&
          render_pass_backdrop_filters,
      SurfaceDamageRectList surface_damage_rect_list_in_root_space) {
    DCLayerOverlayProcessor::RenderPassOverlayDataMap
        render_pass_overlay_data_map;
    auto emplace_pair = render_pass_overlay_data_map.emplace(
        render_passes->back().get(),
        DCLayerOverlayProcessor::RenderPassOverlayData());
    DCLayerOverlayProcessor::RenderPassOverlayData&
        root_render_pass_overlay_data = emplace_pair.first->second;

    root_render_pass_overlay_data.damage_rect =
        render_passes->back()->damage_rect;

    dc_layer_overlay_processor_->Process(
        resource_provider_.get(), render_pass_filters,
        render_pass_backdrop_filters, surface_damage_rect_list_in_root_space,
        /*is_page_fullscreen_mode=*/false, render_pass_overlay_data_map);

    // |DCLayerOverlayProcessor::Process| doesn't guarantee a specific ordering
    // for its overlays so we sort front-to-back so tests can make expectations
    // with the same ordering as the input draw quads.
    base::ranges::sort(root_render_pass_overlay_data.promoted_overlays,
                       base::ranges::greater(),
                       &OverlayCandidate::plane_z_order);

    return std::move(root_render_pass_overlay_data);
  }

  void TestRenderPassRootTransform(bool is_overlay);

  std::unique_ptr<DCLayerOverlayProcessor> dc_layer_overlay_processor_;
};

TEST_F(DCLayerOverlayProcessorTest, DisableVideoOverlayIfMovingFeature) {
  InitializeDCLayerOverlayProcessor();
  auto ProcessForOverlaysSingleVideoRectWithOffset =
      [&](gfx::Vector2d video_rect_offset, bool is_hdr = false,
          bool is_sdr_to_hdr = false) {
        auto pass = CreateRenderPass();

        SharedImageFormat format = SinglePlaneFormat::kRGBA_8888;
        gfx::ColorSpace color_space;
        gfx::HDRMetadata hdr_metadata;

        if (is_hdr) {
          // Content is 10bit P010 content.
          format = MultiPlaneFormat::kP010;
          // Content has HDR10 colorspace.
          color_space = gfx::ColorSpace::CreateHDR10();

          // Content has valid HDR metadata.
          hdr_metadata.cta_861_3 = gfx::HdrMetadataCta861_3(1000, 400);
          hdr_metadata.smpte_st_2086 = gfx::HdrMetadataSmpteSt2086(
              SkNamedPrimariesExt::kRec2020, 1000, 0.0001);

          // Render Pass has HDR content usage.
          pass->content_color_usage = gfx::ContentColorUsage::kHDR;

          // Device has RGB10A2 overlay support.
          gl::SetDirectCompositionScaledOverlaysSupportedForTesting(true);

          // Device has HDR-enabled display and no non-HDR-enabled display.
          dc_layer_overlay_processor_
              ->set_system_hdr_disabled_on_any_display_for_testing(false);

          // Device has video processor support.
          dc_layer_overlay_processor_
              ->set_has_p010_video_processor_support_for_testing(true);
        } else if (is_sdr_to_hdr) {
          // Content is 8bit NV12 content.
          format = MultiPlaneFormat::kNV12;
          // Content has 709 colorspace.
          color_space = gfx::ColorSpace::CreateREC709();

          // Render Pass has SDR content usage.
          pass->content_color_usage = gfx::ContentColorUsage::kSRGB;

          // Device is not using battery power.
          dc_layer_overlay_processor_->set_is_on_battery_power_for_testing(
              false);

          // Device has at least one HDR-enabled display.
          dc_layer_overlay_processor_
              ->set_system_hdr_enabled_on_any_display_for_testing(true);

          // Device has video processor auto hdr support.
          dc_layer_overlay_processor_
              ->set_has_auto_hdr_video_processor_support_for_testing(true);
        }

        auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
            resource_provider_.get(), child_resource_provider_.get(),
            child_provider_.get(), pass->shared_quad_state_list.back(),
            pass.get(), color_space, hdr_metadata, format);
        video_quad->rect = gfx::Rect(0, 0, 10, 10) + video_rect_offset;
        video_quad->visible_rect = gfx::Rect(0, 0, 10, 10) + video_rect_offset;

        OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
        OverlayProcessorInterface::FilterOperationsMap
            render_pass_backdrop_filters;

        AggregatedRenderPassList pass_list;
        pass_list.push_back(std::move(pass));

        auto overlay_data = ProcessRootPassForOverlays(
            &pass_list, render_pass_filters, render_pass_backdrop_filters, {});

        return std::move(overlay_data.promoted_overlays);
      };

  {
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndDisableFeature(
        features::kDisableVideoOverlayIfMoving);
    EXPECT_EQ(1U, ProcessForOverlaysSingleVideoRectWithOffset({0, 0}).size());
    EXPECT_EQ(1U, ProcessForOverlaysSingleVideoRectWithOffset({1, 0}).size());
  }

  {
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndEnableFeature(
        features::kDisableVideoOverlayIfMoving);
    // We expect an overlay promotion after a couple frames of no movement
    for (int i = 0; i < 10; i++) {
      ProcessForOverlaysSingleVideoRectWithOffset({0, 0}).size();
    }
    EXPECT_EQ(1U, ProcessForOverlaysSingleVideoRectWithOffset({0, 0}).size());

    // Since the overlay candidate moved, we expect no overlays
    EXPECT_EQ(0U, ProcessForOverlaysSingleVideoRectWithOffset({1, 0}).size());

    // After some number of frames with no movement, we expect an overlay again
    for (int i = 0; i < 10; i++) {
      ProcessForOverlaysSingleVideoRectWithOffset({1, 0}).size();
    }
    EXPECT_EQ(1U, ProcessForOverlaysSingleVideoRectWithOffset({1, 0}).size());
  }

  {
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndEnableFeature(
        features::kDisableVideoOverlayIfMoving);
    // We expect an overlay promotion after a couple frames of no movement
    for (int i = 0; i < 10; i++) {
      ProcessForOverlaysSingleVideoRectWithOffset({0, 0}, /*is_hdr=*/false,
                                                  /*is_sdr_to_hdr*/ true)
          .size();
    }
    EXPECT_EQ(1U, ProcessForOverlaysSingleVideoRectWithOffset(
                      {0, 0}, /*is_hdr=*/false, /*is_sdr_to_hdr*/ true)
                      .size());
    // We still expect an overlay promotion for SDR video when auto hdr is
    // enabled and when moving to ensure uniform tone mapping results between
    // viz and GPU driver.
    EXPECT_EQ(1U, ProcessForOverlaysSingleVideoRectWithOffset(
                      {1, 0}, /*is_hdr=*/false, /*is_sdr_to_hdr*/ true)
                      .size());
  }

  {
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndEnableFeature(
        features::kDisableVideoOverlayIfMoving);
    // We expect an overlay promotion after a couple frames of no movement
    for (int i = 0; i < 10; i++) {
      ProcessForOverlaysSingleVideoRectWithOffset({0, 0}, /*is_hdr=*/true)
          .size();
    }
    EXPECT_EQ(
        1U, ProcessForOverlaysSingleVideoRectWithOffset({0, 0}, /*is_hdr=*/true)
                .size());
    // We still expect an overlay promotion for HDR video when moving to
    // ensure uniform tone mapping results between viz and GPU driver.
    EXPECT_EQ(
        1U, ProcessForOverlaysSingleVideoRectWithOffset({1, 0}, /*is_hdr=*/true)
                .size());
  }
}

TEST_F(DCLayerOverlayProcessorTest, Occluded) {
  InitializeDCLayerOverlayProcessor();
  {
    auto pass = CreateRenderPass();
    SharedQuadState* first_shared_state = pass->shared_quad_state_list.back();
    first_shared_state->overlay_damage_index = 0;
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 3, 100, 100), SkColors::kWhite);

    SharedQuadState* second_shared_state =
        pass->CreateAndAppendSharedQuadState();
    second_shared_state->overlay_damage_index = 1;
    auto* first_video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    // Set the protected video flag will force the quad to use hw overlay
    first_video_quad->protected_video_type =
        gfx::ProtectedVideoType::kHardwareProtected;

    SharedQuadState* third_shared_state =
        pass->CreateAndAppendSharedQuadState();
    third_shared_state->overlay_damage_index = 2;
    auto* second_video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    // Set the protected video flag will force the quad to use hw overlay
    second_video_quad->protected_video_type =
        gfx::ProtectedVideoType::kHardwareProtected;
    second_video_quad->rect.set_origin(gfx::Point(2, 2));
    second_video_quad->visible_rect.set_origin(gfx::Point(2, 2));

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(1, 1, 10, 10);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {
        gfx::Rect(1, 1, 10, 10), gfx::Rect(0, 0, 0, 0), gfx::Rect(0, 0, 0, 0)};

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    EXPECT_EQ(2U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(-1, overlay_data.promoted_overlays.front().plane_z_order);
    EXPECT_EQ(-2, overlay_data.promoted_overlays.back().plane_z_order);
    // Entire underlay rect must be redrawn.
    EXPECT_EQ(gfx::Rect(0, 0, 256, 256), overlay_data.damage_rect);
  }
  {
    auto pass = CreateRenderPass();
    SharedQuadState* first_shared_state = pass->shared_quad_state_list.back();
    first_shared_state->overlay_damage_index = 0;
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(3, 3, 100, 100), SkColors::kWhite);

    SharedQuadState* second_shared_state =
        pass->CreateAndAppendSharedQuadState();
    second_shared_state->overlay_damage_index = 1;
    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    // Set the protected video flag will force the quad to use hw overlay
    video_quad->protected_video_type =
        gfx::ProtectedVideoType::kHardwareProtected;

    SharedQuadState* third_shared_state =
        pass->CreateAndAppendSharedQuadState();
    third_shared_state->overlay_damage_index = 2;
    auto* second_video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    second_video_quad->protected_video_type =
        gfx::ProtectedVideoType::kHardwareProtected;
    second_video_quad->rect.set_origin(gfx::Point(2, 2));
    second_video_quad->visible_rect.set_origin(gfx::Point(2, 2));

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(1, 1, 10, 10);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {
        gfx::Rect(1, 1, 10, 10), gfx::Rect(0, 0, 0, 0), gfx::Rect(0, 0, 0, 0)};

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    EXPECT_EQ(2U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(-1, overlay_data.promoted_overlays.front().plane_z_order);
    EXPECT_EQ(-2, overlay_data.promoted_overlays.back().plane_z_order);

    // The underlay rectangle is the same, so the damage for first video quad is
    // contained within the combined occluding rects for this and the last
    // frame. Second video quad also adds its damage.
    EXPECT_EQ(gfx::Rect(1, 1, 10, 10), overlay_data.damage_rect);
  }
}

TEST_F(DCLayerOverlayProcessorTest, DamageRectWithoutVideoDamage) {
  InitializeDCLayerOverlayProcessor();
  {
    auto pass = CreateRenderPass();
    SharedQuadState* shared_quad_state = pass->shared_quad_state_list.back();
    shared_quad_state->overlay_damage_index = 0;
    // Occluding quad fully contained in video rect.
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 3, 100, 100), SkColors::kWhite);
    // Non-occluding quad fully outside video rect
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(210, 210, 20, 20), SkColors::kWhite);

    // Underlay video quad
    SharedQuadState* second_shared_state =
        pass->CreateAndAppendSharedQuadState();
    second_shared_state->overlay_damage_index = 1;
    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    video_quad->rect = gfx::Rect(0, 0, 200, 200);
    video_quad->visible_rect = video_quad->rect;

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    // Damage rect fully outside video quad
    pass->damage_rect = gfx::Rect(210, 210, 20, 20);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {
        gfx::Rect(210, 210, 20, 20), gfx::Rect(0, 0, 0, 0)};

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(-1, overlay_data.promoted_overlays.back().plane_z_order);
    // All rects must be redrawn at the first frame.
    EXPECT_EQ(gfx::Rect(0, 0, 230, 230), overlay_data.damage_rect);
  }
  {
    auto pass = CreateRenderPass();
    SharedQuadState* shared_quad_state = pass->shared_quad_state_list.back();
    shared_quad_state->overlay_damage_index = 0;
    // Occluding quad fully contained in video rect.
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 3, 100, 100), SkColors::kWhite);
    // Non-occluding quad fully outside video rect
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(210, 210, 20, 20), SkColors::kWhite);

    // Underlay video quad
    SharedQuadState* second_shared_state =
        pass->CreateAndAppendSharedQuadState();
    second_shared_state->overlay_damage_index = 1;
    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    video_quad->rect = gfx::Rect(0, 0, 200, 200);
    video_quad->visible_rect = video_quad->rect;

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    // Damage rect fully outside video quad
    pass->damage_rect = gfx::Rect(210, 210, 20, 20);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {
        gfx::Rect(210, 210, 20, 20), gfx::Rect(0, 0, 0, 0)};

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(-1, overlay_data.promoted_overlays.back().plane_z_order);
    // Only the non-overlay damaged rect need to be drawn by the gl compositor
    EXPECT_EQ(gfx::Rect(210, 210, 20, 20), overlay_data.damage_rect);
  }
}

TEST_F(DCLayerOverlayProcessorTest, DamageRect) {
  InitializeDCLayerOverlayProcessor();
  for (int i = 0; i < 2; i++) {
    SCOPED_TRACE(base::StringPrintf("Frame %d", i));
    auto pass = CreateRenderPass();
    SharedQuadState* shared_quad_state = pass->shared_quad_state_list.back();
    shared_quad_state->overlay_damage_index = 0;
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(1, 1, 10, 10);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {gfx::Rect(1, 1, 10, 10)};

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(1, overlay_data.promoted_overlays.back().plane_z_order);
    // Damage rect should be unchanged on initial frame because of resize, but
    // should be empty on the second frame because everything was put in a
    // layer.
    if (i == 1)
      EXPECT_TRUE(overlay_data.damage_rect.IsEmpty());
    else
      EXPECT_EQ(gfx::Rect(1, 1, 10, 10), overlay_data.damage_rect);
  }
}

TEST_F(DCLayerOverlayProcessorTest, ClipRect) {
  InitializeDCLayerOverlayProcessor();
  // Process twice. The second time through the overlay list shouldn't change,
  // which will allow the damage rect to reflect just the changes in that
  // frame.
  for (size_t i = 0; i < 2; ++i) {
    auto pass = CreateRenderPass();
    pass->shared_quad_state_list.back()->overlay_damage_index = 0;
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 2, 100, 100), SkColors::kWhite);
    pass->shared_quad_state_list.back()->clip_rect = gfx::Rect(0, 3, 100, 100);

    SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState();
    shared_state->opacity = 1.f;
    shared_state->overlay_damage_index = 1;
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), shared_state, pass.get());
    // Clipped rect shouldn't be overlapped by clipped opaque quad rect.
    shared_state->clip_rect = gfx::Rect(0, 0, 100, 3);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(1, 1, 10, 10);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {gfx::Rect(1, 3, 10, 8),
                                                      gfx::Rect(1, 1, 10, 2)};

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
    // Because of clip rects the overlay isn't occluded and shouldn't be an
    // underlay.
    EXPECT_EQ(1, overlay_data.promoted_overlays.back().plane_z_order);
    EXPECT_EQ(gfx::Rect(0, 0, 100, 3),
              overlay_data.promoted_overlays.back().clip_rect);
    if (i == 1) {
      // The damage rect should only contain contents that aren't in the
      // clipped overlay rect.
      EXPECT_EQ(gfx::Rect(1, 3, 10, 8), overlay_data.damage_rect);
    }
  }
}

TEST_F(DCLayerOverlayProcessorTest, TransparentOnTop) {
  InitializeDCLayerOverlayProcessor();
  // Process twice. The second time through the overlay list shouldn't change,
  // which will allow the damage rect to reflect just the changes in that
  // frame.
  for (size_t i = 0; i < 2; ++i) {
    auto pass = CreateRenderPass();
    pass->shared_quad_state_list.back()->overlay_damage_index = 0;
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    pass->shared_quad_state_list.back()->opacity = 0.5f;

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(1, 1, 10, 10);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {gfx::Rect(1, 1, 10, 10)};

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(1, overlay_data.promoted_overlays.back().plane_z_order);
    // Quad isn't opaque, so underlying damage must remain the same.
    EXPECT_EQ(gfx::Rect(1, 1, 10, 10), overlay_data.damage_rect);
  }
}

TEST_F(DCLayerOverlayProcessorTest, UnderlayDamageRectWithQuadOnTopUnchanged) {
  InitializeDCLayerOverlayProcessor();
  for (int i = 0; i < 3; i++) {
    auto pass = CreateRenderPass();
    // Add a solid color quad on top
    SharedQuadState* shared_state_on_top = pass->shared_quad_state_list.back();
    CreateSolidColorQuadAt(shared_state_on_top, SkColors::kRed, pass.get(),
                           kOverlayBottomRightRect);

    SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState();
    shared_state->opacity = 1.f;
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), shared_state, pass.get());

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = kOverlayRect;
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    shared_state->overlay_damage_index = 1;

    // The quad on top does not give damage on the third frame
    SurfaceDamageRectList surface_damage_rect_list = {kOverlayBottomRightRect,
                                                      kOverlayRect};
    if (i == 2) {
      surface_damage_rect_list[0] = gfx::Rect();
    }

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(-1, overlay_data.promoted_overlays.back().plane_z_order);
    // Damage rect should be unchanged on initial frame, but should be reduced
    // to the size of quad on top, and empty on the third frame.
    if (i == 0)
      EXPECT_EQ(kOverlayRect, overlay_data.damage_rect);
    else if (i == 1)
      EXPECT_EQ(kOverlayBottomRightRect, overlay_data.damage_rect);
    else if (i == 2)
      EXPECT_EQ(gfx::Rect(), overlay_data.damage_rect);
  }
}

// Test whether quads with rounded corners are supported.
TEST_F(DCLayerOverlayProcessorTest, RoundedCorners) {
  InitializeDCLayerOverlayProcessor();
  // Frame #0
  {
    auto pass = CreateRenderPass();

    // Create a video YUV quad with rounded corner, nothing on top.
    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    gfx::Rect rect(0, 0, 256, 256);
    video_quad->rect = rect;
    video_quad->visible_rect = rect;
    pass->shared_quad_state_list.back()->overlay_damage_index = 0;
    // Rounded corners
    pass->shared_quad_state_list.back()->mask_filter_info =
        gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(0.f, 0.f, 20.f, 30.f), 5.f));

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 256, 256);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {
        gfx::Rect(0, 0, 256, 256)};

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    auto* root_pass = pass_list.back().get();
    auto* replaced_quad = root_pass->quad_list.back();
    auto* replaced_sqs = replaced_quad->shared_quad_state;

    // The video should be forced to an underlay mode, even there is nothing on
    // top.
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(-1, overlay_data.promoted_overlays.back().plane_z_order);

    // Check whether there is a replaced quad in the quad list.
    EXPECT_EQ(1U, root_pass->quad_list.size());

    // Check whether blend mode == kDstOut, color == black and still have the
    // rounded corner mask filter for the replaced solid quad.
    EXPECT_EQ(replaced_sqs->blend_mode, SkBlendMode::kDstOut);
    EXPECT_EQ(SolidColorDrawQuad::MaterialCast(replaced_quad)->color,
              SkColors::kBlack);
    EXPECT_TRUE(replaced_sqs->mask_filter_info.HasRoundedCorners());

    // The whole frame is damaged.
    EXPECT_EQ(gfx::Rect(0, 0, 256, 256), overlay_data.damage_rect);
  }

  // Frame #1
  {
    auto pass = CreateRenderPass();
    // Create a solid quad.
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 0, 32, 32), SkColors::kRed);

    // Create a video YUV quad with rounded corners below the red solid quad.
    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    gfx::Rect rect(0, 0, 256, 256);
    video_quad->rect = rect;
    video_quad->visible_rect = rect;
    pass->shared_quad_state_list.back()->overlay_damage_index = 1;
    // Rounded corners
    pass->shared_quad_state_list.back()->mask_filter_info =
        gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(0.f, 0.f, 20.f, 30.f), 5.f));

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 256, 256);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {
        gfx::Rect(0, 0, 32, 32), gfx::Rect(0, 0, 256, 256)};

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    auto* root_pass = pass_list.back().get();
    auto* replaced_quad = root_pass->quad_list.back();
    auto* replaced_sqs = replaced_quad->shared_quad_state;

    // still in an underlay mode.
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(-1, overlay_data.promoted_overlays.back().plane_z_order);

    // Check whether the red quad on top and the replacedment of the YUV quad
    // are still in the render pass.
    EXPECT_EQ(2U, root_pass->quad_list.size());

    // Check whether blend mode is kDstOut, color is black, and still have the
    // rounded corner mask filter for the replaced solid quad.
    EXPECT_EQ(replaced_sqs->blend_mode, SkBlendMode::kDstOut);
    EXPECT_EQ(SolidColorDrawQuad::MaterialCast(replaced_quad)->color,
              SkColors::kBlack);
    EXPECT_TRUE(replaced_sqs->mask_filter_info.HasRoundedCorners());

    // Only the UI is damaged.
    EXPECT_EQ(gfx::Rect(0, 0, 32, 32), overlay_data.damage_rect);
  }

  // Frame #2
  {
    auto pass = CreateRenderPass();
    // Create a solid quad.
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 0, 32, 32), SkColors::kRed);

    // Create a video YUV quad with rounded corners below the red solid quad.
    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    gfx::Rect rect(0, 0, 256, 256);
    video_quad->rect = rect;
    video_quad->visible_rect = rect;
    pass->shared_quad_state_list.back()->overlay_damage_index = 0;
    // Rounded corners
    pass->shared_quad_state_list.back()->mask_filter_info =
        gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(0.f, 0.f, 20.f, 30.f), 5.f));

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 256, 256);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {
        gfx::Rect(0, 0, 256, 256)};

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    auto* root_pass = pass_list.back().get();
    auto* replaced_quad = root_pass->quad_list.back();
    auto* replaced_sqs = replaced_quad->shared_quad_state;

    // still in an underlay mode.
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(-1, overlay_data.promoted_overlays.back().plane_z_order);

    // Check whether the red quad on top and the replacedment of the YUV quad
    // are still in the render pass.
    EXPECT_EQ(2U, root_pass->quad_list.size());

    // Check whether blend mode is kDstOut and color is black for the replaced
    // solid quad.
    EXPECT_EQ(replaced_sqs->blend_mode, SkBlendMode::kDstOut);
    EXPECT_EQ(SolidColorDrawQuad::MaterialCast(replaced_quad)->color,
              SkColors::kBlack);
    EXPECT_TRUE(replaced_sqs->mask_filter_info.HasRoundedCorners());

    // Zero root damage rect.
    EXPECT_TRUE(overlay_data.damage_rect.IsEmpty());
  }
}

// If there are multiple yuv overlay quad candidates, no overlay will be
// promoted to save power.
TEST_F(DCLayerOverlayProcessorTest, MultipleYUVOverlays) {
  InitializeDCLayerOverlayProcessor();
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::kNoUndamagedOverlayPromotion);
  {
    auto pass = CreateRenderPass();
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 0, 256, 256), SkColors::kWhite);

    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    gfx::Rect rect(10, 10, 80, 80);
    video_quad->rect = rect;
    video_quad->visible_rect = rect;
    pass->shared_quad_state_list.back()->overlay_damage_index = 1;

    auto* second_video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    gfx::Rect second_rect(100, 100, 120, 120);
    second_video_quad->rect = second_rect;
    second_video_quad->visible_rect = second_rect;
    pass->shared_quad_state_list.back()->overlay_damage_index = 2;

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;
    surface_damage_rect_list.push_back(gfx::Rect(0, 0, 256, 256));
    surface_damage_rect_list.push_back(video_quad->rect);
    surface_damage_rect_list.push_back(second_video_quad->rect);

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Skip overlay.
    EXPECT_EQ(0U, overlay_data.promoted_overlays.size());
    EXPECT_EQ(gfx::Rect(0, 0, 220, 220), overlay_data.damage_rect);

    // Check whether all 3 quads including two YUV quads are still in the render
    // pass
    auto* root_pass = pass_list.back().get();
    int quad_count = root_pass->quad_list.size();
    EXPECT_EQ(3, quad_count);
  }
}

// Test that the video is forced to underlay if the expanded quad of pixel
// moving foreground filter is on top.
TEST_F(DCLayerOverlayProcessorTest, PixelMovingForegroundFilter) {
  InitializeDCLayerOverlayProcessor();
  AggregatedRenderPassList pass_list;

  // Create a non-root render pass with a pixel-moving foreground filter.
  AggregatedRenderPassId filter_render_pass_id{2};
  gfx::Rect filter_rect = gfx::Rect(260, 260, 100, 100);
  cc::FilterOperations blur_filter;
  blur_filter.Append(cc::FilterOperation::CreateBlurFilter(10.f));
  auto filter_pass = std::make_unique<AggregatedRenderPass>();
  filter_pass->SetNew(filter_render_pass_id, filter_rect, filter_rect,
                      gfx::Transform());
  filter_pass->filters = blur_filter;

  // Add a solid quad to the non-root pass.
  SharedQuadState* shared_state_filter =
      filter_pass->CreateAndAppendSharedQuadState();
  CreateSolidColorQuadAt(shared_state_filter, SkColors::kRed, filter_pass.get(),
                         filter_rect);
  shared_state_filter->opacity = 1.f;
  pass_list.push_back(std::move(filter_pass));

  // Create a root render pass.
  auto pass = CreateRenderPass();
  // Add a RenderPassDrawQuad to the root render pass.
  SharedQuadState* shared_quad_state_rpdq = pass->shared_quad_state_list.back();
  // The pixel-moving render pass draw quad itself (rpdq->rect) doesn't
  // intersect with kOverlayRect(0, 0, 256, 256), but the expanded draw quad
  // (rpdq->rect(260, 260, 100, 100) + blur filter pixel movement (2 * 10.f) =
  // (240, 240, 140, 140)) does.

  CreateRenderPassDrawQuadAt(pass.get(), shared_quad_state_rpdq, filter_rect,
                             filter_render_pass_id);

  // Add a video quad to the root render pass.
  SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState();
  shared_state->opacity = 1.f;
  CreateFullscreenCandidateYUVTextureQuad(
      resource_provider_.get(), child_resource_provider_.get(),
      child_provider_.get(), shared_state, pass.get());
  // Make the root render pass output rect bigger enough to cover the video
  // quad kOverlayRect(0, 0, 256, 256) and the render pass draw quad (260, 260,
  // 100, 100).
  pass->output_rect = gfx::Rect(0, 0, 512, 512);

  OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
  OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
  render_pass_filters[filter_render_pass_id] = &blur_filter;

  // filter_rect + kOverlayRect. Both are damaged.
  pass->damage_rect = gfx::Rect(0, 0, 360, 360);
  pass_list.push_back(std::move(pass));
  shared_state->overlay_damage_index = 1;

  SurfaceDamageRectList surface_damage_rect_list = {filter_rect, kOverlayRect};

  auto overlay_data = ProcessRootPassForOverlays(
      &pass_list, render_pass_filters, render_pass_backdrop_filters,
      std::move(surface_damage_rect_list));

  EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
  // Make sure the video is in an underlay mode if the overlay quad intersects
  // with expanded rpdq->rect.
  EXPECT_EQ(-1, overlay_data.promoted_overlays.back().plane_z_order);
  EXPECT_EQ(gfx::Rect(0, 0, 360, 360), overlay_data.damage_rect);
}

// Test that the video is not promoted if a quad on top has backdrop filters.
TEST_F(DCLayerOverlayProcessorTest, BackdropFilter) {
  InitializeDCLayerOverlayProcessor();
  AggregatedRenderPassList pass_list;

  // Create a non-root render pass with a backdrop filter.
  AggregatedRenderPassId backdrop_filter_render_pass_id{2};
  gfx::Rect backdrop_filter_rect = gfx::Rect(200, 200, 100, 100);
  cc::FilterOperations backdrop_filter;
  backdrop_filter.Append(cc::FilterOperation::CreateBlurFilter(10.f));
  auto backdrop_filter_pass = std::make_unique<AggregatedRenderPass>();
  backdrop_filter_pass->SetNew(backdrop_filter_render_pass_id,
                               backdrop_filter_rect, backdrop_filter_rect,
                               gfx::Transform());
  backdrop_filter_pass->backdrop_filters = backdrop_filter;

  // Add a transparent solid quad to the non-root pass.
  SharedQuadState* shared_state_backdrop_filter =
      backdrop_filter_pass->CreateAndAppendSharedQuadState();
  CreateSolidColorQuadAt(shared_state_backdrop_filter, SkColors::kGreen,
                         backdrop_filter_pass.get(), backdrop_filter_rect);
  shared_state_backdrop_filter->opacity = 0.1f;
  pass_list.push_back(std::move(backdrop_filter_pass));

  // Create a root render pass.
  auto pass = CreateRenderPass();
  // Add a RenderPassDrawQuad to the root render pass, on top of the video.
  SharedQuadState* shared_quad_state_rpdq = pass->shared_quad_state_list.back();
  shared_quad_state_rpdq->opacity = 0.1f;
  // The render pass draw quad rpdq->rect intersects with the overlay quad
  // kOverlayRect(0, 0, 256, 256).
  CreateRenderPassDrawQuadAt(pass.get(), shared_quad_state_rpdq,
                             backdrop_filter_rect,
                             backdrop_filter_render_pass_id);

  // Add a video quad to the root render pass.
  SharedQuadState* shared_state = pass->CreateAndAppendSharedQuadState();
  shared_state->opacity = 1.f;
  CreateFullscreenCandidateYUVTextureQuad(
      resource_provider_.get(), child_resource_provider_.get(),
      child_provider_.get(), shared_state, pass.get());
  // Make the root render pass output rect bigger enough to cover the video
  // quad kOverlayRect(0, 0, 256, 256) and the render pass draw quad (200, 200,
  // 100, 100).
  pass->output_rect = gfx::Rect(0, 0, 512, 512);

  OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
  OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
  render_pass_backdrop_filters[backdrop_filter_render_pass_id] =
      &backdrop_filter;

  // backdrop_filter_rect + kOverlayRect. Both are damaged.
  pass->damage_rect = gfx::Rect(0, 0, 300, 300);
  pass_list.push_back(std::move(pass));
  shared_state->overlay_damage_index = 1;

  SurfaceDamageRectList surface_damage_rect_list = {backdrop_filter_rect,
                                                    kOverlayRect};

  auto overlay_data = ProcessRootPassForOverlays(
      &pass_list, render_pass_filters, render_pass_backdrop_filters,
      std::move(surface_damage_rect_list));

  // Make sure the video is not promoted if the overlay quad intersects
  // with the backdrop filter rpdq->rect.
  EXPECT_EQ(0U, overlay_data.promoted_overlays.size());
  EXPECT_EQ(gfx::Rect(0, 0, 300, 300), overlay_data.damage_rect);
}

// Test if overlay is not used when video capture is on.
TEST_F(DCLayerOverlayProcessorTest, VideoCapture) {
  InitializeDCLayerOverlayProcessor();
  // Frame #0
  {
    auto pass = CreateRenderPass();
    pass->shared_quad_state_list.back();
    // Create a solid quad.
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 0, 32, 32), SkColors::kRed);

    // Create a video YUV quad below the red solid quad.
    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    gfx::Rect rect(0, 0, 256, 256);
    video_quad->rect = rect;
    video_quad->visible_rect = rect;
    pass->shared_quad_state_list.back()->overlay_damage_index = 1;

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 256, 256);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {
        gfx::Rect(0, 0, 32, 32), gfx::Rect(0, 0, 256, 256)};
    // No video capture in this frame.
    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Use overlay for the video quad.
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
  }

  // Frame #1
  {
    auto pass = CreateRenderPass();
    pass->shared_quad_state_list.back();
    // Create a solid quad.
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 0, 32, 32), SkColors::kRed);

    // Create a video YUV quad below the red solid quad.
    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    gfx::Rect rect(0, 0, 256, 256);
    video_quad->rect = rect;
    video_quad->visible_rect = rect;
    pass->shared_quad_state_list.back()->overlay_damage_index = 0;

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 256, 256);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {
        gfx::Rect(0, 0, 256, 256)};

    // Now video capture is enabled.
    pass_list.back()->video_capture_enabled = true;
    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Should not use overlay for the video when video capture is on.
    EXPECT_EQ(0U, overlay_data.promoted_overlays.size());

    // Check whether both quads including the YUV quads are still in the render
    // pass.
    auto* root_pass = pass_list.back().get();
    int quad_count = root_pass->quad_list.size();
    EXPECT_EQ(2, quad_count);
  }
}

// Check that video capture on a non-root pass does not affect overlay promotion
// on the root pass itself.
TEST_F(DCLayerOverlayProcessorTest, VideoCaptureOnIsolatedRenderPass) {
  InitializeDCLayerOverlayProcessor();

  AggregatedRenderPassList pass_list;

  // Create a render pass with video capture enabled. This could represent e.g.
  // capture of a background tab for stream.
  {
    auto pass = CreateRenderPass();
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 0, 32, 32), SkColors::kRed);
    pass->video_capture_enabled = true;
    pass_list.push_back(std::move(pass));
  }

  // Create a root render pass with a video quad that can be promoted to
  // overlay.
  {
    auto root_pass = CreateRenderPass();
    // Create a solid quad.
    CreateOpaqueQuadAt(
        resource_provider_.get(), root_pass->shared_quad_state_list.back(),
        root_pass.get(), gfx::Rect(0, 0, 32, 32), SkColors::kRed);

    // Create a video YUV quad below the red solid quad.
    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), root_pass->shared_quad_state_list.back(),
        root_pass.get());
    gfx::Rect rect(0, 0, 256, 256);
    video_quad->rect = rect;
    video_quad->visible_rect = rect;
    root_pass->shared_quad_state_list.back()->overlay_damage_index = 0;
    root_pass->damage_rect = gfx::Rect(0, 0, 256, 256);
    pass_list.push_back(std::move(root_pass));
  }

  OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
  OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;

  SurfaceDamageRectList surface_damage_rect_list = {gfx::Rect(0, 0, 256, 256)};

  auto overlay_data = ProcessRootPassForOverlays(
      &pass_list, render_pass_filters, render_pass_backdrop_filters,
      std::move(surface_damage_rect_list));

  EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
}

TEST_F(DCLayerOverlayProcessorTest, RenderPassRootTransformOverlay) {
  TestRenderPassRootTransform(/*is_overlay*/ true);
}

TEST_F(DCLayerOverlayProcessorTest, RenderPassRootTransformUnderlay) {
  TestRenderPassRootTransform(/*is_overlay*/ false);
}

// Tests processing overlays/underlays in a render pass that contains a
// non-identity transform to root.
void DCLayerOverlayProcessorTest::TestRenderPassRootTransform(bool is_overlay) {
  InitializeDCLayerOverlayProcessor();
  const gfx::Rect kOutputRect = gfx::Rect(0, 0, 256, 256);
  const gfx::Rect kVideoRect = gfx::Rect(0, 0, 100, 100);
  const gfx::Rect kOpaqueRect = gfx::Rect(90, 80, 15, 30);
  const gfx::Transform kRenderPassToRootTransform =
      gfx::Transform::MakeTranslation(20, 45);
  // Surface damages in root space.
  const SurfaceDamageRectList kSurfaceDamageRectList = {
      // On top and does not intersect overlay. Translates to (110,5 20x10) in
      // render pass space.
      gfx::Rect(130, 50, 20, 10),
      // The video overlay damage rect. (0,0 100x100) in render pass space.
      gfx::Rect(20, 45, 100, 100),
      // Under and intersects the overlay. Translates to (95,25 20x10) in
      // render pass space.
      gfx::Rect(115, 70, 20, 10)};
  const size_t kOverlayDamageIndex = 1;

  for (size_t frame = 0; frame < 3; frame++) {
    auto pass = CreateRenderPass();
    pass->transform_to_root_target = kRenderPassToRootTransform;
    pass->shared_quad_state_list.back()->overlay_damage_index =
        kOverlayDamageIndex;

    if (!is_overlay) {
      // Create a quad that occludes the video to force it to an underlay.
      CreateOpaqueQuadAt(resource_provider_.get(),
                         pass->shared_quad_state_list.back(), pass.get(),
                         kOpaqueRect, SkColors::kWhite);
    }

    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    video_quad->rect = gfx::Rect(kVideoRect);
    video_quad->visible_rect = video_quad->rect;

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = kOutputRect;
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = kSurfaceDamageRectList;

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));
    LOG(INFO) << "frame " << frame
              << " damage rect: " << overlay_data.damage_rect.ToString();

    EXPECT_EQ(overlay_data.promoted_overlays.size(), 1u);
    EXPECT_TRUE(absl::holds_alternative<gfx::Transform>(
        overlay_data.promoted_overlays[0].transform));
    EXPECT_EQ(
        absl::get<gfx::Transform>(overlay_data.promoted_overlays[0].transform),
        kRenderPassToRootTransform);
    if (is_overlay) {
      EXPECT_GT(overlay_data.promoted_overlays[0].plane_z_order, 0);
    } else {
      EXPECT_LT(overlay_data.promoted_overlays[0].plane_z_order, 0);
    }

    if (frame == 0) {
      // On the first frame, the damage rect should be unchanged since the
      // overlays are being processed for the first time.
      EXPECT_EQ(gfx::Rect(0, 0, 256, 256), overlay_data.damage_rect);
    } else {
      // To calculate the damage rect in root space, we first subtract the video
      // damage from (115,70 20x10) since this damage is under the video. This
      // results in (120,70 20x10). This then gets unioned with (130,50 20x10),
      // which doesn't intersect the video. This results in (120,50 30x30).
      // The damage rect returned from the DCLayerOverlayProcessor is in
      // render pass space, so we apply the (20, 45) inverse transform,
      // resulting in (100,5 30x30).
      EXPECT_EQ(overlay_data.damage_rect, gfx::Rect(100, 5, 30, 30));
    }
  }
}

// Tests processing overlays/underlays on multiple render passes per frame,
// where only one render pass has an overlay.
TEST_F(DCLayerOverlayProcessorTest, MultipleRenderPassesOneOverlay) {
  InitializeDCLayerOverlayProcessor(/*allowed_yuv_overlay_count*/ 1);
  const gfx::Rect output_rect = {0, 0, 256, 256};
  const size_t num_render_passes = 3;
  for (size_t frame = 0; frame < 3; frame++) {
    AggregatedRenderPassList render_passes;  // Used to keep render passes alive
    DCLayerOverlayProcessor::RenderPassOverlayDataMap
        render_pass_overlay_data_map;

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    SurfaceDamageRectList surface_damage_rect_list;

    // Create 3 render passes, with only one containing an overlay candidate.
    for (size_t id = 1; id <= num_render_passes; id++) {
      auto pass = CreateRenderPass(AggregatedRenderPassId{id});
      pass->transform_to_root_target = gfx::Transform::MakeTranslation(id, 0);

      gfx::Rect quad_rect_in_root_space =
          gfx::Rect(0, 0, id * 16, pass->output_rect.height());

      if (id == 1) {
        // Create an overlay quad in the first render pass.
        auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
            resource_provider_.get(), child_resource_provider_.get(),
            child_provider_.get(), pass->shared_quad_state_list.back(),
            pass.get());
        gfx::Rect quad_rect_in_quad_space =
            pass->transform_to_root_target
                .InverseMapRect(quad_rect_in_root_space)
                .value();
        video_quad->rect = quad_rect_in_quad_space;
        video_quad->visible_rect = quad_rect_in_quad_space;
        pass->shared_quad_state_list.back()->overlay_damage_index = id - 1;
      } else {
        // Create a quad that's not an overlay.
        CreateSolidColorQuadAt(pass->shared_quad_state_list.back(),
                               SkColors::kBlue, pass.get(),
                               pass->transform_to_root_target
                                   .InverseMapRect(quad_rect_in_root_space)
                                   .value());
      }

      surface_damage_rect_list.emplace_back(quad_rect_in_root_space);
      render_pass_overlay_data_map[pass.get()].damage_rect = output_rect;
      render_passes.emplace_back(std::move(pass));
    }

    surface_damage_rect_list.emplace_back(0, 0, 256, 256);

    dc_layer_overlay_processor_->Process(
        resource_provider_.get(), render_pass_filters,
        render_pass_backdrop_filters, std::move(surface_damage_rect_list),
        /*is_page_fullscreen_mode=*/false, render_pass_overlay_data_map);

    for (auto& [render_pass, overlay_data] : render_pass_overlay_data_map) {
      LOG(INFO) << "frame " << frame << " render pass " << render_pass->id
                << " damage rect : " << overlay_data.damage_rect.ToString();
      LOG(INFO) << "frame " << frame << " render pass " << render_pass->id
                << " number of overlays: "
                << overlay_data.promoted_overlays.size();

      if (render_pass->id == AggregatedRenderPassId(1)) {
        // The render pass that contains an overlay.
        EXPECT_EQ(overlay_data.promoted_overlays.size(), 1u);
        EXPECT_EQ(absl::get<gfx::Transform>(
                      overlay_data.promoted_overlays[0].transform),
                  gfx::Transform::MakeTranslation(1, 0));
        EXPECT_GT(overlay_data.promoted_overlays[0].plane_z_order, 0);

        // The rect of the candidate should be in render pass space, which is an
        // arbitrary space. Combining with the render pass to root transform
        // results in a rect in root space. The transform is defined above as an
        // x translation of -1.
        EXPECT_EQ(overlay_data.promoted_overlays[0].display_rect,
                  gfx::RectF(-1, 0, 16, 256));

        if (frame == 0) {
          // On the first frame, the damage rect should be unchanged since the
          // overlays are being processed for the first time.
          EXPECT_EQ(overlay_data.damage_rect, output_rect);
        } else {
          // On subsequent frames, the video rect should be subtracted from
          // the damage rect. The x coordinate is 15 instead of 16 because of
          // the root_to_transform_target. The surface_damage_rect_list damages
          // are in root space, while the damage_rect output is in render pass
          // space.
          EXPECT_EQ(overlay_data.damage_rect, gfx::Rect(15, 0, 240, 256));
        }
      } else {
        // All other render passes do not have overlays.
        EXPECT_TRUE(overlay_data.promoted_overlays.empty());

        // With no overlays, the damage should be unchanged since there are no
        // overlays to subtract.
        EXPECT_EQ(overlay_data.damage_rect, output_rect);
      }
    }
  }
}

// Tests processing overlays/underlays on multiple render passes per frame, with
// each render pass having an overlay. This exceeds the maximum allowed number
// of overlays, so all overlays should be rejected.
TEST_F(DCLayerOverlayProcessorTest,
       MultipleRenderPassesExceedsOverlayAllowance) {
  const gfx::Rect output_rect = {0, 0, 256, 256};
  const size_t num_render_passes = 3;
  InitializeDCLayerOverlayProcessor(num_render_passes - 1);
  for (size_t frame = 1; frame <= 3; frame++) {
    AggregatedRenderPassList
        render_passes;  // Used to keep render passes alive.
    DCLayerOverlayProcessor::RenderPassOverlayDataMap
        render_pass_overlay_data_map;

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    SurfaceDamageRectList surface_damage_rect_list;

    // Create 3 render passes that all have a video overlay candidate. Start
    // at the frame number so that we switch up the render pass IDs to verify
    // that render passes that do not exist are not kept in
    // |DCLayerOverlayProcessor::previous_frame_render_pass_states_|.
    for (size_t id = frame; id < num_render_passes + frame; id++) {
      auto pass = CreateRenderPass(AggregatedRenderPassId{id});
      pass->transform_to_root_target = gfx::Transform::MakeTranslation(id, 0);
      pass->shared_quad_state_list.back()->overlay_damage_index = id - 1;

      gfx::Rect video_rect_in_root_space =
          gfx::Rect(0, 0, id * 16, pass->output_rect.height());

      gfx::Rect video_rect_in_render_pass_space =
          pass->transform_to_root_target
              .InverseMapRect(video_rect_in_root_space)
              .value();
      auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
          resource_provider_.get(), child_resource_provider_.get(),
          child_provider_.get(), pass->shared_quad_state_list.back(),
          pass.get());
      video_quad->rect = video_rect_in_render_pass_space;
      video_quad->visible_rect = video_rect_in_render_pass_space;

      surface_damage_rect_list.emplace_back(video_rect_in_root_space);
      render_pass_overlay_data_map[pass.get()].damage_rect = output_rect;
      render_passes.emplace_back(std::move(pass));
    }

    surface_damage_rect_list.emplace_back(0, 0, 256, 256);

    dc_layer_overlay_processor_->Process(
        resource_provider_.get(), render_pass_filters,
        render_pass_backdrop_filters, std::move(surface_damage_rect_list),
        /*is_page_fullscreen_mode=*/false, render_pass_overlay_data_map);

    // Verify that the previous frame states contain only 3 render passes and
    // that they have the IDs that we set them to.
    EXPECT_EQ(
        3U,
        dc_layer_overlay_processor_->get_previous_frame_render_pass_count());
    std::vector<AggregatedRenderPassId> previous_frame_render_pass_ids =
        dc_layer_overlay_processor_->get_previous_frame_render_pass_ids();
    std::sort(previous_frame_render_pass_ids.begin(),
              previous_frame_render_pass_ids.end());
    for (size_t id = frame; id < num_render_passes + frame; id++) {
      EXPECT_EQ(id, previous_frame_render_pass_ids[id - frame].value());
    }

    for (auto& [render_pass, overlay_data] : render_pass_overlay_data_map) {
      LOG(INFO) << "frame " << frame << " render pass " << render_pass->id
                << " damage rect : " << overlay_data.damage_rect.ToString();
      LOG(INFO) << "frame " << frame << " render pass " << render_pass->id
                << " number of overlays: "
                << overlay_data.promoted_overlays.size();

      // Since there is more than one overlay, all overlays should be rejected.
      EXPECT_EQ(overlay_data.promoted_overlays.size(), 0u);

      // With no overlays, the damage should be unchanged since there are no
      // overlays to subtract.
      EXPECT_EQ(overlay_data.damage_rect, output_rect);
    }
  }
}

// When there are multiple videos intersected with each other, only the topmost
// of them should be considered as "overlay".
TEST_F(DCLayerOverlayProcessorTest, MultipleYUVOverlaysIntersected) {
  InitializeDCLayerOverlayProcessor(/*allowed_yuv_overlay_count=*/2);
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::kNoUndamagedOverlayPromotion);
  {
    auto pass = CreateRenderPass();

    // Video 1: Topmost video.
    auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    gfx::Rect rect(150, 150, 50, 50);
    video_quad->rect = rect;
    video_quad->visible_rect = rect;
    pass->shared_quad_state_list.back()->overlay_damage_index = 1;

    // Video 2: Intersected with and under the 1st video.
    auto* second_video_quad = CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
    gfx::Rect second_rect(100, 100, 120, 120);
    second_video_quad->rect = second_rect;
    second_video_quad->visible_rect = second_rect;
    pass->shared_quad_state_list.back()->overlay_damage_index = 2;

    // Background.
    CreateOpaqueQuadAt(resource_provider_.get(),
                       pass->shared_quad_state_list.back(), pass.get(),
                       gfx::Rect(0, 0, 256, 256), SkColors::kWhite);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    surface_damage_rect_list.push_back(video_quad->rect);
    surface_damage_rect_list.push_back(second_video_quad->rect);
    surface_damage_rect_list.push_back(gfx::Rect(0, 0, 256, 256));

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    int overlay_cnt = 0;
    for (auto& dc : overlay_data.promoted_overlays) {
      if (dc.plane_z_order > 0) {
        // The overlay video should be the topmost one.
        EXPECT_EQ(gfx::Rect(150, 150, 50, 50),
                  gfx::ToEnclosingRect(dc.display_rect));
        overlay_cnt++;
      }
    }

    EXPECT_EQ(1, overlay_cnt);
  }
}

TEST_F(DCLayerOverlayProcessorTest, HDR10VideoOverlay) {
  InitializeDCLayerOverlayProcessor();
  // Prepare a valid hdr metadata.
  gfx::HDRMetadata valid_hdr_metadata;
  valid_hdr_metadata.cta_861_3 = gfx::HdrMetadataCta861_3(1000, 400);
  valid_hdr_metadata.smpte_st_2086 =
      gfx::HdrMetadataSmpteSt2086(SkNamedPrimariesExt::kRec2020, 1000, 0.0001);

  // Device has RGB10A2 overlay support.
  gl::SetDirectCompositionScaledOverlaysSupportedForTesting(true);

  // Device has HDR-enabled display and no non-HDR-enabled display.
  dc_layer_overlay_processor_
      ->set_system_hdr_disabled_on_any_display_for_testing(false);

  // Device has video processor support.
  dc_layer_overlay_processor_->set_has_p010_video_processor_support_for_testing(
      true);

  // Frame 1 should promote overlay as all conditions satisfied.
  {
    auto pass = CreateRenderPass();
    pass->content_color_usage = gfx::ContentColorUsage::kHDR;

    // Content is 10bit P010 content with HDR10 colorspace.
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get(),
        gfx::ColorSpace::CreateHDR10(), valid_hdr_metadata,
        MultiPlaneFormat::kP010);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Should promote overlay.
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
  }

  // Frame 2 should skip overlay as bit depth not satisfied.
  {
    auto pass = CreateRenderPass();
    pass->content_color_usage = gfx::ContentColorUsage::kHDR;

    // Content is 8bit NV12 (not satisfied) content with HDR10 colorspace.
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get(),
        gfx::ColorSpace::CreateHDR10(), valid_hdr_metadata,
        MultiPlaneFormat::kNV12);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Should skip overlay.
    EXPECT_EQ(0U, overlay_data.promoted_overlays.size());
  }

  // Frame 3 should skip overlay as hdr metadata is invalid.
  {
    auto pass = CreateRenderPass();
    pass->content_color_usage = gfx::ContentColorUsage::kHDR;
    // Content is 10bit P010 content with HDR10 colorspace, but invalid HDR
    // metadata (not satisfied).
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get(),
        gfx::ColorSpace::CreateHDR10(), gfx::HDRMetadata(),
        MultiPlaneFormat::kP010);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Should skip overlay.
    EXPECT_EQ(0U, overlay_data.promoted_overlays.size());
  }

  // Frame 4 should promote overlay as hdr metadata contains cta_861_3.
  {
    auto pass = CreateRenderPass();
    pass->content_color_usage = gfx::ContentColorUsage::kHDR;

    // Content has HDR metadata which contains cta_861_3.
    gfx::HDRMetadata cta_861_3_hdr_metadata;
    cta_861_3_hdr_metadata.cta_861_3 = gfx::HdrMetadataCta861_3(0, 400);
    // Content is 10bit P010 content with HDR10 colorspace.
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get(),
        gfx::ColorSpace::CreateHDR10(), cta_861_3_hdr_metadata,
        MultiPlaneFormat::kP010);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Should promote overlay.
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
  }

  // Frame 5 should promote overlay as hdr metadata contains smpte_st_2086.
  {
    auto pass = CreateRenderPass();
    pass->content_color_usage = gfx::ContentColorUsage::kHDR;

    // Content has HDR metadata which contains smpte_st_2086.
    gfx::HDRMetadata smpte_st_2086_hdr_metadata;
    smpte_st_2086_hdr_metadata.smpte_st_2086 = gfx::HdrMetadataSmpteSt2086(
        SkNamedPrimariesExt::kRec2020, 1000, 0.0001);
    // Content is 10bit P010 content with HDR10 colorspace.
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get(),
        gfx::ColorSpace::CreateHDR10(), smpte_st_2086_hdr_metadata,
        MultiPlaneFormat::kP010);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Should promote overlay.
    EXPECT_EQ(1U, overlay_data.promoted_overlays.size());
  }

  // Frame 6 should skip overlay as color space not satisfied.
  {
    auto pass = CreateRenderPass();
    pass->content_color_usage = gfx::ContentColorUsage::kHDR;

    // Content is 10bit P010 content with HDR colorspace but not in PQ transfer
    // (not satisfied).
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get(),
        gfx::ColorSpace::CreateHLG(), valid_hdr_metadata,
        MultiPlaneFormat::kP010);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Should skip overlay.
    EXPECT_EQ(0U, overlay_data.promoted_overlays.size());
  }

  // Frame 7 should skip overlay as no P010 video processor support.
  {
    dc_layer_overlay_processor_
        ->set_has_p010_video_processor_support_for_testing(false);

    auto pass = CreateRenderPass();
    pass->content_color_usage = gfx::ContentColorUsage::kHDR;

    // Content is 10bit P010 content with HDR10 colorspace.
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get(),
        gfx::ColorSpace::CreateHDR10(), valid_hdr_metadata,
        MultiPlaneFormat::kP010);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Should skip overlay.
    EXPECT_EQ(0U, overlay_data.promoted_overlays.size());

    // Recover config.
    dc_layer_overlay_processor_
        ->set_has_p010_video_processor_support_for_testing(true);
  }

  // Frame 8 should skip overlay as non-HDR-enabled display exists.
  {
    dc_layer_overlay_processor_
        ->set_system_hdr_disabled_on_any_display_for_testing(true);

    auto pass = CreateRenderPass();
    pass->content_color_usage = gfx::ContentColorUsage::kHDR;

    // Content is 10bit P010 content with HDR10 colorspace.
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get(),
        gfx::ColorSpace::CreateHDR10(), valid_hdr_metadata,
        MultiPlaneFormat::kP010);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Should skip overlay.
    EXPECT_EQ(0U, overlay_data.promoted_overlays.size());

    // Recover config.
    dc_layer_overlay_processor_
        ->set_system_hdr_disabled_on_any_display_for_testing(false);
  }

  // Frame 9 should skip overlay as no rgb10a2 overlay support.
  {
    gl::SetDirectCompositionScaledOverlaysSupportedForTesting(false);

    auto pass = CreateRenderPass();
    pass->content_color_usage = gfx::ContentColorUsage::kHDR;

    // Content is 10bit P010 content with HDR10 colorspace.
    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get(),
        gfx::ColorSpace::CreateHDR10(), valid_hdr_metadata,
        MultiPlaneFormat::kP010);

    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    pass->damage_rect = gfx::Rect(0, 0, 220, 220);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    auto overlay_data = ProcessRootPassForOverlays(
        &pass_list, render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list));

    // Should skip overlay.
    EXPECT_EQ(0U, overlay_data.promoted_overlays.size());

    // Recover config.
    gl::SetDirectCompositionScaledOverlaysSupportedForTesting(true);
  }
}

class OverlayProcessorWinStaticTest : public testing::Test {};

MATCHER(ResourceIdEq, "") {
  return std::get<0>(arg).resource_id == ResourceId(std::get<1>(arg));
}

MATCHER(PlaneZOrdersAreUnique, "") {
  const OverlayCandidateList& candidates = arg;
  base::flat_set<int> z_orders;
  for (const auto& candidate : candidates) {
    z_orders.insert(candidate.plane_z_order);
  }
  return candidates.size() == z_orders.size();
}

// Checks that, when the overlay candidates list is sorted by z-order, the
// resource IDs of the candidates matches |expected_resource_ids|. Note these
// resource IDs are not real and a just used to identify overlay candidates in
// tests.
testing::Matcher<const OverlayCandidateList&>
WhenCandidatesAreSortedResourceIdsAre(
    const std::vector<int>& expected_resource_ids) {
  return testing::AllOf(
      PlaneZOrdersAreUnique(),
      testing::WhenSortedBy(
          test::PlaneZOrderAscendingComparator(),
          testing::Pointwise(ResourceIdEq(), expected_resource_ids)));
}

TEST_F(OverlayProcessorWinStaticTest, InsertSurfaceContentOverlay) {
  // Set up a dummy render pass and RPDQ
  AggregatedRenderPass pass;
  pass.id = AggregatedRenderPassId(1);
  AggregatedRenderPassDrawQuad rpdq;
  rpdq.render_pass_id = pass.id;

  DCLayerOverlayProcessor::RenderPassOverlayDataMap
      surface_content_render_passes;
  DCLayerOverlayProcessor::RenderPassOverlayData overlay_data;
  {
    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(3);
    overlay_data.promoted_overlays.back().plane_z_order = 1;
  }
  surface_content_render_passes.insert({&pass, std::move(overlay_data)});

  OverlayCandidateList candidates;
  {
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(4);

    // Pretend this candidate is a RPDQ that we've pulled overlays from.
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(2);
    candidates.back().rpdq = &rpdq;

    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(1);
  }

  std::ignore = OverlayProcessorWin::
      InsertSurfaceContentOverlaysAndSetPlaneZOrderForTesting(
          std::move(surface_content_render_passes), candidates);

  EXPECT_THAT(candidates, WhenCandidatesAreSortedResourceIdsAre({1, 2, 3, 4}));
}

TEST_F(OverlayProcessorWinStaticTest, InsertSurfaceContentUnderlay) {
  // Set up a dummy render pass and RPDQ
  AggregatedRenderPass pass;
  pass.id = AggregatedRenderPassId(1);
  AggregatedRenderPassDrawQuad rpdq;
  rpdq.render_pass_id = pass.id;

  DCLayerOverlayProcessor::RenderPassOverlayDataMap
      surface_content_render_passes;
  DCLayerOverlayProcessor::RenderPassOverlayData overlay_data;
  {
    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(3);
    overlay_data.promoted_overlays.back().plane_z_order = -1;
  }
  surface_content_render_passes.insert({&pass, std::move(overlay_data)});

  OverlayCandidateList candidates;
  {
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(4);

    // Pretend this candidate is a RPDQ that we've pulled overlays from.
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(2);
    candidates.back().rpdq = &rpdq;

    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(1);
  }

  std::ignore = OverlayProcessorWin::
      InsertSurfaceContentOverlaysAndSetPlaneZOrderForTesting(
          std::move(surface_content_render_passes), candidates);

  EXPECT_THAT(candidates, WhenCandidatesAreSortedResourceIdsAre({1, 3, 2, 4}));
}

// Check that |InsertSurfaceContentOverlaysAndSetPlaneZOrder| supports promoted
// overlay candidates that have gaps in the z-order.
TEST_F(OverlayProcessorWinStaticTest,
       InsertSurfaceContentOverlaysWithGapsInZOrder) {
  // Set up a dummy render pass and RPDQ
  AggregatedRenderPass pass;
  pass.id = AggregatedRenderPassId(1);
  AggregatedRenderPassDrawQuad rpdq;
  rpdq.render_pass_id = pass.id;

  DCLayerOverlayProcessor::RenderPassOverlayDataMap
      surface_content_render_passes;
  DCLayerOverlayProcessor::RenderPassOverlayData overlay_data;
  {
    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(2);
    overlay_data.promoted_overlays.back().plane_z_order = -3;

    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(4);
    overlay_data.promoted_overlays.back().plane_z_order = 3;
  }
  surface_content_render_passes.insert({&pass, std::move(overlay_data)});

  OverlayCandidateList candidates;
  {
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(5);

    // Pretend this candidate is a RPDQ that we've pulled overlays from.
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(3);
    candidates.back().rpdq = &rpdq;

    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(1);
  }

  std::ignore = OverlayProcessorWin::
      InsertSurfaceContentOverlaysAndSetPlaneZOrderForTesting(
          std::move(surface_content_render_passes), candidates);

  EXPECT_THAT(candidates,
              WhenCandidatesAreSortedResourceIdsAre({1, 2, 3, 4, 5}));
}

TEST_F(OverlayProcessorWinStaticTest,
       InsertSurfaceContentOverlaysWithNoPromotedOverlays) {
  // Set up a dummy render pass and RPDQ
  AggregatedRenderPass pass;
  pass.id = AggregatedRenderPassId(1);
  AggregatedRenderPassDrawQuad rpdq;
  rpdq.render_pass_id = pass.id;

  DCLayerOverlayProcessor::RenderPassOverlayDataMap
      surface_content_render_passes;
  DCLayerOverlayProcessor::RenderPassOverlayData overlay_data;
  // No candidates in |overlay_data|.
  surface_content_render_passes.insert({&pass, std::move(overlay_data)});

  OverlayCandidateList candidates;
  {
    // Pretend this candidate is a RPDQ that we've pulled overlays from.
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(2);
    candidates.back().rpdq = &rpdq;

    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(1);
  }

  std::ignore = OverlayProcessorWin::
      InsertSurfaceContentOverlaysAndSetPlaneZOrderForTesting(
          std::move(surface_content_render_passes), candidates);

  EXPECT_THAT(candidates, WhenCandidatesAreSortedResourceIdsAre({1, 2}));
}

TEST_F(OverlayProcessorWinStaticTest,
       InsertSurfaceContentOverlaysWithUnderlays) {
  // Set up a dummy render pass and RPDQ
  AggregatedRenderPass pass;
  pass.id = AggregatedRenderPassId(1);
  AggregatedRenderPassDrawQuad rpdq;
  rpdq.render_pass_id = pass.id;

  DCLayerOverlayProcessor::RenderPassOverlayDataMap
      surface_content_render_passes;
  DCLayerOverlayProcessor::RenderPassOverlayData overlay_data;
  {
    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(5);
    overlay_data.promoted_overlays.back().plane_z_order = 1;

    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(2);
    overlay_data.promoted_overlays.back().plane_z_order = -2;

    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(3);
    overlay_data.promoted_overlays.back().plane_z_order = -1;

    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(6);
    overlay_data.promoted_overlays.back().plane_z_order = 2;
  }
  surface_content_render_passes.insert({&pass, std::move(overlay_data)});

  OverlayCandidateList candidates;
  {
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(8);

    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(7);

    // Pretend this candidate is a RPDQ that we've pulled overlays from.
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(4);
    candidates.back().rpdq = &rpdq;

    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(1);
  }

  std::ignore = OverlayProcessorWin::
      InsertSurfaceContentOverlaysAndSetPlaneZOrderForTesting(
          std::move(surface_content_render_passes), candidates);

  EXPECT_THAT(candidates,
              WhenCandidatesAreSortedResourceIdsAre({1, 2, 3, 4, 5, 6, 7, 8}));
}

TEST_F(OverlayProcessorWinStaticTest,
       InsertSurfaceContentOverlaysMultipleSurfaces) {
  // Set up dummy render passes and RPDQs
  AggregatedRenderPass pass1;
  pass1.id = AggregatedRenderPassId(1);
  AggregatedRenderPassDrawQuad rpdq1;
  rpdq1.render_pass_id = pass1.id;
  AggregatedRenderPass pass2;
  pass2.id = AggregatedRenderPassId(2);
  AggregatedRenderPassDrawQuad rpdq2;
  rpdq2.render_pass_id = pass2.id;

  DCLayerOverlayProcessor::RenderPassOverlayDataMap
      surface_content_render_passes;
  DCLayerOverlayProcessor::RenderPassOverlayData overlay_data;
  {
    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(2);
    overlay_data.promoted_overlays.back().plane_z_order = 1;
  }
  surface_content_render_passes.insert({&pass1, std::move(overlay_data)});

  {
    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(4);
    overlay_data.promoted_overlays.back().plane_z_order = 1;
  }
  surface_content_render_passes.insert({&pass2, std::move(overlay_data)});

  OverlayCandidateList candidates;
  {
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(5);

    // Pretend this candidate is a RPDQ that we've pulled overlays from.
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(3);
    candidates.back().rpdq = &rpdq2;

    // Pretend this candidate is a RPDQ that we've pulled overlays from.
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(1);
    candidates.back().rpdq = &rpdq1;
  }

  std::ignore = OverlayProcessorWin::
      InsertSurfaceContentOverlaysAndSetPlaneZOrderForTesting(
          std::move(surface_content_render_passes), candidates);

  EXPECT_THAT(candidates,
              WhenCandidatesAreSortedResourceIdsAre({1, 2, 3, 4, 5}));
}

TEST_F(OverlayProcessorWinStaticTest,
       InsertSurfaceContentOverlaysSameSurfaceEmbeddedTwice) {
  // Set up a dummy render pass and RPDQ
  AggregatedRenderPass pass;
  pass.id = AggregatedRenderPassId(1);
  AggregatedRenderPassDrawQuad rpdq;
  rpdq.render_pass_id = pass.id;

  DCLayerOverlayProcessor::RenderPassOverlayDataMap
      surface_content_render_passes;
  DCLayerOverlayProcessor::RenderPassOverlayData overlay_data;
  {
    overlay_data.promoted_overlays.emplace_back();
    overlay_data.promoted_overlays.back().resource_id = ResourceId(3);
    overlay_data.promoted_overlays.back().plane_z_order = 1;
  }
  surface_content_render_passes.insert({&pass, std::move(overlay_data)});

  OverlayCandidateList candidates;
  {
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(6);

    // Pretend this candidate is a RPDQ that we've pulled overlays from.
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(4);
    candidates.back().rpdq = &rpdq;

    // Pretend this candidate is a RPDQ that we've pulled overlays from.
    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(2);
    candidates.back().rpdq = &rpdq;

    candidates.emplace_back();
    candidates.back().resource_id = ResourceId(1);
  }

  std::ignore = OverlayProcessorWin::
      InsertSurfaceContentOverlaysAndSetPlaneZOrderForTesting(
          std::move(surface_content_render_passes), candidates);

  EXPECT_THAT(candidates, WhenCandidatesAreSortedResourceIdsAre(
                              {1, 2, 3, 4,
                               3,  // We've embedded this overlay twice
                               6}));
}

class TestOverlayProcessorWin : public OverlayProcessorWin {
 public:
  explicit TestOverlayProcessorWin(int allowed_yuv_overlay_count)
      : OverlayProcessorWin(OutputSurface::DCSupportLevel::kDCompTexture,
                            &debug_settings_,
                            std::make_unique<DCLayerOverlayProcessor>(
                                allowed_yuv_overlay_count,
                                /*skip_initialization_for_testing=*/true)) {}
  DebugRendererSettings debug_settings_;
};

class OverlayProcessorWinTest : public OverlayProcessorTestBase {
 protected:
  void SetUp() override {
    OverlayProcessorTestBase::SetUp();

    overlay_processor_ = std::make_unique<TestOverlayProcessorWin>(
        /*allowed_yuv_overlay_count=*/1);
    overlay_processor_->SetUsingDCLayersForTesting(kDefaultRootPassId, true);
    overlay_processor_->SetViewportSize(gfx::Size(256, 256));

    EXPECT_TRUE(overlay_processor_->IsOverlaySupported());

    output_surface_plane_ =
        OverlayProcessorInterface::OutputSurfaceOverlayPlane();
  }

  void TearDown() override {
    overlay_processor_ = nullptr;
    OverlayProcessorTestBase::TearDown();
  }

  OverlayProcessorInterface::OutputSurfaceOverlayPlane*
  GetOutputSurfacePlane() {
    EXPECT_TRUE(output_surface_plane_.has_value());
    return &output_surface_plane_.value();
  }

  std::optional<OverlayProcessorInterface::OutputSurfaceOverlayPlane>
      output_surface_plane_;
  std::unique_ptr<OverlayProcessorWin> overlay_processor_;
  gfx::Rect damage_rect_;
  std::vector<gfx::Rect> content_bounds_;
};

enum class SurfaceTestMode {
  RootSurface,
  SimulatePartiallyDelegated,
};

// Tests that check the behavior of surface planes returned from
// OverlayProcessorWin. These planes are render passes that OverlayProcessorWin
// treats as a surface to "hole punch" overlays out of. This is normally the
// root render pass. In partially delegated compositing, any web contents pass.
class OverlayProcessorWinSurfacePlaneTest
    : public OverlayProcessorWinTest,
      public testing::WithParamInterface<SurfaceTestMode> {
 public:
  static std::string GetParamName(
      const testing::TestParamInfo<ParamType>& info) {
    switch (info.param) {
      case SurfaceTestMode::RootSurface:
        return "RootSurface";
      case SurfaceTestMode::SimulatePartiallyDelegated:
        return "SimulatePartiallyDelegated";
    }
  }

 protected:
  OverlayProcessorWinSurfacePlaneTest() {
    switch (GetParam()) {
      case SurfaceTestMode::RootSurface:
        feature_list_.InitAndDisableFeature(features::kDelegatedCompositing);
        break;
      case SurfaceTestMode::SimulatePartiallyDelegated:
        feature_list_.InitAndEnableFeatureWithParameters(
            features::kDelegatedCompositing, {{"mode", "limit_to_ui"}});
        break;
    }
  }

  void ProcessForOverlays(
      AggregatedRenderPassList* render_passes,
      const OverlayProcessorInterface::FilterOperationsMap& render_pass_filters,
      const OverlayProcessorInterface::FilterOperationsMap&
          render_pass_backdrop_filters,
      SurfaceDamageRectList surface_damage_rect_list_in_root_space,
      OverlayCandidateList* candidates) {
    // Wraps the root pass of |pass_list| in a RPDQ to simulate the
    // SurfaceAggregator not merging the web contents root pass, which is what
    // happens during partial delegation.
    //
    // On cleanup, we remove the root pass that we added and the RPDQ overlay
    // added as the explicit output surface plane. We also adjust candidate's
    // z-orders to be relative to 0 instead of the RPDQ overlay we removed.
    //
    // Note this does not touch the |OutputSurfaceOverlayPlane| that the overlay
    // processor modifies.
    class ScopedSimulateUnmergedWebContentsSurface {
     public:
      ScopedSimulateUnmergedWebContentsSurface(
          AggregatedRenderPassList* pass_list,
          OverlayCandidateList* candidates,
          gfx::Rect* damage_rect)
          : pass_list_(pass_list),
            candidates_(candidates),
            damage_rect_(damage_rect) {
        // Some tests provide a smaller |damage_rect_| than root pass damage
        // rect. This is normally not possible.
        pass_list_->back()->damage_rect.Intersect(*damage_rect_);

        const AggregatedRenderPassId max_pass_id =
            base::ranges::max_element(*pass_list_, base::ranges::less(),
                                      &AggregatedRenderPass::id)
                ->get()
                ->id;
        const AggregatedRenderPassId unused_pass_id(max_pass_id.value() + 1);

        auto pass = std::make_unique<AggregatedRenderPass>();
        pass->SetNew(unused_pass_id, pass_list_->back()->output_rect,
                     pass_list_->back()->damage_rect, gfx::Transform());

        auto* shared_quad_state = pass->CreateAndAppendSharedQuadState();
        shared_quad_state->SetAll(
            /*transform=*/gfx::Transform(),
            /*layer_rect=*/pass_list_->back()->output_rect,
            /*visible_layer_rect=*/pass_list_->back()->output_rect,
            gfx::MaskFilterInfo(),
            /*clip=*/std::nullopt, /*contents_opaque=*/false,
            /*opacity_f=*/1.f, SkBlendMode::kSrc, /*sorting_context=*/0,
            /*layer_id=*/0u,
            /*fast_rounded_corner=*/false);

        auto* quad =
            pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
        quad->SetNew(
            shared_quad_state, /*rect=*/pass_list_->back()->output_rect,
            /*visible_rect=*/pass_list_->back()->output_rect,
            pass_list_->back()->id,
            /*mask_resource_id=*/kInvalidResourceId,
            /*mask_uv_rect=*/gfx::RectF(),
            /*mask_texture_size=*/gfx::Size(),
            /*filters_scale=*/gfx::Vector2dF(1.0f, 1.0f),
            /*filters_origin=*/gfx::PointF(),
            /*tex_coord_rect=*/gfx::RectF(pass_list_->back()->output_rect),
            /*force_anti_aliasing_off=*/false,
            /*backdrop_filter_quality=*/1.0f);

        // Pretend that our old root pass is actually the root pass of a
        // surface.
        pass_list_->back()->is_from_surface_root_pass = true;

        pass_list_->push_back(std::move(pass));
      }

      ~ScopedSimulateUnmergedWebContentsSurface() {
        auto it =
            base::ranges::find_if(*candidates_, [](const auto& candidate) {
              return candidate.rpdq &&
                     candidate.rpdq->render_pass_id == kDefaultRootPassId;
            });
        CHECK(it != candidates_->end());
        const int surface_z_order = it->plane_z_order;
        candidates_->erase(it);
        for (auto& candidate : *candidates_) {
          candidate.plane_z_order = candidate.plane_z_order - surface_z_order;
        }

        pass_list_->pop_back();
        // The last render pass should now be the surface pass.
        *damage_rect_ = pass_list_->back()->damage_rect;
      }

     private:
      raw_ptr<AggregatedRenderPassList> pass_list_;
      raw_ptr<OverlayCandidateList> candidates_;
      raw_ptr<gfx::Rect> damage_rect_;
    };

    std::optional<ScopedSimulateUnmergedWebContentsSurface>
        simulate_unmerged_web_contents_surface;
    if (GetParam() == SurfaceTestMode::SimulatePartiallyDelegated) {
      simulate_unmerged_web_contents_surface.emplace(render_passes, candidates,
                                                     &damage_rect_);
    }

    output_surface_plane_ =
        OverlayProcessorInterface::OutputSurfaceOverlayPlane();
    overlay_processor_->ProcessForOverlays(
        resource_provider_.get(), render_passes, SkM44(), render_pass_filters,
        render_pass_backdrop_filters,
        std::move(surface_damage_rect_list_in_root_space),
        &output_surface_plane_.value(), candidates, &damage_rect_,
        &content_bounds_);
    overlay_processor_->AdjustOutputSurfaceOverlay(&output_surface_plane_);

    // Sort candidates front-to-back so tests can assume they appear in the same
    // order as the input draw quads.
    base::ranges::sort(*candidates, base::ranges::greater(),
                       &OverlayCandidate::plane_z_order);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Check that we can promote an overlay in the simple case from a surface.
TEST_P(OverlayProcessorWinSurfacePlaneTest, PromoteOverlayFromSurface) {
  AggregatedRenderPassList pass_list;
  auto pass = CreateRenderPass();
  CreateTextureQuadAt(resource_provider_.get(), child_resource_provider_.get(),
                      child_provider_.get(),
                      pass->CreateAndAppendSharedQuadState(), pass.get(),
                      gfx::Rect(0, 0, 50, 50),
                      /*is_overlay_candidate=*/true);
  pass_list.push_back(std::move(pass));

  damage_rect_ = pass_list.back()->output_rect;

  OverlayCandidateList dc_layer_list;
  OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
  OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
  ProcessForOverlays(&pass_list, render_pass_filters,
                     render_pass_backdrop_filters, SurfaceDamageRectList(),
                     &dc_layer_list);

  EXPECT_TRUE(pass_list.back()->needs_synchronous_dcomp_commit);
  EXPECT_EQ(1U, dc_layer_list.size());
}

// Check that we don't accidentally end up in a case where we try to read back a
// DComp surface, which can happen if one issues a copy request while we're in
// the hysteresis when switching from a DComp surface back to a swap chain.
TEST_P(OverlayProcessorWinSurfacePlaneTest, ForceSwapChainForCapture) {
  // Frame with no overlays, but we expect to still be in DComp surface mode,
  // due to one-sided hysteresis intended to prevent allocation churn.
  {
    AggregatedRenderPassList pass_list;
    pass_list.push_back(CreateRenderPass());

    damage_rect_ = pass_list.back()->output_rect;

    OverlayCandidateList dc_layer_list;
    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    ProcessForOverlays(&pass_list, render_pass_filters,
                       render_pass_backdrop_filters, SurfaceDamageRectList(),
                       &dc_layer_list);

    EXPECT_TRUE(pass_list.back()->needs_synchronous_dcomp_commit);
  }

  // Frame with a copy request. Even though we're still in the hysteresis, we
  // expect to forcibly switch to swap chain mode so that the copy request
  // succeeds.
  {
    AggregatedRenderPassList pass_list;
    pass_list.push_back(CreateRenderPass());

    pass_list.back()->copy_requests.push_back(
        CopyOutputRequest::CreateStubForTesting());

    damage_rect_ = pass_list.back()->output_rect;

    OverlayCandidateList dc_layer_list;
    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    ProcessForOverlays(&pass_list, render_pass_filters,
                       render_pass_backdrop_filters, SurfaceDamageRectList(),
                       &dc_layer_list);

    EXPECT_FALSE(pass_list.back()->needs_synchronous_dcomp_commit);
  }
}

TEST_P(OverlayProcessorWinSurfacePlaneTest, UseDCompSurfaceWithVideo) {
  overlay_processor_->SetUsingDCLayersForTesting(kDefaultRootPassId, false);
  // Draw 60 frames with overlay video quads.
  for (int i = 0; i < 60; i++) {
    SCOPED_TRACE(base::StringPrintf("Frame with overlay %d", i));
    auto pass = CreateRenderPass();
    // Use an opaque pass to check that the overlay processor makes it
    // transparent in the case of overlays.
    pass->has_transparent_background = false;

    CreateFullscreenCandidateYUVTextureQuad(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());

    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));

    OverlayCandidateList dc_layer_list;
    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    SurfaceDamageRectList surface_damage_rect_list;
    damage_rect_ = gfx::Rect(1, 1, 10, 10);

    // Full damage on the first frame.
    const gfx::Rect expected_damage =
        (i == 0) ? pass_list.back()->output_rect : gfx::Rect();

    ProcessForOverlays(&pass_list, render_pass_filters,
                       render_pass_backdrop_filters, SurfaceDamageRectList(),
                       &dc_layer_list);

    EXPECT_TRUE(pass_list.back()->needs_synchronous_dcomp_commit);
    EXPECT_TRUE(pass_list.back()->has_transparent_background);
    if (GetParam() == SurfaceTestMode::RootSurface) {
      EXPECT_TRUE(output_surface_plane_);
      EXPECT_EQ(output_surface_plane_->enable_blending,
                pass_list.back()->has_transparent_background);
    } else {
      // Delegated compositing removes the output surface plane.
    }

    EXPECT_EQ(1U, dc_layer_list.size());
    EXPECT_EQ(1, dc_layer_list.back().plane_z_order);
    EXPECT_EQ(damage_rect_, expected_damage);

    Mock::VerifyAndClearExpectations(output_surface_.get());
  }

  // Draw 65 frames without overlays.
  for (int i = 0; i < 65; i++) {
    SCOPED_TRACE(base::StringPrintf("Frame without overlay %d", i));
    auto pass = CreateRenderPass();
    pass->has_transparent_background = false;

    damage_rect_ = gfx::Rect(1, 1, 10, 10);
    auto* quad = pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
    quad->SetNew(pass->CreateAndAppendSharedQuadState(), damage_rect_,
                 damage_rect_, SkColors::kRed, false);

    OverlayCandidateList dc_layer_list;
    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;

    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list;

    damage_rect_ = gfx::Rect(1, 1, 10, 10);

    // There will be full damage and needs_synchronous_dcomp_commit will be
    // false after 60 consecutive frames with no overlays. The first frame
    // without overlays will also have full damage.
    const gfx::Rect expected_damage = (i == 0 || (i + 1) == 60)
                                          ? pass_list.back()->output_rect
                                          : damage_rect_;

    const bool in_dc_layer_hysteresis = i + 1 < 60;

    ProcessForOverlays(&pass_list, render_pass_filters,
                       render_pass_backdrop_filters, SurfaceDamageRectList(),
                       &dc_layer_list);

    EXPECT_EQ(pass_list.back()->needs_synchronous_dcomp_commit,
              in_dc_layer_hysteresis);
    EXPECT_EQ(pass_list.back()->has_transparent_background,
              in_dc_layer_hysteresis);
    if (GetParam() == SurfaceTestMode::RootSurface) {
      EXPECT_TRUE(output_surface_plane_);
      EXPECT_EQ(output_surface_plane_->enable_blending,
                pass_list.back()->has_transparent_background);
    } else {
      // Delegated compositing removes the output surface plane.
    }

    EXPECT_EQ(0u, dc_layer_list.size());
    EXPECT_EQ(damage_rect_, expected_damage);

    Mock::VerifyAndClearExpectations(output_surface_.get());
  }
}

// Tests that Delegated Ink in the frame correctly sets
// needs_synchronous_dcomp_commit on the render pass.
TEST_P(OverlayProcessorWinSurfacePlaneTest, FrameHasDelegatedInk) {
  overlay_processor_->SetUsingDCLayersForTesting(kDefaultRootPassId, false);
  // Test that needs_synchronous_dcomp_commit on the render pass gets set to
  // false as default.
  {
    auto pass = CreateRenderPass();
    OverlayCandidateList dc_layer_list;
    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    damage_rect_ = gfx::Rect(1, 1, 10, 10);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {gfx::Rect(1, 1, 10, 10)};

    EXPECT_FALSE(pass_list[0]->needs_synchronous_dcomp_commit);
    ProcessForOverlays(&pass_list, render_pass_filters,
                       render_pass_backdrop_filters, SurfaceDamageRectList(),
                       &dc_layer_list);
    EXPECT_FALSE(pass_list[0]->needs_synchronous_dcomp_commit);
  }

  // Test that needs_synchronous_dcomp_commit gets set to true when the frame
  // has delegated ink.
  overlay_processor_->SetFrameHasDelegatedInk();
  auto pass = CreateRenderPass();
  OverlayCandidateList dc_layer_list;
  OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
  OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
  damage_rect_ = gfx::Rect(1, 1, 10, 10);
  AggregatedRenderPassList pass_list;
  pass_list.push_back(std::move(pass));
  SurfaceDamageRectList surface_damage_rect_list = {gfx::Rect(1, 1, 10, 10)};

  EXPECT_FALSE(pass_list[0]->needs_synchronous_dcomp_commit);
  ProcessForOverlays(&pass_list, render_pass_filters,
                     render_pass_backdrop_filters, SurfaceDamageRectList(),
                     &dc_layer_list);
  // Make sure |frame_has_delegated_ink_| has been set to false.
  EXPECT_FALSE(overlay_processor_->frame_has_delegated_ink_for_testing());
  EXPECT_TRUE(pass_list[0]->needs_synchronous_dcomp_commit);
}

// Ensure needs_synchronous_dcomp_commit lasts for 60 frames after
// |SetFrameHasDelegatedInk| has been called (once). Based on
// kNumberOfFramesBeforeDisablingDCLayers in
// components/viz/service/display/overlay_processor_win.cc.
TEST_P(OverlayProcessorWinSurfacePlaneTest, DelegatedInkSurfaceHysteresis) {
  overlay_processor_->SetUsingDCLayersForTesting(kDefaultRootPassId, false);

  overlay_processor_->SetFrameHasDelegatedInk();
  for (int frame = 1; frame <= 61; frame++) {
    auto pass = CreateRenderPass();
    OverlayCandidateList dc_layer_list;
    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;
    damage_rect_ = gfx::Rect(1, 1, 10, 10);
    AggregatedRenderPassList pass_list;
    pass_list.push_back(std::move(pass));
    SurfaceDamageRectList surface_damage_rect_list = {gfx::Rect(1, 1, 10, 10)};

    EXPECT_FALSE(pass_list[0]->needs_synchronous_dcomp_commit);
    ProcessForOverlays(&pass_list, render_pass_filters,
                       render_pass_backdrop_filters, SurfaceDamageRectList(),
                       &dc_layer_list);
    // Make sure |frame_has_delegated_ink_| has been set to false.
    EXPECT_FALSE(overlay_processor_->frame_has_delegated_ink_for_testing());
    if (frame <= 60) {
      EXPECT_TRUE(pass_list[0]->needs_synchronous_dcomp_commit);
    } else {
      EXPECT_FALSE(pass_list[0]->needs_synchronous_dcomp_commit);
    }
  }
}

INSTANTIATE_TEST_SUITE_P(
    ,
    OverlayProcessorWinSurfacePlaneTest,
    testing::Values(SurfaceTestMode::RootSurface,
                    SurfaceTestMode::SimulatePartiallyDelegated),
    &OverlayProcessorWinSurfacePlaneTest::GetParamName);

class OverlayProcessorWinDelegatedCompositingTest
    : public OverlayProcessorWinTest {
 protected:
  OverlayProcessorWinDelegatedCompositingTest() {
    feature_list_.InitAndEnableFeatureWithParameters(
        features::kDelegatedCompositing, {{"mode", "full"}});
  }

  class DelegationResult {
   public:
    DelegationResult(OverlayCandidateList candidates,
                     bool delegation_succeeded,
                     gfx::Rect original_root_surface_damage,
                     gfx::Rect root_surface_damage)
        : candidates_(std::move(candidates)),
          delegation_succeeded_(delegation_succeeded),
          original_root_surface_damage_(original_root_surface_damage),
          root_surface_damage_(root_surface_damage) {}

    void ExpectDelegationSuccess() const {
      EXPECT_TRUE(delegation_succeeded_);
      EXPECT_EQ(gfx::Rect(), root_surface_damage_);
    }

    void ExpectDelegationFailure() const {
      EXPECT_FALSE(delegation_succeeded_);
      EXPECT_EQ(original_root_surface_damage_, root_surface_damage_);
    }

    const OverlayCandidateList& candidates() const { return candidates_; }

   private:
    OverlayCandidateList candidates_;
    bool delegation_succeeded_ = false;
    gfx::Rect original_root_surface_damage_;
    gfx::Rect root_surface_damage_;
  };

  DelegationResult TryProcessForDelegatedOverlays(
      AggregatedRenderPassList& pass_list,
      SurfaceDamageRectList surface_damage_rect_list = {}) {
    if (!output_surface_plane_) {
      // Reset the output surface plane in case we're calling
      // |TryProcessForDelegatedOverlays| multiple times.
      output_surface_plane_ =
          OverlayProcessorInterface::OutputSurfaceOverlayPlane();
    }

    const gfx::Rect original_root_surface_damage =
        pass_list.back()->damage_rect;

    OverlayCandidateList candidates;
    OverlayProcessorInterface::FilterOperationsMap render_pass_filters;
    OverlayProcessorInterface::FilterOperationsMap render_pass_backdrop_filters;

    for (const auto& pass : pass_list) {
      if (!pass->filters.IsEmpty()) {
        render_pass_filters[pass->id] = &pass->filters;
      }
      if (!pass->backdrop_filters.IsEmpty()) {
        render_pass_backdrop_filters[pass->id] = &pass->backdrop_filters;
      }
    }

    damage_rect_ = original_root_surface_damage;
    overlay_processor_->ProcessForOverlays(
        resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
        render_pass_filters, render_pass_backdrop_filters,
        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
        &candidates, &damage_rect_, &content_bounds_);

    overlay_processor_->AdjustOutputSurfaceOverlay(&output_surface_plane_);
    const bool delegation_succeeded = !output_surface_plane_.has_value();

    return DelegationResult(candidates, delegation_succeeded,
                            original_root_surface_damage, damage_rect_);
  }

  testing::Matcher<const OverlayCandidateList&>
  WhenCandidatesAreSortedElementsAre(
      std::vector<testing::Matcher<const OverlayCandidate&>> element_matchers) {
    return testing::AllOf(
        PlaneZOrdersAreUnique(),
        testing::WhenSortedBy(test::PlaneZOrderAscendingComparator(),
                              testing::ElementsAreArray(element_matchers)));
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Check that we can do delegated compositing of a single quad.
TEST_F(OverlayProcessorWinDelegatedCompositingTest, SingleQuad) {
  AggregatedRenderPassList pass_list;

  auto pass = CreateRenderPass();
  CreateSolidColorQuadAt(pass->CreateAndAppendSharedQuadState(), SkColors::kRed,
                         pass.get(), gfx::Rect(0, 0, 50, 50));
  pass_list.push_back(std::move(pass));

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();
  EXPECT_THAT(result.candidates(),
              WhenCandidatesAreSortedElementsAre({
                  test::IsSolidColorOverlay(SkColors::kRed),
              }));
}

// Check that we don't try delegated compositing when there are too many quads.
TEST_F(OverlayProcessorWinDelegatedCompositingTest, TooManyQuads) {
  AggregatedRenderPassList pass_list;

  auto pass = CreateRenderPass();
  auto* sqs = pass->CreateAndAppendSharedQuadState();
  for (int i = 0; i < 2049; i++) {
    CreateSolidColorQuadAt(sqs, SkColors::kRed, pass.get(),
                           gfx::Rect(i, 0, 50, 50));
  }
  pass_list.push_back(std::move(pass));

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationFailure();
  EXPECT_THAT(result.candidates(), testing::IsEmpty());
}

// Check that we don't try delegated compositing when there are too many complex
// quads. This limit is lower since they have a larger performance hit in DWM.
TEST_F(OverlayProcessorWinDelegatedCompositingTest, TooManyComplexQuads) {
  AggregatedRenderPassList pass_list;

  auto pass = CreateRenderPass();
  auto* sqs = pass->CreateAndAppendSharedQuadState();
  sqs->mask_filter_info =
      gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(0, 0, 50, 50), 50));
  for (int i = 0; i < 257; i++) {
    CreateSolidColorQuadAt(sqs, SkColors::kRed, pass.get(),
                           gfx::Rect(i, 0, 50, 50));
  }
  pass_list.push_back(std::move(pass));

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationFailure();
  EXPECT_THAT(result.candidates(), testing::IsEmpty());
}

// Check that, when delegated compositing fails, we still successfully promote
// videos to overlay.
TEST_F(OverlayProcessorWinDelegatedCompositingTest,
       DelegationFailStillPromotesVideos) {
  AggregatedRenderPassList pass_list;

  auto pass = CreateRenderPass();

  // Add a video quad we expect to go to overlay.
  auto* video_quad = CreateFullscreenCandidateYUVTextureQuad(
      resource_provider_.get(), child_resource_provider_.get(),
      child_provider_.get(), pass->shared_quad_state_list.back(), pass.get());
  ResourceId video_resource_id = video_quad->resource_id();

  {
    // A RPDQ with a backdrop filter occluding another quad will cause delegated
    // compositing to fail.
    auto child_pass = CreateRenderPass(AggregatedRenderPassId(2));
    child_pass->backdrop_filters.Append(
        cc::FilterOperation::CreateBlurFilter(1.f));
    CreateRenderPassDrawQuadAt(pass.get(), pass->shared_quad_state_list.back(),
                               gfx::Rect(0, 0, 50, 50), child_pass->id);
    pass_list.push_back(std::move(child_pass));

    CreateSolidColorQuadAt(pass->shared_quad_state_list.back(),
                           SkColors::kGreen, pass.get(),
                           gfx::Rect(0, 0, 50, 50));
  }

  pass_list.push_back(std::move(pass));

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationFailure();
  EXPECT_THAT(result.candidates(),
              WhenCandidatesAreSortedElementsAre({
                  test::OverlayHasResource(video_resource_id),
              }))
      << "The overlay processor fall back to using DCLayerOverlayProcessor on "
         "the root surface.";
}

// Test that when |OverlayCandidateFactory| returns |kFailVisible| we just skip
// the quad instead of failing delegation.
TEST_F(OverlayProcessorWinDelegatedCompositingTest, SkipNonVisibleOverlays) {
  AggregatedRenderPassList pass_list;

  auto pass = CreateRenderPass();
  CreateSolidColorQuadAt(pass->CreateAndAppendSharedQuadState(), SkColors::kRed,
                         pass.get(), gfx::Rect(0, 0, 0, 0));
  pass_list.push_back(std::move(pass));

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();
  EXPECT_THAT(result.candidates(), testing::IsEmpty());
}

// Check that delegated compositing fails when there is a color conversion pass.
TEST_F(OverlayProcessorWinDelegatedCompositingTest, HdrNotSupported) {
  AggregatedRenderPassList pass_list;

  pass_list.push_back(CreateRenderPass(AggregatedRenderPassId{2}));

  auto pass = CreateRenderPass();
  pass->is_color_conversion_pass = true;
  pass_list.push_back(std::move(pass));

  damage_rect_ = pass_list.back()->damage_rect;

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationFailure();
}

// Check that delegated compositing fails when the root is being captured.
TEST_F(OverlayProcessorWinDelegatedCompositingTest, CaptureNotSupported) {
  AggregatedRenderPassList pass_list;

  auto pass = CreateRenderPass();
  pass->video_capture_enabled = true;
  pass_list.push_back(std::move(pass));

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationFailure();
}

// Check that delegated compositing fails when there is a backdrop filter that
// would need to read another overlay candidate.
TEST_F(OverlayProcessorWinDelegatedCompositingTest,
       OccludedByFilteredQuadNotSupported) {
  AggregatedRenderPassList pass_list;

  AggregatedRenderPassId child_pass_id{2};

  // Create a pass with a backdrop filter.
  {
    auto child_pass = CreateRenderPass(child_pass_id);
    child_pass->backdrop_filters = cc::FilterOperations({
        cc::FilterOperation::CreateGrayscaleFilter(1.0f),
    });
    pass_list.push_back(std::move(child_pass));
  }

  {
    auto pass = CreateRenderPass();

    const gfx::Rect rect(0, 0, 50, 50);

    CreateRenderPassDrawQuadAt(pass.get(),
                               pass->CreateAndAppendSharedQuadState(), rect,
                               child_pass_id);

    // Create a quad that will be occluded by the backdrop-filtered RPDQ above.
    CreateSolidColorQuadAt(pass->CreateAndAppendSharedQuadState(),
                           SkColors::kRed, pass.get(), rect);

    pass_list.push_back(std::move(pass));
  }

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationFailure();
}

// Check that the various ways we can set |will_backing_be_read_by_viz| work as
// expected.
TEST_F(OverlayProcessorWinDelegatedCompositingTest, BackingWillBeReadInViz) {
  AggregatedRenderPassList pass_list;

  AggregatedRenderPassId::Generator id_generator;
  base::flat_map<AggregatedRenderPassId, const char*> pass_names;
  base::flat_set<AggregatedRenderPassId> passes_to_embed_in_root;

  auto CreateNamedPass =
      [&](const char* name, bool embed_in_root,
          base::OnceCallback<void(AggregatedRenderPass*)> update_pass) {
        AggregatedRenderPassId pass_id = id_generator.GenerateNextId();
        pass_names.insert({pass_id, name});
        if (embed_in_root) {
          passes_to_embed_in_root.insert(pass_id);
        }

        std::unique_ptr<AggregatedRenderPass> pass = CreateRenderPass(pass_id);
        std::move(update_pass).Run(pass.get());
        pass_list.push_back(std::move(pass));

        return pass_id;
      };

  CreateNamedPass("video capture enabled", true,
                  base::BindOnce([](AggregatedRenderPass* pass) {
                    pass->video_capture_enabled = true;
                  }));

  CreateNamedPass("filters", true,
                  base::BindOnce([](AggregatedRenderPass* pass) {
                    pass->filters = cc::FilterOperations({
                        cc::FilterOperation::CreateGrayscaleFilter(1.0f),
                    });
                  }));

  CreateNamedPass("generate mipmaps", true,
                  base::BindOnce([](AggregatedRenderPass* pass) {
                    pass->generate_mipmap = true;
                  }));

  auto non_overlay_embeddee_id = CreateNamedPass(
      "normal pass with non-overlay embedder", true, base::DoNothing());
  CreateNamedPass("non-overlay embedder", false,
                  base::BindLambdaForTesting([&](AggregatedRenderPass* pass) {
                    CreateRenderPassDrawQuadAt(
                        pass, pass->CreateAndAppendSharedQuadState(),
                        pass->output_rect, non_overlay_embeddee_id);
                  }));

  auto complex_mask_embeddee_id = CreateNamedPass(
      "normal pass with gradient mask embedder", true, base::DoNothing());
  CreateNamedPass("gradient mask embedder", false,
                  base::BindLambdaForTesting([&](AggregatedRenderPass* pass) {
                    auto* sqs = pass->CreateAndAppendSharedQuadState();
                    CreateRenderPassDrawQuadAt(pass, sqs, pass->output_rect,
                                               complex_mask_embeddee_id);

                    // We can delegated rounded corners fine, so set a complex
                    // mask filter that we will handle with an intermediate
                    // surface in |SkiaRenderer|.
                    gfx::LinearGradient gradient;
                    gradient.AddStep(0.f, 0);
                    gradient.AddStep(1.f, 0xff);
                    sqs->mask_filter_info = gfx::MaskFilterInfo(
                        gfx::RRectF(gfx::RectF(pass->output_rect)), gradient);
                  }));

  CreateNamedPass("root pass", false,
                  base::BindLambdaForTesting([&](AggregatedRenderPass* pass) {
                    for (auto id : passes_to_embed_in_root) {
                      CreateRenderPassDrawQuadAt(
                          pass, pass->CreateAndAppendSharedQuadState(),
                          pass->output_rect, id);
                    }
                  }));

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();

  // In this test, we expect every pass except the root pass to be read by viz.
  // Passes that are not composited as overlays are assumed to be read by viz
  // e.g. for copy output requests, etc.
  for (size_t i = 0u; i < pass_list.size(); i++) {
    SCOPED_TRACE(base::StringPrintf("pass_list[%zu]: %s", i,
                                    pass_names[pass_list[i]->id]));
    if (pass_list[i] == pass_list.back()) {
      EXPECT_FALSE(pass_list[i]->will_backing_be_read_by_viz);
    } else {
      EXPECT_TRUE(pass_list[i]->will_backing_be_read_by_viz);
    }
  }
}

// Tests that check that overlay promotion is supported from non-root render
// passes in the partially delegated case.
class OverlayProcessorWinPartiallyDelegatedCompositingTest
    : public OverlayProcessorWinDelegatedCompositingTest {
 protected:
  OverlayProcessorWinPartiallyDelegatedCompositingTest() {
    feature_list_.InitAndEnableFeatureWithParameters(
        features::kDelegatedCompositing, {{"mode", "limit_to_ui"}});
  }

  TextureDrawQuad* CreateOverlayQuadWithSurfaceDamageAt(
      AggregatedRenderPass* pass,
      SurfaceDamageRectList& surface_damage_rect_list,
      const gfx::Rect& rect) {
    SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
    auto* quad = CreateTextureQuadAt(resource_provider_.get(),
                                     child_resource_provider_.get(),
                                     child_provider_.get(), sqs, pass, rect,
                                     /*is_overlay_candidate=*/true);

    pass->damage_rect.Union(
        sqs->quad_to_target_transform.MapRect(quad->visible_rect));

    gfx::Transform quad_to_root_transform(sqs->quad_to_target_transform);
    quad_to_root_transform.PostConcat(pass->transform_to_root_target);

    sqs->overlay_damage_index = surface_damage_rect_list.size();
    surface_damage_rect_list.push_back(
        quad_to_root_transform.MapRect(quad->visible_rect));

    return quad;
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

// Check that an overlay candidate can be promoted from a non-root pass
// representing a surface.
TEST_F(OverlayProcessorWinPartiallyDelegatedCompositingTest,
       CandidatePromotedFromNonRootSurface) {
  AggregatedRenderPassList pass_list;

  const AggregatedRenderPassId child_pass_id{2};
  ResourceId child_pass_texture_id;

  // Create a pass with just an overlay quad.
  {
    auto child_pass = CreateRenderPass(child_pass_id);
    child_pass->is_from_surface_root_pass = true;
    auto* texture_quad = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), child_pass->CreateAndAppendSharedQuadState(),
        child_pass.get(), gfx::Rect(0, 0, 50, 50),
        /*is_overlay_candidate=*/true);
    child_pass_texture_id = texture_quad->resource_id();
    pass_list.push_back(std::move(child_pass));
  }

  {
    auto pass = CreateRenderPass();
    CreateRenderPassDrawQuadAt(pass.get(),
                               pass->CreateAndAppendSharedQuadState(),
                               gfx::Rect(0, 0, 50, 50), child_pass_id);
    pass_list.push_back(std::move(pass));
  }

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();

  // We expect both the RPDQ and the inner video to be promoted.
  EXPECT_THAT(result.candidates(),
              WhenCandidatesAreSortedElementsAre({
                  test::IsRenderPassOverlay(child_pass_id),
                  test::OverlayHasResource(child_pass_texture_id),
              }));
}

// Check that an overlay candidate can be promoted from a non-root pass
// representing a surface, but will be placed behind the output surface plane if
// it is occluded by something in the surface.
TEST_F(OverlayProcessorWinPartiallyDelegatedCompositingTest,
       CandidatePromotedFromNonRootSurfaceAsUnderlay) {
  AggregatedRenderPassList pass_list;

  const AggregatedRenderPassId child_pass_id{2};
  ResourceId child_pass_texture_id;

  // Create a pass with an overlay quad that is occluded by some other quad.
  // This forces the overlay candidate to appear as an underlay to the surface.
  {
    auto child_pass = CreateRenderPass(child_pass_id);
    child_pass->is_from_surface_root_pass = true;
    CreateSolidColorQuadAt(child_pass->CreateAndAppendSharedQuadState(),
                           SkColors::kRed, child_pass.get(),
                           gfx::Rect(5, 5, 10, 10));
    auto* texture_quad = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), child_pass->CreateAndAppendSharedQuadState(),
        child_pass.get(), gfx::Rect(0, 0, 50, 50),
        /*is_overlay_candidate=*/true);
    child_pass_texture_id = texture_quad->resource_id();
    pass_list.push_back(std::move(child_pass));
  }

  {
    auto pass = CreateRenderPass();
    CreateRenderPassDrawQuadAt(pass.get(),
                               pass->CreateAndAppendSharedQuadState(),
                               gfx::Rect(0, 0, 50, 50), child_pass_id);
    CreateSolidColorQuadAt(pass->CreateAndAppendSharedQuadState(),
                           SkColors::kBlue, pass.get(), pass->output_rect);
    pass_list.push_back(std::move(pass));
  }

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();

  // We expect both the RPDQ and the inner video to be promoted and in front of
  // the solid color background in the root pass.
  EXPECT_THAT(result.candidates(),
              WhenCandidatesAreSortedElementsAre({
                  test::IsSolidColorOverlay(SkColors::kBlue),
                  test::OverlayHasResource(child_pass_texture_id),
                  test::IsRenderPassOverlay(child_pass_id),
              }));
}

TEST_F(OverlayProcessorWinPartiallyDelegatedCompositingTest,
       CandidatesPromotedFromMultipleSurfaces) {
  AggregatedRenderPassList pass_list;

  const AggregatedRenderPassId child_pass_id{2};
  ResourceId child_pass_video_id;
  {
    auto child_pass = CreateRenderPass(child_pass_id);
    child_pass->is_from_surface_root_pass = true;
    auto* texture_quad = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), child_pass->CreateAndAppendSharedQuadState(),
        child_pass.get(), gfx::Rect(0, 0, 50, 50),
        /*is_overlay_candidate=*/true);
    child_pass_video_id = texture_quad->resource_id();
    pass_list.push_back(std::move(child_pass));
  }

  const AggregatedRenderPassId other_child_pass_id{3};
  ResourceId other_child_pass_video_id;
  ResourceId other_child_pass_video_2_id;
  {
    auto other_child_pass = CreateRenderPass(other_child_pass_id);
    other_child_pass->is_from_surface_root_pass = true;
    // Make this first quad partially occlude the next.
    auto* texture_quad = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(),
        other_child_pass->CreateAndAppendSharedQuadState(),
        other_child_pass.get(), gfx::Rect(10, 0, 50, 50),
        /*is_overlay_candidate=*/true);
    other_child_pass_video_id = texture_quad->resource_id();
    auto* texture_quad_2 = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(),
        other_child_pass->CreateAndAppendSharedQuadState(),
        other_child_pass.get(), gfx::Rect(0, 0, 50, 50),
        /*is_overlay_candidate=*/true);
    other_child_pass_video_2_id = texture_quad_2->resource_id();
    pass_list.push_back(std::move(other_child_pass));
  }

  {
    auto pass = CreateRenderPass();
    CreateRenderPassDrawQuadAt(pass.get(),
                               pass->CreateAndAppendSharedQuadState(),
                               gfx::Rect(0, 0, 50, 50), child_pass_id);
    CreateSolidColorQuadAt(pass->CreateAndAppendSharedQuadState(),
                           SkColors::kBlue, pass.get(), pass->output_rect);
    CreateRenderPassDrawQuadAt(pass.get(),
                               pass->CreateAndAppendSharedQuadState(),
                               gfx::Rect(50, 0, 50, 50), other_child_pass_id);
    pass_list.push_back(std::move(pass));
  }

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();

  // We expect both the RPDQ and the inner video(s) to be promoted for both
  // RPDQs.
  EXPECT_THAT(result.candidates(),
              WhenCandidatesAreSortedElementsAre({
                  test::OverlayHasResource(other_child_pass_video_2_id),
                  test::IsRenderPassOverlay(other_child_pass_id),
                  test::OverlayHasResource(other_child_pass_video_id),
                  test::IsSolidColorOverlay(SkColors::kBlue),
                  test::IsRenderPassOverlay(child_pass_id),
                  test::OverlayHasResource(child_pass_video_id),
              }));
}

TEST_F(OverlayProcessorWinPartiallyDelegatedCompositingTest,
       CandidatePromotionRespectsAllowedYuvOverlayCount) {
  AggregatedRenderPassList pass_list;

  const AggregatedRenderPassId child_pass_id{2};
  {
    auto child_pass = CreateRenderPass(child_pass_id);
    child_pass->is_from_surface_root_pass = true;
    auto* texture_quad = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), child_pass->CreateAndAppendSharedQuadState(),
        child_pass.get(), gfx::Rect(0, 0, 50, 50),
        /*is_overlay_candidate=*/true);
    texture_quad->is_video_frame = true;
    pass_list.push_back(std::move(child_pass));
  }

  const AggregatedRenderPassId other_child_pass_id{3};
  {
    auto other_child_pass = CreateRenderPass(other_child_pass_id);
    other_child_pass->is_from_surface_root_pass = true;
    auto* texture_quad = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(),
        other_child_pass->CreateAndAppendSharedQuadState(),
        other_child_pass.get(), gfx::Rect(0, 0, 50, 50));
    texture_quad->is_video_frame = true;
    pass_list.push_back(std::move(other_child_pass));
  }

  {
    auto pass = CreateRenderPass();
    CreateRenderPassDrawQuadAt(pass.get(),
                               pass->CreateAndAppendSharedQuadState(),
                               gfx::Rect(0, 0, 50, 50), child_pass_id);
    CreateRenderPassDrawQuadAt(pass.get(),
                               pass->CreateAndAppendSharedQuadState(),
                               gfx::Rect(50, 0, 50, 50), other_child_pass_id);
    pass_list.push_back(std::move(pass));
  }

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();

  // We expect both the RPDQs to be promoted, but neither of the videos.
  EXPECT_THAT(result.candidates(),
              WhenCandidatesAreSortedElementsAre({
                  test::IsRenderPassOverlay(other_child_pass_id),
                  test::IsRenderPassOverlay(child_pass_id),
              }));
}

TEST_F(OverlayProcessorWinPartiallyDelegatedCompositingTest,
       CandidatesInheritSurfaceEmbeddersBounds) {
  AggregatedRenderPassList pass_list;

  const AggregatedRenderPassId child_pass_id{2};
  ResourceId child_pass_texture_id;

  {
    auto child_pass = CreateRenderPass(child_pass_id);
    child_pass->is_from_surface_root_pass = true;
    auto* texture_quad = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), child_pass->CreateAndAppendSharedQuadState(),
        child_pass.get(), gfx::Rect(0, 0, 50, 50),
        /*is_overlay_candidate=*/true);
    child_pass_texture_id = texture_quad->resource_id();
    pass_list.push_back(std::move(child_pass));
  }

  const gfx::Rect rpdq_bounds = gfx::Rect(0, 0, 20, 30);
  gfx::Rect expected_overlay_clip;

  {
    auto pass = CreateRenderPass();
    SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
    sqs->quad_to_target_transform.Translate(1, 2);
    CreateRenderPassDrawQuadAt(pass.get(), sqs, rpdq_bounds, child_pass_id);

    expected_overlay_clip = sqs->quad_to_target_transform.MapRect(rpdq_bounds);

    pass_list.push_back(std::move(pass));
  }

  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();

  EXPECT_THAT(
      result.candidates(),
      WhenCandidatesAreSortedElementsAre({
          test::IsRenderPassOverlay(child_pass_id),
          testing::AllOf(test::OverlayHasResource(child_pass_texture_id),
                         test::OverlayHasClip(expected_overlay_clip)),
      }));
}

TEST_F(OverlayProcessorWinPartiallyDelegatedCompositingTest,
       CandidatesInheritSurfaceEmbeddersClip) {
  AggregatedRenderPassList pass_list;

  const AggregatedRenderPassId child_pass_id{2};
  ResourceId child_pass_texture_id;

  const gfx::Transform child_to_root = gfx::Transform::MakeTranslation(1, 2);

  {
    auto child_pass = CreateRenderPass(child_pass_id);
    child_pass->is_from_surface_root_pass = true;
    child_pass->transform_to_root_target = child_to_root;
    auto* texture_quad = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), child_pass->CreateAndAppendSharedQuadState(),
        child_pass.get(), gfx::Rect(0, 0, 50, 50),
        /*is_overlay_candidate=*/true);
    child_pass_texture_id = texture_quad->resource_id();
    pass_list.push_back(std::move(child_pass));
  }

  const gfx::Rect rpdq_clip_rect = gfx::Rect(10, 20, 5, 15);
  const gfx::Rect rpdq_bounds = gfx::Rect(0, 0, 20, 30);
  gfx::Rect expected_overlay_clip;

  {
    auto pass = CreateRenderPass();
    SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
    sqs->quad_to_target_transform = child_to_root;
    sqs->clip_rect = rpdq_clip_rect;
    CreateRenderPassDrawQuadAt(pass.get(), sqs, rpdq_bounds, child_pass_id);

    expected_overlay_clip = sqs->quad_to_target_transform.MapRect(rpdq_bounds);
    expected_overlay_clip.Intersect(rpdq_clip_rect);

    pass_list.push_back(std::move(pass));
  }
  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();

  EXPECT_THAT(
      result.candidates(),
      WhenCandidatesAreSortedElementsAre({
          testing::AllOf(test::IsRenderPassOverlay(child_pass_id),
                         test::OverlayHasClip(rpdq_clip_rect)),
          testing::AllOf(test::OverlayHasResource(child_pass_texture_id),
                         test::OverlayHasClip(expected_overlay_clip)),
      }));
}

TEST_F(OverlayProcessorWinPartiallyDelegatedCompositingTest,
       CandidatesInheritSurfaceEmbeddersClipAndIntersect) {
  AggregatedRenderPassList pass_list;

  const AggregatedRenderPassId child_pass_id{2};
  ResourceId child_pass_texture_id;

  const gfx::Rect inner_quad_clip = gfx::Rect(10, 15, 1, 2);
  const gfx::Transform child_to_root = gfx::Transform::MakeTranslation(1, 2);

  {
    auto child_pass = CreateRenderPass(child_pass_id);
    child_pass->is_from_surface_root_pass = true;
    child_pass->transform_to_root_target = child_to_root;
    SharedQuadState* sqs = child_pass->CreateAndAppendSharedQuadState();
    sqs->clip_rect = inner_quad_clip;
    auto* texture_quad = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), sqs, child_pass.get(), gfx::Rect(0, 0, 50, 50),
        /*is_overlay_candidate=*/true);
    child_pass_texture_id = texture_quad->resource_id();
    pass_list.push_back(std::move(child_pass));
  }

  const gfx::Rect rpdq_bounds = gfx::Rect(0, 0, 20, 30);
  gfx::Rect expected_overlay_clip;

  {
    auto pass = CreateRenderPass();
    SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
    sqs->quad_to_target_transform = child_to_root;
    CreateRenderPassDrawQuadAt(pass.get(), sqs, rpdq_bounds, child_pass_id);

    expected_overlay_clip =
        sqs->quad_to_target_transform.MapRect(inner_quad_clip);
    expected_overlay_clip.Intersect(rpdq_bounds);

    pass_list.push_back(std::move(pass));
  }
  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();

  EXPECT_THAT(
      result.candidates(),
      WhenCandidatesAreSortedElementsAre({
          test::IsRenderPassOverlay(child_pass_id),
          testing::AllOf(test::OverlayHasResource(child_pass_texture_id),
                         test::OverlayHasClip(expected_overlay_clip)),
      }));
}

TEST_F(OverlayProcessorWinPartiallyDelegatedCompositingTest,
       CandidatesInheritSurfaceEmbeddersClipAndRoundedCorners) {
  AggregatedRenderPassList pass_list;

  const AggregatedRenderPassId child_pass_id{2};
  ResourceId child_pass_texture_id;

  {
    auto child_pass = CreateRenderPass(child_pass_id);
    child_pass->is_from_surface_root_pass = true;
    SharedQuadState* sqs = child_pass->CreateAndAppendSharedQuadState();
    // We expect this rounded corner to be painted into the |child_pass|
    // surface.
    sqs->mask_filter_info =
        gfx::MaskFilterInfo(gfx::RRectF(gfx::RectF(10, 10), 1));
    auto* texture_quad = CreateTextureQuadAt(
        resource_provider_.get(), child_resource_provider_.get(),
        child_provider_.get(), sqs, child_pass.get(), gfx::Rect(0, 0, 50, 50),
        /*is_overlay_candidate=*/true);
    child_pass_texture_id = texture_quad->resource_id();
    pass_list.push_back(std::move(child_pass));
  }

  const gfx::RRectF rpdq_rounded_corners = gfx::RRectF(gfx::RectF(10, 10), 1);

  {
    auto pass = CreateRenderPass();
    SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
    sqs->mask_filter_info = gfx::MaskFilterInfo(rpdq_rounded_corners);
    CreateRenderPassDrawQuadAt(pass.get(), sqs, gfx::Rect(0, 0, 50, 50),
                               child_pass_id);

    pass_list.push_back(std::move(pass));
  }
  auto result = TryProcessForDelegatedOverlays(pass_list);
  result.ExpectDelegationSuccess();

  // Our texture quad is behind the surface, due to having its own rounded
  // corners.
  EXPECT_THAT(
      result.candidates(),
      WhenCandidatesAreSortedElementsAre({
          testing::AllOf(test::OverlayHasResource(child_pass_texture_id),
                         test::OverlayHasClip(gfx::Rect(0, 0, 50, 50)),
                         test::OverlayHasRoundedCorners(rpdq_rounded_corners)),
          testing::AllOf(test::IsRenderPassOverlay(child_pass_id),
                         test::OverlayHasRoundedCorners(rpdq_rounded_corners)),
      }));
}

TEST_F(OverlayProcessorWinPartiallyDelegatedCompositingTest,
       DamageRemovedFromSurface) {
  const AggregatedRenderPassId child_pass_id{2};
  const gfx::Rect texture_quad_rect = gfx::Rect(5, 5, 50, 50);

  for (int frame = 0; frame < 3; frame++) {
    SCOPED_TRACE(base::StringPrintf("Frame %d", frame));

    const bool is_overlay_candidate_with_damage = frame < 2;

    AggregatedRenderPassList pass_list;
    SurfaceDamageRectList surface_damage_rect_list;

    const gfx::Rect rpdq_quad = gfx::Rect(10, 10, 100, 100);

    auto child_pass = CreateRenderPass(child_pass_id);
    child_pass->transform_to_root_target =
        gfx::Transform::MakeTranslation(rpdq_quad.OffsetFromOrigin());
    child_pass->is_from_surface_root_pass = true;
    child_pass->damage_rect = gfx::Rect();
    auto* texture_quad =
        is_overlay_candidate_with_damage
            ? CreateOverlayQuadWithSurfaceDamageAt(
                  child_pass.get(), surface_damage_rect_list, texture_quad_rect)
            : CreateTextureQuadAt(resource_provider_.get(),
                                  child_resource_provider_.get(),
                                  child_provider_.get(),
                                  child_pass->CreateAndAppendSharedQuadState(),
                                  child_pass.get(), texture_quad_rect,
                                  /*is_overlay_candidate=*/false);
    ResourceId child_pass_texture_id = texture_quad->resource_id();
    pass_list.push_back(std::move(child_pass));

    auto pass = CreateRenderPass();
    CreateRenderPassDrawQuadAt(pass.get(),
                               pass->CreateAndAppendSharedQuadState(),
                               rpdq_quad, child_pass_id);
    pass_list.push_back(std::move(pass));

    switch (frame) {
      case 0:
      case 1:
        EXPECT_EQ(pass_list[0]->damage_rect, texture_quad_rect)
            << "The quad in the child surface contributes damage";
        break;

      case 2:
        EXPECT_EQ(pass_list[0]->damage_rect, gfx::Rect())
            << "No damage on the child surface before overlay processing";
        break;
    }

    auto result = TryProcessForDelegatedOverlays(
        pass_list, std::move(surface_damage_rect_list));
    result.ExpectDelegationSuccess();

    switch (frame) {
      case 0:
        EXPECT_EQ(pass_list[0]->damage_rect, pass_list[0]->output_rect)
            << "Full damage is forced on the first frame";
        EXPECT_THAT(result.candidates(),
                    WhenCandidatesAreSortedElementsAre({
                        test::IsRenderPassOverlay(child_pass_id),
                        test::OverlayHasResource(child_pass_texture_id),
                    }));
        break;

      case 1:
        EXPECT_EQ(pass_list[0]->damage_rect, gfx::Rect())
            << "Damage is removed when only from overlays";
        EXPECT_THAT(result.candidates(),
                    WhenCandidatesAreSortedElementsAre({
                        test::IsRenderPassOverlay(child_pass_id),
                        test::OverlayHasResource(child_pass_texture_id),
                    }));
        break;

      case 2:
        EXPECT_EQ(pass_list[0]->damage_rect, texture_quad_rect)
            << "Damage removed in frame 1 is re-added";
        EXPECT_THAT(result.candidates(),
                    WhenCandidatesAreSortedElementsAre({
                        test::IsRenderPassOverlay(child_pass_id),
                    }));
        break;
    }
  }
}

TEST_F(OverlayProcessorWinPartiallyDelegatedCompositingTest,
       DamageRemovedFromMultipleSurfaces) {
  const AggregatedRenderPassId left_child_pass_id{2};
  const AggregatedRenderPassId right_child_pass_id{3};
  const gfx::Rect left_texture_quad_rect = gfx::Rect(5, 5, 50, 50);
  const gfx::Rect right_texture_quad_rect = gfx::Rect(10, 5, 50, 50);

  for (int frame = 0; frame < 4; frame++) {
    SCOPED_TRACE(base::StringPrintf("Frame %d", frame));

    AggregatedRenderPassList pass_list;
    SurfaceDamageRectList surface_damage_rect_list;

    const bool is_left_overlay_candidate_with_damage = frame < 3;
    const bool is_right_overlay_candidate_with_damage = frame < 2;

    auto pass = CreateRenderPass();
    gfx::Rect left_rpdq_quad;
    gfx::Rect right_rpdq_quad;
    pass->output_rect.SplitVertically(left_rpdq_quad, right_rpdq_quad);
    // Ensure the RPDQs aren't touching so their damages will be disjoint.
    left_rpdq_quad.Inset(5);
    right_rpdq_quad.Inset(5);

    auto left_child_pass = CreateRenderPass(left_child_pass_id);
    left_child_pass->transform_to_root_target =
        gfx::Transform::MakeTranslation(left_rpdq_quad.OffsetFromOrigin());
    left_child_pass->is_from_surface_root_pass = true;
    left_child_pass->output_rect.set_size(left_rpdq_quad.size());
    left_child_pass->damage_rect = gfx::Rect();
    auto* left_texture_quad =
        is_left_overlay_candidate_with_damage
            ? CreateOverlayQuadWithSurfaceDamageAt(left_child_pass.get(),
                                                   surface_damage_rect_list,
                                                   left_texture_quad_rect)
            : CreateTextureQuadAt(
                  resource_provider_.get(), child_resource_provider_.get(),
                  child_provider_.get(),
                  left_child_pass->CreateAndAppendSharedQuadState(),
                  left_child_pass.get(), left_texture_quad_rect,
                  /*is_overlay_candidate=*/false);
    ResourceId left_child_pass_texture_id = left_texture_quad->resource_id();
    pass_list.push_back(std::move(left_child_pass));

    auto right_child_pass = CreateRenderPass(right_child_pass_id);
    right_child_pass->transform_to_root_target =
        gfx::Transform::MakeTranslation(right_rpdq_quad.OffsetFromOrigin());
    right_child_pass->is_from_surface_root_pass = true;
    right_child_pass->output_rect.set_size(right_rpdq_quad.size());
    right_child_pass->damage_rect = gfx::Rect();
    auto* right_texture_quad =
        is_right_overlay_candidate_with_damage
            ? CreateOverlayQuadWithSurfaceDamageAt(right_child_pass.get(),
                                                   surface_damage_rect_list,
                                                   right_texture_quad_rect)
            : CreateTextureQuadAt(
                  resource_provider_.get(), child_resource_provider_.get(),
                  child_provider_.get(),
                  right_child_pass->CreateAndAppendSharedQuadState(),
                  right_child_pass.get(), right_texture_quad_rect,
                  /*is_overlay_candidate=*/false);
    ResourceId right_child_pass_texture_id = right_texture_quad->resource_id();
    pass_list.push_back(std::move(right_child_pass));

    CreateRenderPassDrawQuadAt(pass.get(),
                               pass->CreateAndAppendSharedQuadState(),
                               left_rpdq_quad, left_child_pass_id);
    CreateRenderPassDrawQuadAt(pass.get(),
                               pass->CreateAndAppendSharedQuadState(),
                               right_rpdq_quad, right_child_pass_id);
    pass_list.push_back(std::move(pass));

    switch (frame) {
      case 0:
      case 1:
        EXPECT_EQ(pass_list[0]->damage_rect, left_texture_quad_rect)
            << "The quad in the child surface contributes damage";
        EXPECT_EQ(pass_list[1]->damage_rect, right_texture_quad_rect)
            << "The quad in the child surface contributes damage";
        break;

      case 2:
        EXPECT_EQ(pass_list[0]->damage_rect, left_texture_quad_rect)
            << "The quad in the child surface contributes damage";
        EXPECT_EQ(pass_list[1]->damage_rect, gfx::Rect())
            << "No damage on the child surface before overlay processing";
        break;

      case 3:
        EXPECT_EQ(pass_list[0]->damage_rect, gfx::Rect())
            << "No damage on the child surface before overlay processing";
        EXPECT_EQ(pass_list[1]->damage_rect, gfx::Rect())
            << "No damage on the child surface before overlay processing";
        break;
    }

    auto result = TryProcessForDelegatedOverlays(
        pass_list, std::move(surface_damage_rect_list));
    result.ExpectDelegationSuccess();

    switch (frame) {
      case 0:
        EXPECT_EQ(pass_list[0]->damage_rect, pass_list[0]->output_rect)
            << "Full damage is forced on the first frame";
        EXPECT_EQ(pass_list[1]->damage_rect, pass_list[1]->output_rect)
            << "Full damage is forced on the first frame";
        EXPECT_THAT(result.candidates(),
                    WhenCandidatesAreSortedElementsAre({
                        test::IsRenderPassOverlay(right_child_pass_id),
                        test::OverlayHasResource(right_child_pass_texture_id),
                        test::IsRenderPassOverlay(left_child_pass_id),
                        test::OverlayHasResource(left_child_pass_texture_id),
                    }));
        break;

      case 1:
        EXPECT_EQ(pass_list[0]->damage_rect, gfx::Rect())
            << "Damage is removed when only from overlays";
        EXPECT_EQ(pass_list[1]->damage_rect, gfx::Rect())
            << "Damage is removed when only from overlays";
        EXPECT_THAT(result.candidates(),
                    WhenCandidatesAreSortedElementsAre({
                        test::IsRenderPassOverlay(right_child_pass_id),
                        test::OverlayHasResource(right_child_pass_texture_id),
                        test::IsRenderPassOverlay(left_child_pass_id),
                        test::OverlayHasResource(left_child_pass_texture_id),
                    }));
        break;

      case 2:
        EXPECT_EQ(pass_list[0]->damage_rect, gfx::Rect())
            << "Damage is removed when only from overlays";
        EXPECT_EQ(pass_list[1]->damage_rect, right_texture_quad_rect)
            << "Damage removed in frame 1 is re-added";
        EXPECT_THAT(result.candidates(),
                    WhenCandidatesAreSortedElementsAre({
                        test::IsRenderPassOverlay(right_child_pass_id),
                        test::IsRenderPassOverlay(left_child_pass_id),
                        test::OverlayHasResource(left_child_pass_texture_id),
                    }));
        break;

      case 3:
        EXPECT_EQ(pass_list[0]->damage_rect, left_texture_quad_rect)
            << "Damage removed in frame 2 is re-added";
        EXPECT_EQ(pass_list[1]->damage_rect, gfx::Rect());
        EXPECT_THAT(result.candidates(),
                    WhenCandidatesAreSortedElementsAre({
                        test::IsRenderPassOverlay(right_child_pass_id),
                        test::IsRenderPassOverlay(left_child_pass_id),
                    }));
        break;
    }
  }
}

}  // namespace
}  // namespace viz