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

#include "components/viz/service/display/display.h"

#include <limits>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/null_task_runner.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "cc/base/features.h"
#include "cc/base/math_util.h"
#include "cc/test/scheduler_test_common.h"
#include "components/viz/common/features.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/frame_sinks/copy_output_result.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/compositor_frame.h"
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/surface_draw_quad.h"
#include "components/viz/common/surfaces/frame_sink_id.h"
#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
#include "components/viz/common/surfaces/region_capture_bounds.h"
#include "components/viz/common/surfaces/subtree_capture_id.h"
#include "components/viz/common/switches.h"
#include "components/viz/service/display/aggregated_frame.h"
#include "components/viz/service/display/delegated_ink_point_renderer_skia.h"
#include "components/viz/service/display/delegated_ink_trail_data.h"
#include "components/viz/service/display/direct_renderer.h"
#include "components/viz/service/display/display_client.h"
#include "components/viz/service/display/display_scheduler.h"
#include "components/viz/service/display/overlay_processor_stub.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/service/surfaces/surface.h"
#include "components/viz/service/surfaces/surface_manager.h"
#include "components/viz/test/compositor_frame_helpers.h"
#include "components/viz/test/delegated_ink_point_renderer_skia_for_test.h"
#include "components/viz/test/fake_output_surface.h"
#include "components/viz/test/fake_skia_output_surface.h"
#include "components/viz/test/mock_compositor_frame_sink_client.h"
#include "components/viz/test/test_gles2_interface.h"
#include "components/viz/test/test_surface_id_allocator.h"
#include "components/viz/test/viz_test_suite.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "gpu/command_buffer/common/swap_buffers_complete_params.h"
#include "gpu/command_buffer/service/scheduler.h"
#include "gpu/command_buffer/service/shared_image/shared_image_manager.h"
#include "gpu/command_buffer/service/sync_point_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkRect.h"
#include "ui/gfx/delegated_ink_metadata.h"
#include "ui/gfx/delegated_ink_point.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/gfx/mojom/delegated_ink_point_renderer.mojom.h"
#include "ui/gfx/overlay_transform.h"

using testing::_;
using testing::AnyNumber;

namespace viz {
namespace {

static constexpr FrameSinkId kArbitraryFrameSinkId(3, 3);
static constexpr FrameSinkId kAnotherFrameSinkId(4, 4);
static constexpr FrameSinkId kAnotherFrameSinkId2(5, 5);

const uint64_t kBeginFrameSourceId = 1337;

class TestSoftwareOutputDevice : public SoftwareOutputDevice {
 public:
  gfx::Rect damage_rect() const { return damage_rect_; }
  gfx::Size viewport_pixel_size() const { return viewport_pixel_size_; }
};

class TestDisplayScheduler : public DisplayScheduler {
 public:
  explicit TestDisplayScheduler(BeginFrameSource* begin_frame_source,
                                base::SingleThreadTaskRunner* task_runner)
      : DisplayScheduler(begin_frame_source,
                         task_runner,
                         PendingSwapParams(1)) {}

  ~TestDisplayScheduler() override = default;

  void OnDisplayDamaged(SurfaceId surface_id, BeginFrameId frame_id) override {
    damaged_ = true;
    needs_draw_ = true;
  }

  void DidSwapBuffers() override { swapped_ = true; }

  void ResetDamageForTest() { damaged_ = false; }

  bool damaged() const { return damaged_; }
  bool swapped() const { return swapped_; }
  void reset_swapped_for_test() { swapped_ = false; }

 private:
  bool damaged_ = false;
  bool swapped_ = false;
};

class StubDisplayClient : public DisplayClient {
 public:
  void DisplayOutputSurfaceLost() override {}
  void DisplayWillDrawAndSwap(
      bool will_draw_and_swap,
      AggregatedRenderPassList* render_passes) override {}
  void DisplayDidDrawAndSwap() override {}
  void DisplayDidReceiveCALayerParams(
      gfx::CALayerParams ca_layer_params) override {}
  void DisplayDidCompleteSwapWithSize(const gfx::Size& pixel_size) override {}
  void DisplayAddChildWindowToBrowser(
      gpu::SurfaceHandle child_window) override {}
  void SetWideColorEnabled(bool enabled) override {}
};

void CopyCallback(bool* called,
                  base::OnceClosure finished,
                  std::unique_ptr<CopyOutputResult> result) {
  *called = true;
  std::move(finished).Run();
}

}  // namespace

class DisplayTest : public testing::Test {
 public:
  DisplayTest()
      : manager_(FrameSinkManagerImpl::InitParams()),
        support_(
            std::make_unique<CompositorFrameSinkSupport>(nullptr,
                                                         &manager_,
                                                         kArbitraryFrameSinkId,
                                                         true /* is_root */)),
        task_runner_(new base::NullTaskRunner),
        client_(std::make_unique<StubDisplayClient>()) {}

  ~DisplayTest() override = default;

  void SetUpSoftwareDisplay(const RendererSettings& settings) {
    std::unique_ptr<FakeSoftwareOutputSurface> output_surface;
    auto device = std::make_unique<TestSoftwareOutputDevice>();
    software_output_device_ = device.get();
    output_surface =
        std::make_unique<FakeSoftwareOutputSurface>(std::move(device));
    output_surface_ = output_surface.get();

    CreateDisplaySchedulerAndDisplay(settings, kArbitraryFrameSinkId,
                                     std::move(output_surface));
  }

  void SetUpGpuDisplay(const RendererSettings& settings) {
    scoped_refptr<TestContextProvider> provider =
        TestContextProvider::CreateGLES();
    provider->BindToCurrentSequence();
    std::unique_ptr<FakeSkiaOutputSurface> skia_output_surface =
        FakeSkiaOutputSurface::Create3d(std::move(provider));
    skia_output_surface_ = skia_output_surface.get();

    CreateDisplaySchedulerAndDisplay(settings, kArbitraryFrameSinkId,
                                     std::move(skia_output_surface));
  }

  void CreateDisplaySchedulerAndDisplay(
      const RendererSettings& settings,
      const FrameSinkId& frame_sink_id,
      std::unique_ptr<OutputSurface> output_surface) {
    begin_frame_source_ = std::make_unique<StubBeginFrameSource>();
    auto scheduler = std::make_unique<TestDisplayScheduler>(
        begin_frame_source_.get(), task_runner_.get());
    scheduler_ = scheduler.get();
    display_ = CreateDisplay(settings, kArbitraryFrameSinkId,
                             std::move(scheduler), std::move(output_surface));
    manager_.RegisterBeginFrameSource(begin_frame_source_.get(),
                                      kArbitraryFrameSinkId);
  }

  std::unique_ptr<Display> CreateDisplay(
      const RendererSettings& settings,
      const FrameSinkId& frame_sink_id,
      std::unique_ptr<DisplayScheduler> scheduler,
      std::unique_ptr<OutputSurface> output_surface) {
    auto overlay_processor = std::make_unique<OverlayProcessorStub>();
    // Normally display will need to take ownership of a
    // DisplayCompositorMemoryAndTaskController in order to keep it alive to
    // share between the output surface and the overlay processor. In this case
    // the overlay processor is a stub and the output surface is test only as
    // well, so there is no need to pass in a real
    // DisplayCompositorMemoryAndTaskController.
    auto display = std::make_unique<Display>(
        &shared_image_manager_, &gpu_scheduler_, settings, &debug_settings_,
        frame_sink_id, nullptr /* DisplayCompositorMemoryAndTaskController */,
        std::move(output_surface), std::move(overlay_processor),
        std::move(scheduler), task_runner_);
    display->SetVisible(true);
    return display;
  }

  bool ShouldSendBeginFrame(CompositorFrameSinkSupport* support,
                            base::TimeTicks frame_time) {
    return support->ShouldSendBeginFrame(BeginFrameId(999, 999), frame_time,
                                         base::Seconds(0));
  }

  void UpdateBeginFrameTime(CompositorFrameSinkSupport* support,
                            base::TimeTicks frame_time) {
    support->last_frame_time_ = frame_time;
    support->frame_timing_details_.clear();
  }

 protected:
  void TearDown() override {
    // Only call UnregisterBeginFrameSource if SetupDisplay has been called.
    if (begin_frame_source_)
      manager_.UnregisterBeginFrameSource(begin_frame_source_.get());
  }

  void SubmitCompositorFrame(CompositorRenderPassList* pass_list,
                             const LocalSurfaceId& local_surface_id) {
    CompositorFrame frame = CompositorFrameBuilder()
                                .SetRenderPassList(std::move(*pass_list))
                                .Build();
    pass_list->clear();

    support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
  }

  void ResetDamageForTest() { scheduler_->ResetDamageForTest(); }

  void RunUntilIdle() { VizTestSuite::RunUntilIdle(); }

  void LatencyInfoCapTest(bool over_capacity);

  size_t pending_presentation_group_timings_size() {
    return display_->pending_presentation_group_timings_.size();
  }

  DebugRendererSettings debug_settings_;
  gpu::SharedImageManager shared_image_manager_;
  gpu::SyncPointManager sync_point_manager_;
  gpu::Scheduler gpu_scheduler_{&sync_point_manager_};
  FrameSinkManagerImpl manager_;
  std::unique_ptr<CompositorFrameSinkSupport> support_;
  ParentLocalSurfaceIdAllocator id_allocator_;
  scoped_refptr<base::NullTaskRunner> task_runner_;
  std::unique_ptr<BeginFrameSource> begin_frame_source_;
  std::unique_ptr<StubDisplayClient> client_;  // Must outlive `display_`.
  std::unique_ptr<Display> display_;
  raw_ptr<TestSoftwareOutputDevice> software_output_device_ = nullptr;
  raw_ptr<FakeSoftwareOutputSurface> output_surface_ = nullptr;
  raw_ptr<FakeSkiaOutputSurface> skia_output_surface_ = nullptr;
  raw_ptr<TestDisplayScheduler> scheduler_ = nullptr;
};

// Check that frame is damaged and swapped only under correct conditions.
TEST_F(DisplayTest, DisplayDamaged) {
  RendererSettings settings;
  settings.partial_swap_enabled = true;
  SetUpSoftwareDisplay(settings);
  display_->Initialize(client_.get(), manager_.surface_manager());

  gfx::ColorSpace color_space_1 = gfx::ColorSpace::CreateXYZD50();
  gfx::ColorSpace color_space_2 = gfx::ColorSpace::CreateSRGBLinear();
  gfx::DisplayColorSpaces color_spaces_1(color_space_1);
  gfx::DisplayColorSpaces color_spaces_2(color_space_2);
  display_->SetDisplayColorSpaces(color_spaces_1);

  EXPECT_FALSE(scheduler_->damaged());
  id_allocator_.GenerateId();
  display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
  display_->Resize(gfx::Size(100, 100));

  // First draw from surface should have full damage.
  CompositorRenderPassList pass_list;
  auto pass = CompositorRenderPass::Create();
  pass->output_rect = gfx::Rect(0, 0, 100, 100);
  pass->damage_rect = gfx::Rect(10, 10, 1, 1);
  pass->id = CompositorRenderPassId{1u};
  pass_list.push_back(std::move(pass));

  ResetDamageForTest();
  SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
  EXPECT_TRUE(scheduler_->damaged());

  EXPECT_FALSE(scheduler_->swapped());
  EXPECT_EQ(0u, output_surface_->num_sent_frames());
  EXPECT_EQ(gfx::ColorSpace(), output_surface_->last_reshape_color_space());
  DrawAndSwapParams params1;
  params1.expected_display_time = base::TimeTicks::Now();
  display_->DrawAndSwap(params1);
  EXPECT_EQ(color_space_1, output_surface_->last_reshape_color_space());
  EXPECT_TRUE(scheduler_->swapped());
  EXPECT_EQ(1u, output_surface_->num_sent_frames());
  EXPECT_EQ(gfx::Size(100, 100),
            software_output_device_->viewport_pixel_size());
  EXPECT_EQ(gfx::Rect(0, 0, 100, 100), software_output_device_->damage_rect());

  // Only a small area is damaged but the color space changes which should
  // result in full damage.
  {
    pass = CompositorRenderPass::Create();
    pass->output_rect = gfx::Rect(0, 0, 100, 100);
    pass->damage_rect = gfx::Rect(10, 10, 1, 1);
    pass->id = CompositorRenderPassId{1u};

    pass_list.push_back(std::move(pass));
    ResetDamageForTest();
    SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
    EXPECT_TRUE(scheduler_->damaged());

    scheduler_->reset_swapped_for_test();
    EXPECT_EQ(color_space_1, output_surface_->last_reshape_color_space());
    display_->SetDisplayColorSpaces(color_spaces_2);
    DrawAndSwapParams params2;
    params2.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(params2);
    EXPECT_EQ(color_space_2, output_surface_->last_reshape_color_space());
    EXPECT_TRUE(scheduler_->swapped());
    EXPECT_EQ(2u, output_surface_->num_sent_frames());
    EXPECT_EQ(gfx::Size(100, 100),
              software_output_device_->viewport_pixel_size());
    EXPECT_EQ(gfx::Rect(0, 0, 100, 100),
              software_output_device_->damage_rect());

    EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());
  }

  // Same frame as above but no color space change. Only partial area should be
  // drawn.
  {
    pass = CompositorRenderPass::Create();
    pass->output_rect = gfx::Rect(0, 0, 100, 100);
    pass->damage_rect = gfx::Rect(10, 10, 1, 1);
    pass->id = CompositorRenderPassId{1u};

    pass_list.push_back(std::move(pass));
    ResetDamageForTest();
    SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
    EXPECT_TRUE(scheduler_->damaged());

    scheduler_->reset_swapped_for_test();
    EXPECT_EQ(color_space_2, output_surface_->last_reshape_color_space());
    display_->SetDisplayColorSpaces(color_spaces_2);
    DrawAndSwapParams params3;
    params3.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(params3);
    EXPECT_EQ(color_space_2, output_surface_->last_reshape_color_space());
    EXPECT_TRUE(scheduler_->swapped());
    EXPECT_EQ(3u, output_surface_->num_sent_frames());
    EXPECT_EQ(gfx::Size(100, 100),
              software_output_device_->viewport_pixel_size());
    EXPECT_EQ(gfx::Rect(10, 10, 1, 1), software_output_device_->damage_rect());

    EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());
  }

  // Pass has no damage so shouldn't be swapped.
  {
    pass = CompositorRenderPass::Create();
    pass->output_rect = gfx::Rect(0, 0, 100, 100);
    pass->damage_rect = gfx::Rect(10, 10, 0, 0);
    pass->id = CompositorRenderPassId{1u};

    pass_list.push_back(std::move(pass));
    ResetDamageForTest();
    SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
    EXPECT_TRUE(scheduler_->damaged());

    scheduler_->reset_swapped_for_test();
    DrawAndSwapParams params4;
    params4.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(params4);
    EXPECT_TRUE(scheduler_->swapped());
    EXPECT_EQ(3u, output_surface_->num_sent_frames());
  }

  // Pass is wrong size so shouldn't be swapped. However, damage should
  // result in latency info being stored for the next swap.
  {
    id_allocator_.GenerateId();
    display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);

    ResetDamageForTest();

    constexpr gfx::Rect kOutputRect(0, 0, 99, 99);
    constexpr gfx::Rect kDamageRect(10, 10, 10, 10);
    CompositorFrame frame = CompositorFrameBuilder()
                                .AddRenderPass(kOutputRect, kDamageRect)
                                .AddLatencyInfo(ui::LatencyInfo())
                                .Build();

    support_->SubmitCompositorFrame(id_allocator_.GetCurrentLocalSurfaceId(),
                                    std::move(frame));
    EXPECT_TRUE(scheduler_->damaged());

    scheduler_->reset_swapped_for_test();
    DrawAndSwapParams params5;
    params5.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(params5);
    EXPECT_TRUE(scheduler_->swapped());
    EXPECT_EQ(3u, output_surface_->num_sent_frames());
  }

  // Previous frame wasn't swapped, so next swap should have full damage.
  {
    pass = CompositorRenderPass::Create();
    pass->output_rect = gfx::Rect(0, 0, 100, 100);
    pass->damage_rect = gfx::Rect(10, 10, 0, 0);
    pass->id = CompositorRenderPassId{1u};

    id_allocator_.GenerateId();
    display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);

    pass_list.push_back(std::move(pass));
    ResetDamageForTest();
    SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
    EXPECT_TRUE(scheduler_->damaged());

    scheduler_->reset_swapped_for_test();
    DrawAndSwapParams params6;
    params6.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(params6);
    EXPECT_TRUE(scheduler_->swapped());
    EXPECT_EQ(4u, output_surface_->num_sent_frames());
    EXPECT_EQ(gfx::Rect(0, 0, 100, 100),
              software_output_device_->damage_rect());

    EXPECT_EQ(1u, output_surface_->last_sent_frame()->latency_info.size());
  }

  // Pass has copy output request so should be swapped.
  {
    pass = CompositorRenderPass::Create();
    pass->output_rect = gfx::Rect(0, 0, 100, 100);
    pass->damage_rect = gfx::Rect(10, 10, 0, 0);
    base::RunLoop copy_run_loop;
    bool copy_called = false;
    pass->copy_requests.push_back(std::make_unique<CopyOutputRequest>(
        CopyOutputRequest::ResultFormat::RGBA,
        CopyOutputRequest::ResultDestination::kSystemMemory,
        base::BindOnce(&CopyCallback, &copy_called,
                       copy_run_loop.QuitClosure())));
    pass->id = CompositorRenderPassId{1u};

    pass_list.push_back(std::move(pass));
    ResetDamageForTest();
    SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
    EXPECT_TRUE(scheduler_->damaged());

    scheduler_->reset_swapped_for_test();
    DrawAndSwapParams params7;
    params7.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(params7);
    EXPECT_TRUE(scheduler_->swapped());
    EXPECT_EQ(5u, output_surface_->num_sent_frames());
    copy_run_loop.Run();
    EXPECT_TRUE(copy_called);
  }

  // Pass has no damage, so shouldn't be swapped and latency info should be
  // discarded.
  {
    ResetDamageForTest();

    constexpr gfx::Rect kOutputRect(0, 0, 100, 100);
    constexpr gfx::Rect kDamageRect(10, 10, 0, 0);
    CompositorFrame frame = CompositorFrameBuilder()
                                .AddRenderPass(kOutputRect, kDamageRect)
                                .AddLatencyInfo(ui::LatencyInfo())
                                .Build();
    frame.metadata.latency_info.push_back(ui::LatencyInfo());

    support_->SubmitCompositorFrame(id_allocator_.GetCurrentLocalSurfaceId(),
                                    std::move(frame));
    EXPECT_TRUE(scheduler_->damaged());

    scheduler_->reset_swapped_for_test();
    DrawAndSwapParams params8;
    params8.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(params8);
    EXPECT_TRUE(scheduler_->swapped());
    EXPECT_EQ(5u, output_surface_->num_sent_frames());
  }

  // Surface that's damaged completely should be resized and swapped.
  {
    id_allocator_.GenerateId();
    display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.0f);
    pass = CompositorRenderPass::Create();
    pass->output_rect = gfx::Rect(0, 0, 99, 99);
    pass->damage_rect = gfx::Rect(0, 0, 99, 99);
    pass->id = CompositorRenderPassId{1u};

    pass_list.push_back(std::move(pass));
    ResetDamageForTest();
    SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
    EXPECT_TRUE(scheduler_->damaged());

    scheduler_->reset_swapped_for_test();
    DrawAndSwapParams params9;
    params9.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(params9);
    EXPECT_TRUE(scheduler_->swapped());
    EXPECT_EQ(6u, output_surface_->num_sent_frames());
    EXPECT_EQ(gfx::Size(100, 100),
              software_output_device_->viewport_pixel_size());
    EXPECT_EQ(gfx::Rect(0, 0, 100, 100),
              software_output_device_->damage_rect());
    EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());
  }
}

// Verifies latency info is stored only up to a limit if a swap fails.
void DisplayTest::LatencyInfoCapTest(bool over_capacity) {
  SetUpSoftwareDisplay(RendererSettings());
  display_->Initialize(client_.get(), manager_.surface_manager());

  id_allocator_.GenerateId();
  LocalSurfaceId local_surface_id(id_allocator_.GetCurrentLocalSurfaceId());
  display_->SetLocalSurfaceId(local_surface_id, 1.f);

  display_->Resize(gfx::Size(100, 100));

  // Start off with a successful swap so output_surface_->last_sent_frame() is
  // valid.
  constexpr gfx::Rect kOutputRect(0, 0, 100, 100);
  constexpr gfx::Rect kDamageRect(10, 10, 1, 1);
  CompositorFrame frame1 =
      CompositorFrameBuilder().AddRenderPass(kOutputRect, kDamageRect).Build();
  support_->SubmitCompositorFrame(local_surface_id, std::move(frame1));

  DrawAndSwapParams cap_params1;
  cap_params1.expected_display_time = base::TimeTicks::Now();
  display_->DrawAndSwap(cap_params1);
  EXPECT_EQ(1u, output_surface_->num_sent_frames());
  EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());

  // Resize so the swap fails even though there's damage, which triggers
  // the case where we store latency info to append to a future swap.
  display_->Resize(gfx::Size(200, 200));

  // This is the same as LatencyInfo::kMaxLatencyInfoNumber.
  const size_t max_latency_info_count = 100;
  size_t latency_count = max_latency_info_count;
  if (over_capacity)
    latency_count++;
  std::vector<ui::LatencyInfo> latency_info(latency_count, ui::LatencyInfo());

  CompositorFrame frame2 = CompositorFrameBuilder()
                               .AddRenderPass(kOutputRect, kDamageRect)
                               .AddLatencyInfos(std::move(latency_info))
                               .Build();
  support_->SubmitCompositorFrame(local_surface_id, std::move(frame2));

  DrawAndSwapParams cap_params2;
  cap_params2.expected_display_time = base::TimeTicks::Now();
  EXPECT_TRUE(display_->DrawAndSwap(cap_params2));
  EXPECT_EQ(1u, output_surface_->num_sent_frames());
  EXPECT_EQ(0u, output_surface_->last_sent_frame()->latency_info.size());

  // Run a successful swap and verify whether or not LatencyInfo was discarded.
  display_->Resize(gfx::Size(100, 100));
  CompositorFrame frame3 =
      CompositorFrameBuilder().AddRenderPass(kOutputRect, kDamageRect).Build();
  support_->SubmitCompositorFrame(local_surface_id, std::move(frame3));
  DrawAndSwapParams cap_params3;
  cap_params3.expected_display_time = base::TimeTicks::Now();
  EXPECT_TRUE(display_->DrawAndSwap(cap_params3));

  // Verify whether or not LatencyInfo was dropped.
  size_t expected_size = 0;
  if (!over_capacity)
    expected_size += max_latency_info_count;
  EXPECT_EQ(2u, output_surface_->num_sent_frames());
  EXPECT_EQ(expected_size,
            output_surface_->last_sent_frame()->latency_info.size());
}

TEST_F(DisplayTest, UnderLatencyInfoCap) {
  LatencyInfoCapTest(false);
}

TEST_F(DisplayTest, OverLatencyInfoCap) {
  LatencyInfoCapTest(true);
}

TEST_F(DisplayTest, BackdropFilterTest) {
  RendererSettings settings;
  settings.partial_swap_enabled = true;
  id_allocator_.GenerateId();
  const LocalSurfaceId local_surface_id(
      id_allocator_.GetCurrentLocalSurfaceId());

  // Set up first display.
  SetUpSoftwareDisplay(settings);
  display_->Initialize(client_.get(), manager_.surface_manager());
  display_->SetLocalSurfaceId(local_surface_id, 1.f);

  // Create frame sink for a sub surface.
  const LocalSurfaceId sub_local_surface_id1(6,
                                             base::UnguessableToken::Create());
  const SurfaceId sub_surface_id1(kAnotherFrameSinkId, sub_local_surface_id1);
  auto sub_support1 = std::make_unique<CompositorFrameSinkSupport>(
      nullptr, &manager_, kAnotherFrameSinkId, /*is_root=*/false);

  // Create frame sink for another sub surface.
  const LocalSurfaceId sub_local_surface_id2(7,
                                             base::UnguessableToken::Create());
  const SurfaceId sub_surface_id2(kAnotherFrameSinkId2, sub_local_surface_id2);
  auto sub_support2 = std::make_unique<CompositorFrameSinkSupport>(
      nullptr, &manager_, kAnotherFrameSinkId2, /*is_root=*/false);

  // Main surface M, damage D, sub-surface B with backdrop filter.
  //   +-----------+
  //   | +----+   M|
  //   | |B +-|-+  |
  //   | +--|-+ |  |
  //   |    |  D|  |
  //   |    +---+  |
  //   +-----------+
  cc::FilterOperation backdrop_filter =
      cc::FilterOperation::CreateBlurFilter(5.0);
  const gfx::Size display_size(100, 100);
  const gfx::Rect isolated_damage_rect(20, 20, 40, 40);
  display_->Resize(display_size);
  const gfx::Rect sub_surface_rect(5, 5, 25, 25);
  const gfx::Rect no_damage;

  // By this time, the compositor should have already calculated the damage rect
  // taking into consideration backdrop filters. Therefore, to correctly
  // simulate the compositor's behavior, we need to calculate the damage rect
  // while taking the backdrop filters into account so that no damage expansion
  // occurs during surface aggregation.
  gfx::Rect intersection_damage_rect = isolated_damage_rect;
  intersection_damage_rect.Intersect(sub_surface_rect);
  gfx::Rect expanded_intersection_damage_rect =
      backdrop_filter.MapRect(intersection_damage_rect, SkMatrix());
  gfx::Rect damage_rect = expanded_intersection_damage_rect;
  damage_rect.Union(isolated_damage_rect);

  CompositorRenderPassId::Generator render_pass_id_generator;
  for (size_t frame_num = 1; frame_num <= 2; ++frame_num) {
    bool first_frame = frame_num == 1;
    ResetDamageForTest();
    {
      // To-be-filtered sub-surface.
      CompositorRenderPassList pass_list;
      auto bd_pass = CompositorRenderPass::Create();
      bd_pass->SetNew(render_pass_id_generator.GenerateNextId(),
                      sub_surface_rect, no_damage, gfx::Transform());
      pass_list.push_back(std::move(bd_pass));

      CompositorFrame frame = CompositorFrameBuilder()
                                  .SetRenderPassList(std::move(pass_list))
                                  .Build();
      sub_support1->SubmitCompositorFrame(sub_local_surface_id1,
                                          std::move(frame));
    }

    {
      // Sub-surface with damage.
      CompositorRenderPassList pass_list;
      auto other_pass = CompositorRenderPass::Create();
      other_pass->output_rect = gfx::Rect(display_size);
      other_pass->damage_rect = damage_rect;
      other_pass->id = render_pass_id_generator.GenerateNextId();
      pass_list.push_back(std::move(other_pass));

      CompositorFrame frame = CompositorFrameBuilder()
                                  .SetRenderPassList(std::move(pass_list))
                                  .Build();
      sub_support2->SubmitCompositorFrame(sub_local_surface_id2,
                                          std::move(frame));
    }

    {
      // Child render pass embedding sub surface 1 via a SurfaceDrawQuad.
      auto child_pass_id = render_pass_id_generator.GenerateNextId();
      auto root_pass_id = render_pass_id_generator.GenerateNextId();

      auto frame =
          CompositorFrameBuilder()
              .AddRenderPass(RenderPassBuilder(child_pass_id, sub_surface_rect)
                                 .AddBackdropFilter(backdrop_filter)
                                 .AddSurfaceQuad(sub_surface_rect,
                                                 SurfaceRange(std::nullopt,
                                                              sub_surface_id1),
                                                 {.allow_merge = false}))
              .AddRenderPass(
                  RenderPassBuilder(root_pass_id, gfx::Rect(display_size))
                      .AddRenderPassQuad(sub_surface_rect, child_pass_id)
                      .AddSurfaceQuad(
                          gfx::Rect(display_size),
                          SurfaceRange(std::nullopt, sub_surface_id2),
                          {.allow_merge = false})
                      .SetDamageRect(damage_rect))
              .Build();
      support_->SubmitCompositorFrame(local_surface_id, std::move(frame));

      scheduler_->reset_swapped_for_test();
      DrawAndSwapParams backdrop_params;
      backdrop_params.expected_display_time = base::TimeTicks::Now();
      display_->DrawAndSwap(backdrop_params);
      EXPECT_TRUE(scheduler_->swapped());
      EXPECT_EQ(frame_num, output_surface_->num_sent_frames());
      EXPECT_EQ(display_size, software_output_device_->viewport_pixel_size());
      // The damage rect produced by surface_aggregator only includes the
      // damaged surface rect, and is not expanded for the backdrop-filter
      // surface.
      auto expected_damage =
          first_frame ? gfx::Rect(display_size) : damage_rect;
      EXPECT_EQ(expected_damage, software_output_device_->damage_rect());
      // The scissor rect is expanded by direct_renderer to include the
      // overlapping pixel-moving backdrop filter surface.
      auto expected_scissor_rect =
          first_frame ? gfx::Rect(display_size) : gfx::Rect(5, 5, 55, 55);
      EXPECT_EQ(
          expected_scissor_rect,
          display_->renderer_for_testing()->GetLastRootScissorRectForTesting());
    }
  }
}

// Regression test for https://crbug.com/727162: Submitting a CompositorFrame to
// a surface should only cause damage on the Display the surface belongs to.
// There should not be a side-effect on other Displays.
TEST_F(DisplayTest, CompositorFrameDamagesCorrectDisplay) {
  RendererSettings settings;
  id_allocator_.GenerateId();
  LocalSurfaceId local_surface_id(id_allocator_.GetCurrentLocalSurfaceId());

  // Set up first display.
  SetUpSoftwareDisplay(settings);
  display_->Initialize(client_.get(), manager_.surface_manager());
  display_->SetLocalSurfaceId(local_surface_id, 1.f);

  // Set up second frame sink + display.
  auto support2 = std::make_unique<CompositorFrameSinkSupport>(
      nullptr, &manager_, kAnotherFrameSinkId, true /* is_root */);
  auto begin_frame_source2 = std::make_unique<StubBeginFrameSource>();
  auto scheduler_for_display2 = std::make_unique<TestDisplayScheduler>(
      begin_frame_source2.get(), task_runner_.get());
  TestDisplayScheduler* scheduler2 = scheduler_for_display2.get();
  StubDisplayClient client2;  // Must outlive `display2`.
  auto display2 = CreateDisplay(
      settings, kAnotherFrameSinkId, std::move(scheduler_for_display2),
      std::make_unique<FakeSoftwareOutputSurface>(
          std::make_unique<TestSoftwareOutputDevice>()));
  manager_.RegisterBeginFrameSource(begin_frame_source2.get(),
                                    kAnotherFrameSinkId);
  display2->Initialize(&client2, manager_.surface_manager());
  display2->SetLocalSurfaceId(local_surface_id, 1.f);

  display_->Resize(gfx::Size(100, 100));
  display2->Resize(gfx::Size(100, 100));

  ResetDamageForTest();
  scheduler2->ResetDamageForTest();
  EXPECT_FALSE(scheduler_->damaged());
  EXPECT_FALSE(scheduler2->damaged());

  // Submit a frame for display_ with full damage.
  CompositorRenderPassList pass_list;
  auto pass = CompositorRenderPass::Create();
  pass->output_rect = gfx::Rect(0, 0, 100, 100);
  pass->damage_rect = gfx::Rect(10, 10, 1, 1);
  pass->id = CompositorRenderPassId{1};
  pass_list.push_back(std::move(pass));

  SubmitCompositorFrame(&pass_list, local_surface_id);

  // Should have damaged only display_ but not display2.
  EXPECT_TRUE(scheduler_->damaged());
  EXPECT_FALSE(scheduler2->damaged());
  manager_.UnregisterBeginFrameSource(begin_frame_source2.get());
}

TEST_F(DisplayTest, CompositorFrameWithPresentationToken) {
  RendererSettings settings;
  id_allocator_.GenerateId();
  const LocalSurfaceId local_surface_id(
      id_allocator_.GetCurrentLocalSurfaceId());

  // Set up first display.
  SetUpSoftwareDisplay(settings);
  display_->Initialize(client_.get(), manager_.surface_manager());
  display_->SetLocalSurfaceId(local_surface_id, 1.f);

  // Create frame sink for a sub surface.
  const LocalSurfaceId sub_local_surface_id(6,
                                            base::UnguessableToken::Create());
  const SurfaceId sub_surface_id(kAnotherFrameSinkId, sub_local_surface_id);

  MockCompositorFrameSinkClient sub_client;

  auto sub_support = std::make_unique<CompositorFrameSinkSupport>(
      &sub_client, &manager_, kAnotherFrameSinkId, false /* is_root */);

  const gfx::Size display_size(100, 100);
  display_->Resize(display_size);
  const gfx::Size sub_surface_size(32, 32);

  uint32_t frame_token_1 = kInvalidFrameToken;
  uint32_t frame_token_2 = kInvalidFrameToken;
  {
    CompositorFrame frame =
        CompositorFrameBuilder()
            .AddRenderPass(gfx::Rect(sub_surface_size), gfx::Rect())
            .SetBeginFrameSourceId(kBeginFrameSourceId)
            .Build();
    EXPECT_CALL(sub_client, DidReceiveCompositorFrameAck(_));
    frame_token_1 = frame.metadata.frame_token;
    sub_support->SubmitCompositorFrame(sub_local_surface_id, std::move(frame));
  }

  {
    // Submit a frame for display_ with full damage.
    CompositorRenderPassList pass_list;
    auto pass = CompositorRenderPass::Create();
    pass->output_rect = gfx::Rect(display_size);
    pass->damage_rect = gfx::Rect(display_size);
    pass->id = CompositorRenderPassId{1};

    auto* shared_quad_state1 = pass->CreateAndAppendSharedQuadState();
    gfx::Rect rect1(display_size);
    shared_quad_state1->SetAll(gfx::Transform(), /*quad_layer_rect=*/rect1,
                               /*visible_quad_layer_rect=*/rect1,
                               /*mask_filter_info=*/gfx::MaskFilterInfo(),
                               /*clip=*/std::nullopt, /*contents_opaque=*/false,
                               /*opacity_f=*/0.5f, SkBlendMode::kSrcOver,
                               /*sorting_context=*/0,
                               /*layer_id=*/0u, /*fast_rounded_corner=*/false);
    auto* quad1 = pass->quad_list.AllocateAndConstruct<SolidColorDrawQuad>();
    quad1->SetNew(shared_quad_state1, rect1 /* rect */,
                  rect1 /* visible_rect */, SkColors::kBlack,
                  false /* force_anti_aliasing_off */);

    auto* shared_quad_state2 = pass->CreateAndAppendSharedQuadState();
    gfx::Rect rect2(gfx::Point(20, 20), sub_surface_size);
    shared_quad_state2->SetAll(gfx::Transform(), /*quad_layer_rect=*/rect2,
                               /*visible_quad_layer_rect=*/rect2,
                               /*mask_filter_info=*/gfx::MaskFilterInfo(),
                               /*clip=*/std::nullopt, /*contents_opaque=*/false,
                               /*opacity_f=*/1.f, SkBlendMode::kSrcOver,
                               /*sorting_context=*/0,
                               /*layer_id=*/0u, /*fast_rounded_corner=*/false);
    auto* quad2 = pass->quad_list.AllocateAndConstruct<SurfaceDrawQuad>();
    quad2->SetNew(shared_quad_state2, rect2 /* rect */,
                  rect2 /* visible_rect */,
                  SurfaceRange(std::nullopt, sub_surface_id), SkColors::kBlack,
                  false /* stretch_content_to_fill_bounds */);

    pass_list.push_back(std::move(pass));
    SubmitCompositorFrame(&pass_list, local_surface_id);
    DrawAndSwapParams presentation_params1;
    presentation_params1.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(presentation_params1);
    RunUntilIdle();
  }

  {
    CompositorFrame frame = CompositorFrameBuilder()
                                .AddRenderPass(gfx::Rect(sub_surface_size),
                                               gfx::Rect(sub_surface_size))
                                .SetBeginFrameSourceId(kBeginFrameSourceId)
                                .Build();
    frame_token_2 = frame.metadata.frame_token;

    EXPECT_CALL(sub_client, DidReceiveCompositorFrameAck(_));
    sub_support->SubmitCompositorFrame(sub_local_surface_id, std::move(frame));

    DrawAndSwapParams presentation_params2;
    presentation_params2.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(presentation_params2);
    RunUntilIdle();

    // Both frames with frame-tokens 1 and 2 requested presentation-feedback.
    ASSERT_EQ(2u, sub_support->timing_details().size());
    EXPECT_EQ(sub_support->timing_details().count(frame_token_1), 1u);
    EXPECT_EQ(sub_support->timing_details().count(frame_token_2), 1u);
  }

  {
    CompositorFrame frame =
        CompositorFrameBuilder()
            .AddRenderPass(gfx::Rect(sub_surface_size), gfx::Rect())
            .SetBeginFrameSourceId(kBeginFrameSourceId)
            .Build();

    EXPECT_CALL(sub_client, DidReceiveCompositorFrameAck(_));
    sub_support->SubmitCompositorFrame(sub_local_surface_id, std::move(frame));

    DrawAndSwapParams presentation_params3;
    presentation_params3.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(presentation_params3);
    RunUntilIdle();
  }
}

TEST_F(DisplayTest, BeginFrameThrottling) {
  base::test::ScopedFeatureList scoped_features;
  scoped_features.InitAndDisableFeature(features::kNoCompositorFrameAcks);
  id_allocator_.GenerateId();
  SetUpGpuDisplay(RendererSettings());
  display_->Initialize(client_.get(), manager_.surface_manager());
  display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
  support_->SetNeedsBeginFrame(true);

  // Helper fn to submit a CF.
  auto submit_frame = [this]() {
    CompositorRenderPassList pass_list;
    auto pass = CompositorRenderPass::Create();
    pass->output_rect = gfx::Rect(0, 0, 100, 100);
    pass->damage_rect = gfx::Rect(10, 10, 1, 1);
    pass->id = CompositorRenderPassId{1u};
    pass_list.push_back(std::move(pass));

    SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
  };

  // Submit kUndrawnFrameLimit+1 frames. BeginFrames should be throttled only
  // after the last frame.
  base::TimeTicks frame_time;
  for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit + 1;
       ++i) {
    frame_time = base::TimeTicks::Now();
    EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
    UpdateBeginFrameTime(support_.get(), frame_time);
    submit_frame();
    // Until we reach throttling we should return true.
    if (i < CompositorFrameSinkSupport::kUndrawnFrameLimit) {
      EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
    } else {
      EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
    }
    // Clear the presentation feedbacks.
    UpdateBeginFrameTime(support_.get(), frame_time);
  }
  frame_time = base::TimeTicks::Now();
  EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
  UpdateBeginFrameTime(support_.get(), frame_time);

  // Drawing should unthrottle begin-frames.
  DrawAndSwapParams throttle_params;
  throttle_params.expected_display_time = base::TimeTicks::Now();
  display_->DrawAndSwap(throttle_params);
  frame_time = base::TimeTicks::Now();
  EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
  UpdateBeginFrameTime(support_.get(), frame_time);

  // Verify that throttling starts again after kUndrawnFrameLimit+1 frames.
  for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit + 1;
       ++i) {
    // This clears the presentation feedbacks.
    UpdateBeginFrameTime(support_.get(), frame_time);
    frame_time = base::TimeTicks::Now();
    EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
    UpdateBeginFrameTime(support_.get(), frame_time);
    submit_frame();
    // Until we reach throttling we should return true.
    if (i < CompositorFrameSinkSupport::kUndrawnFrameLimit) {
      EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
    } else {
      EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
    }
    // Clear the presentation feedbacks.
    UpdateBeginFrameTime(support_.get(), frame_time);
  }
  frame_time = base::TimeTicks::Now();
  EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
  UpdateBeginFrameTime(support_.get(), frame_time);

  // Instead of doing a draw, forward time by ~1 seconds. That should unthrottle
  // the begin-frame.
  frame_time += base::Seconds(1.1);
  EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
}

TEST_F(DisplayTest, BeginFrameThrottlingMultipleSurfaces) {
  base::test::ScopedFeatureList scoped_features;
  scoped_features.InitAndDisableFeature(features::kNoCompositorFrameAcks);
  id_allocator_.GenerateId();
  SetUpGpuDisplay(RendererSettings());
  display_->Initialize(client_.get(), manager_.surface_manager());
  display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
  support_->SetNeedsBeginFrame(true);

  // Helper fn to submit a CF.
  auto submit_frame = [this]() {
    CompositorRenderPassList pass_list;
    auto pass = CompositorRenderPass::Create();
    pass->output_rect = gfx::Rect(0, 0, 100, 100);
    pass->damage_rect = gfx::Rect(10, 10, 1, 1);
    pass->id = CompositorRenderPassId{1u};
    pass_list.push_back(std::move(pass));

    SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
  };

  // Submit kUndrawnFrameLimit frames. BeginFrames should be throttled only
  // after the last frame.
  base::TimeTicks frame_time;
  for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit + 1;
       ++i) {
    frame_time = base::TimeTicks::Now();
    EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
    UpdateBeginFrameTime(support_.get(), frame_time);
    submit_frame();
    // Generate a new LocalSurfaceId for the next submission.
    id_allocator_.GenerateId();
  }
  frame_time = base::TimeTicks::Now();
  EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
  UpdateBeginFrameTime(support_.get(), frame_time);

  // This only draws the first surface, so we should only be able to send one
  // more BeginFrame.
  DrawAndSwapParams throttle_multi_params1;
  throttle_multi_params1.expected_display_time = base::TimeTicks::Now();
  display_->DrawAndSwap(throttle_multi_params1);
  frame_time = base::TimeTicks::Now();
  EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
  UpdateBeginFrameTime(support_.get(), frame_time);

  // After this frame submission, we are throttled again.
  submit_frame();
  frame_time = base::TimeTicks::Now();
  EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
  UpdateBeginFrameTime(support_.get(), frame_time);

  // Now the last surface is drawn. This should unblock us to submit
  // kUndrawnFrameLimit+1 frames again.
  display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
  DrawAndSwapParams throttle_multi_params2;
  throttle_multi_params2.expected_display_time = base::TimeTicks::Now();
  display_->DrawAndSwap(throttle_multi_params2);
  id_allocator_.GenerateId();
  for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit + 1;
       ++i) {
    frame_time = base::TimeTicks::Now();
    EXPECT_TRUE(ShouldSendBeginFrame(support_.get(), frame_time));
    UpdateBeginFrameTime(support_.get(), frame_time);
    submit_frame();
    // Generate a new LocalSurfaceId for the next submission.
    id_allocator_.GenerateId();
  }
  frame_time = base::TimeTicks::Now();
  EXPECT_FALSE(ShouldSendBeginFrame(support_.get(), frame_time));
  UpdateBeginFrameTime(support_.get(), frame_time);
}

TEST_F(DisplayTest, DontThrottleWhenParentBlocked) {
  id_allocator_.GenerateId();
  SetUpGpuDisplay(RendererSettings());
  display_->Initialize(client_.get(), manager_.surface_manager());
  display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
  support_->SetNeedsBeginFrame(true);

  // Create frame sink for a sub surface.
  const LocalSurfaceId sub_local_surface_id(6,
                                            base::UnguessableToken::Create());
  const LocalSurfaceId sub_local_surface_id2(7,
                                             base::UnguessableToken::Create());
  const SurfaceId sub_surface_id2(kAnotherFrameSinkId, sub_local_surface_id2);

  MockCompositorFrameSinkClient sub_client;

  auto sub_support = std::make_unique<CompositorFrameSinkSupport>(
      &sub_client, &manager_, kAnotherFrameSinkId, false /* is_root */);
  sub_support->SetNeedsBeginFrame(true);

  // Submit kUndrawnFrameLimit+1 frames. BeginFrames should be throttled only
  // after the last frame.
  base::TimeTicks frame_time;
  for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit + 1;
       ++i) {
    frame_time = base::TimeTicks::Now();
    EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
    UpdateBeginFrameTime(sub_support.get(), frame_time);
    sub_support->SubmitCompositorFrame(sub_local_surface_id,
                                       MakeDefaultCompositorFrame());
    // Until we reach throttling we should return true.
    if (i < CompositorFrameSinkSupport::kUndrawnFrameLimit) {
      EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
    } else {
      EXPECT_FALSE(ShouldSendBeginFrame(sub_support.get(), frame_time));
    }
    // Clear the presentation feedbacks.
    UpdateBeginFrameTime(sub_support.get(), frame_time);
  }
  frame_time = base::TimeTicks::Now();
  EXPECT_FALSE(ShouldSendBeginFrame(sub_support.get(), frame_time));
  UpdateBeginFrameTime(sub_support.get(), frame_time);

  // Make the display block on |sub_local_surface_id2|.
  CompositorFrame frame =
      CompositorFrameBuilder()
          .AddDefaultRenderPass()
          .SetActivationDependencies({sub_surface_id2})
          .SetDeadline(FrameDeadline(base::TimeTicks::Now(),
                                     std::numeric_limits<uint32_t>::max(),
                                     base::Seconds(1), false))
          .Build();
  support_->SubmitCompositorFrame(id_allocator_.GetCurrentLocalSurfaceId(),
                                  std::move(frame));

  for (uint32_t i = 0; i < CompositorFrameSinkSupport::kUndrawnFrameLimit * 3;
       ++i) {
    frame_time = base::TimeTicks::Now();
    EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
    UpdateBeginFrameTime(sub_support.get(), frame_time);
    sub_support->SubmitCompositorFrame(sub_local_surface_id,
                                       MakeDefaultCompositorFrame());
    // Immediately after submitting frame, because there is presentation
    // feedback queued up, ShouldSendBeginFrame should always return true.
    EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
    // Clear the presentation feedbacks.
    UpdateBeginFrameTime(sub_support.get(), frame_time);
  }

  // Now submit to |sub_local_surface_id2|. This should unblock the parent and
  // throttling will resume.
  frame_time = base::TimeTicks::Now();
  EXPECT_TRUE(ShouldSendBeginFrame(sub_support.get(), frame_time));
  UpdateBeginFrameTime(sub_support.get(), frame_time);
  sub_support->SubmitCompositorFrame(sub_local_surface_id2,
                                     MakeDefaultCompositorFrame());
  frame_time = base::TimeTicks::Now();
  EXPECT_FALSE(ShouldSendBeginFrame(sub_support.get(), frame_time));
  UpdateBeginFrameTime(sub_support.get(), frame_time);
}

TEST_F(DisplayTest, DisplayTransformHint) {
  SetUpSoftwareDisplay(RendererSettings());
  display_->Initialize(client_.get(), manager_.surface_manager());

  id_allocator_.GenerateId();
  LocalSurfaceId local_surface_id(id_allocator_.GetCurrentLocalSurfaceId());
  display_->SetLocalSurfaceId(local_surface_id, 1.f);

  constexpr gfx::Size kSize = gfx::Size(100, 80);
  constexpr gfx::Size kTransposedSize =
      gfx::Size(kSize.height(), kSize.width());

  display_->Resize(kSize);

  const struct {
    bool support_display_transform;
    gfx::OverlayTransform display_transform_hint;
    gfx::Size expected_size;
  } kTestCases[] = {
      // Output size is always the display size when output surface does not
      // support display transform hint.
      {false, gfx::OVERLAY_TRANSFORM_NONE, kSize},
      {false, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90, kSize},
      {false, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180, kSize},
      {false, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270, kSize},

      // Output size is transposed on 90/270 degree rotation when output surface
      // supports display transform hint.
      {true, gfx::OVERLAY_TRANSFORM_NONE, kSize},
      {true, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_90, kTransposedSize},
      {true, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_180, kSize},
      {true, gfx::OVERLAY_TRANSFORM_ROTATE_CLOCKWISE_270, kTransposedSize},
  };

  size_t expected_frame_sent = 0u;
  for (const auto& test : kTestCases) {
    SCOPED_TRACE(testing::Message()
                 << "support_display_transform="
                 << test.support_display_transform
                 << ", display_transform_hint=" << test.display_transform_hint);

    output_surface_->set_support_display_transform_hint(
        test.support_display_transform);

    constexpr gfx::Rect kOutputRect(gfx::Point(0, 0), kSize);
    constexpr gfx::Rect kDamageRect(10, 10, 1, 1);
    CompositorFrame frame = CompositorFrameBuilder()
                                .AddRenderPass(kOutputRect, kDamageRect)
                                .Build();
    frame.metadata.display_transform_hint = test.display_transform_hint;
    support_->SubmitCompositorFrame(local_surface_id, std::move(frame));

    DrawAndSwapParams transform_params;
    transform_params.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(transform_params);
    EXPECT_EQ(++expected_frame_sent, output_surface_->num_sent_frames());
    EXPECT_EQ(test.expected_size,
              software_output_device_->viewport_pixel_size());
  }
}

TEST_F(DisplayTest, DisplaySizeMismatch) {
  RendererSettings settings;
  settings.partial_swap_enabled = true;
  settings.auto_resize_output_surface = false;
  SetUpSoftwareDisplay(settings);
  display_->Initialize(client_.get(), manager_.surface_manager());

  id_allocator_.GenerateId();
  display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
  display_->Resize(gfx::Size(100, 100));

  // Pass has copy output request but wrong size so it should be drawn, but not
  // swapped.
  {
    auto pass = CompositorRenderPass::Create();
    pass->output_rect = gfx::Rect(0, 0, 99, 99);
    pass->damage_rect = gfx::Rect(10, 10, 0, 0);
    base::RunLoop copy_run_loop;
    bool copy_called = false;
    pass->copy_requests.push_back(std::make_unique<CopyOutputRequest>(
        CopyOutputRequest::ResultFormat::RGBA,
        CopyOutputRequest::ResultDestination::kSystemMemory,
        base::BindOnce(&CopyCallback, &copy_called,
                       copy_run_loop.QuitClosure())));
    pass->id = CompositorRenderPassId{1u};

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

    SubmitCompositorFrame(&pass_list, id_allocator_.GetCurrentLocalSurfaceId());
    EXPECT_TRUE(scheduler_->damaged());

    DrawAndSwapParams mismatch_params;
    mismatch_params.expected_display_time = base::TimeTicks::Now();
    display_->DrawAndSwap(mismatch_params);

    copy_run_loop.Run();

    // Expect no swap happen
    EXPECT_EQ(0u, output_surface_->num_sent_frames());

    // Expect draw and copy output request happen
    EXPECT_TRUE(copy_called);

    // Expect there is no pending
    EXPECT_EQ(pending_presentation_group_timings_size(), 0u);
  }
}

TEST_F(DisplayTest, PixelMovingForegroundFilterTest) {
  RendererSettings settings;
  settings.partial_swap_enabled = true;
  id_allocator_.GenerateId();
  const LocalSurfaceId local_surface_id(
      id_allocator_.GetCurrentLocalSurfaceId());

  // Set up first display.
  SetUpSoftwareDisplay(settings);
  display_->Initialize(client_.get(), manager_.surface_manager());
  display_->SetLocalSurfaceId(local_surface_id, 1.f);

  // Create frame sink for a sub surface.
  TestSurfaceIdAllocator sub_surface_id1(kAnotherFrameSinkId);
  auto sub_support1 = std::make_unique<CompositorFrameSinkSupport>(
      nullptr, &manager_, kAnotherFrameSinkId, /*is_root=*/false);

  // Create frame sink for another sub surface.
  TestSurfaceIdAllocator sub_surface_id2(kAnotherFrameSinkId2);
  auto sub_support2 = std::make_unique<CompositorFrameSinkSupport>(
      nullptr, &manager_, kAnotherFrameSinkId2, /*is_root=*/false);

  // Main surface M, damage D, sub-surface B with foreground filter.
  //   +-----------+
  //   | +----+   M|
  //   | |B +-|-+  |
  //   | +--|-+ |  |
  //   |    |  D|  |
  //   |    +---+  |
  //   +-----------+
  const gfx::Size display_size(100, 100);
  const gfx::Rect isolated_damage_rect(20, 20, 40, 40);
  display_->Resize(display_size);
  const gfx::Rect sub_surface_rect(5, 5, 25, 25);
  const gfx::Rect no_damage;

  // By this time, the compositor should have already calculated the damage rect
  // taking into consideration foreground filters. Therefore, to correctly
  // simulate the compositor's behavior, we need to calculate the damage rect
  // while taking the foreground filters into account so that no damage
  // expansion occurs during surface aggregation.
  cc::FilterOperation foreground_filter =
      cc::FilterOperation::CreateDropShadowFilter(gfx::Point(5, 10), 2.f,
                                                  SkColors::kTransparent);
  gfx::Rect expanded_filter_rect = foreground_filter.MapRect(sub_surface_rect);
  gfx::Rect damage_rect = isolated_damage_rect;
  ASSERT_TRUE(expanded_filter_rect.Intersects(damage_rect));
  damage_rect.Union(expanded_filter_rect);

  CompositorRenderPassId::Generator render_pass_id_generator;
  for (size_t frame_num = 1; frame_num <= 2; ++frame_num) {
    bool first_frame = frame_num == 1;
    ResetDamageForTest();
    {
      // To-be-filtered Sub-surface.
      CompositorRenderPassList pass_list;
      auto bd_pass = CompositorRenderPass::Create();
      bd_pass->SetNew(render_pass_id_generator.GenerateNextId(),
                      sub_surface_rect, no_damage, gfx::Transform());
      pass_list.push_back(std::move(bd_pass));

      CompositorFrame frame = CompositorFrameBuilder()
                                  .SetRenderPassList(std::move(pass_list))
                                  .Build();
      sub_support1->SubmitCompositorFrame(sub_surface_id1.local_surface_id(),
                                          std::move(frame));
    }

    {
      // Sub-surface with damage.
      CompositorRenderPassList pass_list;
      auto other_pass = CompositorRenderPass::Create();
      other_pass->output_rect = gfx::Rect(display_size);
      other_pass->damage_rect = damage_rect;
      other_pass->id = render_pass_id_generator.GenerateNextId();
      pass_list.push_back(std::move(other_pass));

      CompositorFrame frame = CompositorFrameBuilder()
                                  .SetRenderPassList(std::move(pass_list))
                                  .Build();
      sub_support2->SubmitCompositorFrame(sub_surface_id2.local_surface_id(),
                                          std::move(frame));
    }

    {
      // Child render pass embedding sub surface 1 via a SurfaceDrawQuad.
      auto child_pass_id = render_pass_id_generator.GenerateNextId();
      auto root_pass_id = render_pass_id_generator.GenerateNextId();

      auto frame =
          CompositorFrameBuilder()
              .AddRenderPass(RenderPassBuilder(child_pass_id, sub_surface_rect)
                                 .AddFilter(foreground_filter)
                                 .AddSurfaceQuad(sub_surface_rect,
                                                 SurfaceRange(std::nullopt,
                                                              sub_surface_id1),
                                                 {.allow_merge = false}))
              .AddRenderPass(
                  RenderPassBuilder(root_pass_id, gfx::Rect(display_size))
                      .AddRenderPassQuad(sub_surface_rect, child_pass_id)
                      .AddSurfaceQuad(
                          gfx::Rect(display_size),
                          SurfaceRange(std::nullopt, sub_surface_id2),
                          {.allow_merge = false})
                      .SetDamageRect(damage_rect))
              .Build();
      support_->SubmitCompositorFrame(local_surface_id, std::move(frame));

      scheduler_->reset_swapped_for_test();
      DrawAndSwapParams pixel_params;
      pixel_params.expected_display_time = base::TimeTicks::Now();
      display_->DrawAndSwap(pixel_params);
      EXPECT_TRUE(scheduler_->swapped());
      EXPECT_EQ(frame_num, output_surface_->num_sent_frames());
      EXPECT_EQ(display_size, software_output_device_->viewport_pixel_size());

      auto expected_damage =
          first_frame ? gfx::Rect(display_size) : damage_rect;
      EXPECT_EQ(expected_damage, software_output_device_->damage_rect());
      // The scissor rect is expanded by direct_renderer to include the
      // overlapping pixel-moving foreground filter surface.
      auto expected_scissor_rect =
          first_frame ? gfx::Rect(display_size) : gfx::Rect(4, 5, 56, 55);
      EXPECT_EQ(
          expected_scissor_rect,
          display_->renderer_for_testing()->GetLastRootScissorRectForTesting());
    }
  }
}

TEST_F(DisplayTest, CanSkipRenderPass) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::kAllowUndamagedNonrootRenderPassToSkip);

  id_allocator_.GenerateId();
  const LocalSurfaceId local_surface_id(
      id_allocator_.GetCurrentLocalSurfaceId());

  // Set up first display.
  SetUpSoftwareDisplay(RendererSettings());
  display_->Initialize(client_.get(), manager_.surface_manager());
  display_->SetLocalSurfaceId(local_surface_id, 1.f);

  // Create frame sink for a sub surface.
  TestSurfaceIdAllocator sub_surface_id1(kAnotherFrameSinkId);
  auto sub_support1 = std::make_unique<CompositorFrameSinkSupport>(
      nullptr, &manager_, kAnotherFrameSinkId, /*is_root=*/false);

  // generate render pass id for the nonroot render pass.
  CompositorRenderPassId::Generator render_pass_id_generator;
  auto id_1 = render_pass_id_generator.GenerateNextId();

  const gfx::Size display_size(100, 100);
  const gfx::Rect root_damage_rect(20, 20, 40, 40);
  display_->Resize(display_size);
  const gfx::Rect sub_surface_rect(5, 5, 60, 60);
  const gfx::Rect sub_surface_damage_rect(10, 10, 30, 30);

  for (size_t frame_num = 1; frame_num <= 3; ++frame_num) {
    ResetDamageForTest();

    // Nonroot render pass with id_1. No update for frame #3.
    if (frame_num != 3) {
      CompositorRenderPassList pass_list;
      auto bd_pass = CompositorRenderPass::Create();
      bd_pass->output_rect = sub_surface_rect;
      bd_pass->damage_rect = sub_surface_damage_rect;
      bd_pass->has_damage_from_contributing_content = true;
      bd_pass->id = id_1;
      pass_list.push_back(std::move(bd_pass));

      CompositorFrame frame = CompositorFrameBuilder()
                                  .SetRenderPassList(std::move(pass_list))
                                  .Build();

      sub_support1->SubmitCompositorFrame(sub_surface_id1.local_surface_id(),
                                          std::move(frame));
    }

    // Root render pass
    {
      auto frame =
          CompositorFrameBuilder()
              .AddRenderPass(RenderPassBuilder(display_size)
                                 .AddSurfaceQuad(sub_surface_rect,
                                                 SurfaceRange(std::nullopt,
                                                              sub_surface_id1),
                                                 {.allow_merge = false})
                                 .SetDamageRect(root_damage_rect))
              .Build();
      support_->SubmitCompositorFrame(local_surface_id, std::move(frame));

      scheduler_->reset_swapped_for_test();
      DrawAndSwapParams skip_params;
      skip_params.expected_display_time = base::TimeTicks::Now();
      display_->DrawAndSwap(skip_params);
      EXPECT_TRUE(scheduler_->swapped());

      // Number of skipped non-root render passes.
      auto* skipped = display_->renderer_for_testing()
                          ->GetLastSkippedRenderPassIdsForTesting();

      if (frame_num != 3) {
        // Whether the render pass can be skpped or not depends on the flag
        // pass->has_damage_from_contributing_content and the render pass
        // damage rect.
        EXPECT_EQ(0u, skipped->size());
      } else {
        // No frame update for the sub surface. The nonroot render pass damage
        // rect will be zero. pass->has_damage_from_contributing_content becomes
        // false when there is no frame update. The associated non-render pass
        // can be skipped.
        EXPECT_EQ(1u, skipped->size());
      }
    }
  }
}

class SkiaDelegatedInkRendererTest : public DisplayTest {
 public:
  ~SkiaDelegatedInkRendererTest() override {
    // Reset `client_` in `display_` to avoid accessing DisplayClient after
    // `client_` is destructed. Without this, `display_` which is declared in
    // DisplayTest class is destructed after `client_` which is declared in this
    // class.
    display_->ResetDisplayClientForTesting(&client_);
  }

  void SetUpRenderers() {
    SetUpGpuDisplay(RendererSettings());

    // Initialize the renderer and create an ink renderer.
    display_->Initialize(&client_, manager_.surface_manager());

    auto renderer = std::make_unique<DelegatedInkPointRendererSkiaForTest>();
    ink_renderer_ = renderer.get();
    display_->renderer_for_testing()->SetDelegatedInkPointRendererSkiaForTest(
        std::move(renderer));
  }

  DelegatedInkPointRendererBase* ink_renderer() {
    return display_->renderer_for_testing()
        ->GetDelegatedInkPointRenderer(/*create_if_necessary=*/
                                       false);
  }

  int UniqueStoredPointerIds() {
    return ink_renderer()->GetPointsMapForTest().size();
  }

  int StoredPointsForPointerId(int32_t pointer_id) {
    return GetPointsForPointerId(pointer_id).size();
  }

  const std::map<base::TimeTicks, gfx::DelegatedInkPoint>&
  GetPointsForPointerId(int32_t pointer_id) {
    DCHECK(ink_renderer()->GetPointsMapForTest().find(pointer_id) !=
           ink_renderer()->GetPointsMapForTest().end());
    return ink_renderer()
        ->GetPointsMapForTest()
        .find(pointer_id)
        ->second.GetPoints();
  }

  void CreateAndStoreDelegatedInkPoint(const gfx::PointF& point,
                                       base::TimeTicks timestamp,
                                       int32_t pointer_id) {
    ink_points_[pointer_id].emplace_back(point, timestamp, pointer_id);
    ink_renderer()->StoreDelegatedInkPoint(ink_points_[pointer_id].back());
  }

  void CreateAndStoreDelegatedInkPointFromPreviousPoint(int32_t pointer_id) {
    DCHECK(ink_points_.find(pointer_id) != ink_points_.end());

    gfx::PointF point(ink_points_[pointer_id].back().point());
    point.Offset(10, 10);

    base::TimeTicks timestamp = ink_points_[pointer_id].back().timestamp();
    timestamp += base::Milliseconds(5);

    CreateAndStoreDelegatedInkPoint(point, timestamp, pointer_id);
  }

  void StoreAlreadyCreatedDelegatedInkPoints() {
    DCHECK_EQ(ink_points_.size(), 1u);
    StoreAlreadyCreatedDelegatedInkPoints(ink_points_.begin()->first);
  }

  void StoreAlreadyCreatedDelegatedInkPoints(int32_t pointer_id) {
    DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
    for (gfx::DelegatedInkPoint ink_point : ink_points_[pointer_id])
      ink_renderer()->StoreDelegatedInkPoint(ink_point);
  }

  void SendMetadata(gfx::DelegatedInkMetadata metadata) {
    ink_renderer()->SetDelegatedInkMetadata(
        std::make_unique<gfx::DelegatedInkMetadata>(metadata));
  }

  gfx::DelegatedInkMetadata MakeAndSendMetadataFromStoredInkPoint(
      int index,
      float diameter,
      SkColor4f color,
      const gfx::RectF& presentation_area) {
    DCHECK_EQ(ink_points_.size(), 1u);
    return MakeAndSendMetadataFromStoredInkPoint(
        ink_points_.begin()->first, index, diameter, color, presentation_area);
  }

  gfx::DelegatedInkMetadata MakeAndSendMetadataFromStoredInkPoint(
      int32_t pointer_id,
      int index,
      float diameter,
      SkColor4f color,
      const gfx::RectF& presentation_area) {
    DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
    EXPECT_GE(index, 0);
    EXPECT_LT(index, ink_points_size(pointer_id));

    // TODO(crbug.com/40219248): gfx::DelegatedInkMetadata to SkColor4f
    gfx::DelegatedInkMetadata metadata(
        ink_points_[pointer_id][index].point(), diameter, color.toSkColor(),
        ink_points_[pointer_id][index].timestamp(), presentation_area,
        base::TimeTicks::Now(),
        /*hovering*/ false, /*render_pass_id=*/0);
    SendMetadata(metadata);
    return metadata;
  }

  void HistogramCheck(const base::HistogramTester& histograms,
                      base::TimeDelta expected_bucket,
                      const char* histogram_name) {
    if (expected_bucket == base::TimeDelta::Min()) {
      histograms.ExpectTotalCount(histogram_name, 0);
    } else {
      histograms.ExpectTotalCount(histogram_name, 1);
      histograms.ExpectTimeBucketCount(histogram_name, expected_bucket, 1);
    }
  }

  // Either bucket containing base::TimeDelta::Min() is interpreted to mean that
  // expected total count of the histogram should be 0.
  void FinalizePathAndCheckHistograms(
      base::TimeDelta expected_bucket_without_prediction,
      base::TimeDelta expected_bucket_with_prediction) {
    base::HistogramTester histograms;
    ink_renderer()->FinalizePathForDraw();
    HistogramCheck(
        histograms, expected_bucket_without_prediction,
        "Renderer.DelegatedInkTrail.LatencyImprovement.Skia.WithoutPrediction");
    HistogramCheck(
        histograms, expected_bucket_with_prediction,
        "Renderer.DelegatedInkTrail.LatencyImprovement.Skia.WithPrediction");
  }

  void DrawDelegatedInkTrail() {
    SkCanvas canvas;
    static_cast<DelegatedInkPointRendererSkia*>(ink_renderer())
        ->DrawDelegatedInkTrail(&canvas, gfx::Transform());
  }

  int GetPathPointCount() { return ink_renderer()->GetPathPointCountForTest(); }

  // Explicitly get the metadata that is stored on the renderer.
  const gfx::DelegatedInkMetadata* GetMetadataFromRenderer() {
    return ink_renderer()->GetMetadataForTest();
  }

  const gfx::DelegatedInkPoint& ink_point(int index) {
    DCHECK_EQ(ink_points_.size(), 1u);
    return ink_point(ink_points_.begin()->first, index);
  }

  const gfx::DelegatedInkPoint& ink_point(int32_t pointer_id, int index) {
    DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
    EXPECT_GE(index, 0);
    EXPECT_LT(index, ink_points_size(pointer_id));
    return ink_points_[pointer_id][index];
  }

  const gfx::DelegatedInkPoint& last_ink_point(int32_t pointer_id) {
    DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
    return ink_points_[pointer_id].back();
  }

  int ink_points_size() {
    DCHECK_EQ(ink_points_.size(), 1u);
    return ink_points_.begin()->second.size();
  }

  int ink_points_size(int32_t pointer_id) {
    DCHECK(ink_points_.find(pointer_id) != ink_points_.end());
    return ink_points_[pointer_id].size();
  }

  int points_to_predict() const { return kPointsToPredict; }

  const base::TimeDelta time_into_the_future() const {
    return base::Milliseconds(
        (kMillisecondsIntoFuturePerPoint - kResampleLatency) *
        kPointsToPredict);
  }

 protected:
  raw_ptr<DelegatedInkPointRendererSkiaForTest> ink_renderer_ = nullptr;

  // Stub client kept in scope to prevent access violations during DrawAndSwap.
  StubDisplayClient client_;

  base::test::ScopedFeatureList feature_list_;

 private:
  std::unordered_map<int32_t, std::vector<gfx::DelegatedInkPoint>> ink_points_;

  // Values used to configure the points predictor. Needs to match the values
  // in `DelegatedInkTrailData`;
  static const int kPointsToPredict = 2;
  static const int kMillisecondsIntoFuturePerPoint = 6;
  static const int kResampleLatency = 5;
};

// Testing filtering points in the the delegated ink renderer when the skia
// renderer is in use.
TEST_F(SkiaDelegatedInkRendererTest, SkiaDelegatedInkRendererFilteringPoints) {
  SetUpRenderers();

  // First, a sanity check.
  EXPECT_EQ(0, UniqueStoredPointerIds());

  // Insert 3 arbitrary points into the ink renderer to confirm that they go
  // where we expect and are all stored correctly.
  const int kInitialDelegatedPoints = 3;
  base::TimeTicks timestamp = base::TimeTicks::Now();
  gfx::PointF point(10, 10);
  const int32_t kPointerId = std::numeric_limits<int32_t>::max();
  CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
  for (int i = 1; i < kInitialDelegatedPoints; ++i)
    CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerId);

  // They all have the same pointer ID, so there should be exactly one unique
  // element in the map, and that element should itself have all three points.
  EXPECT_EQ(1, UniqueStoredPointerIds());
  EXPECT_EQ(kInitialDelegatedPoints, StoredPointsForPointerId(kPointerId));

  // Now provide metadata with a timestamp matching one of the points to
  // confirm that earlier points are removed and later points remain.
  const int kInkPointForMetadata = 1;
  const float kDiameter = 1.f;
  gfx::DelegatedInkMetadata metadata = MakeAndSendMetadataFromStoredInkPoint(
      kInkPointForMetadata, kDiameter, SkColors::kBlack, gfx::RectF());

  // The histogram should count one in the bucket that is the difference between
  // the latest point stored and the metadata. The *WithoutPrediction histogram
  // should count the difference between the last point and the metadata, while
  // the *WithPrediction* histogram should count 1 in the 7ms bucket because
  // prediction can occer with linear resampling and 2 input points.
  base::TimeDelta bucket_without_prediction =
      last_ink_point(kPointerId).timestamp() - metadata.timestamp();
  FinalizePathAndCheckHistograms(bucket_without_prediction,
                                 base::Milliseconds(7));

  EXPECT_EQ(kInitialDelegatedPoints - kInkPointForMetadata,
            StoredPointsForPointerId(kPointerId));
  EXPECT_EQ(metadata.point(),
            GetPointsForPointerId(kPointerId).begin()->second.point());
  EXPECT_EQ(last_ink_point(kPointerId).point(),
            GetPointsForPointerId(kPointerId).rbegin()->second.point());
  EXPECT_EQ(ink_point(0).pointer_id(), kPointerId);

  // Confirm that the metadata is cleared when DrawDelegatedInkTrail() is
  // called.
  DrawDelegatedInkTrail();
  EXPECT_FALSE(GetMetadataFromRenderer());

  // Add more points than the maximum that will be stored to confirm only the
  // max is stored and the correct ones are removed first.
  const int kPointsBeyondMaxAllowed = 2;
  StoreAlreadyCreatedDelegatedInkPoints();
  while (ink_points_size() <
         gfx::kMaximumNumberOfDelegatedInkPoints + kPointsBeyondMaxAllowed)
    CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerId);

  EXPECT_EQ(gfx::kMaximumNumberOfDelegatedInkPoints,
            StoredPointsForPointerId(kPointerId));
  EXPECT_EQ(ink_point(kPointsBeyondMaxAllowed).point(),
            GetPointsForPointerId(kPointerId).begin()->second.point());
  EXPECT_EQ(last_ink_point(kPointerId).point(),
            GetPointsForPointerId(kPointerId).rbegin()->second.point());
  EXPECT_EQ(last_ink_point(kPointerId).pointer_id(), kPointerId);

  // Now send metadata with a timestamp before all of the points that are
  // currently stored to confirm that no points are filtered out and the number
  // stored remains the same. The *WithoutPrediction histogram should record 0
  // improvement, but the *WithPrediction* one should not record anything at all
  // due to not finding a matching pointer ID to predict with.
  const int kExpectedPoints = StoredPointsForPointerId(kPointerId);
  SendMetadata(metadata);
  FinalizePathAndCheckHistograms(base::Milliseconds(0), base::TimeDelta::Min());
  EXPECT_EQ(kExpectedPoints, StoredPointsForPointerId(kPointerId));
}

// Test filtering when points arrive with several different pointer IDs.
TEST_F(SkiaDelegatedInkRendererTest,
       SkiaDelegatedInkRendererFilteringPointsWithMultiplePointerIds) {
  SetUpRenderers();

  // Unique pointer IDs used - numbers arbitrary.
  const std::vector<int32_t> kPointerIds = {1, 20, 300};

  // First add just one DelegatedInkPoint for each pointer id to confirm that
  // they all get stored separately.
  base::TimeTicks timestamp = base::TimeTicks::Now();
  for (uint64_t i = 0; i < kPointerIds.size(); ++i) {
    // Make sure that each pointer id has slightly different points so that when
    // new points are added later that are based on previous points, it doesn't
    // result in multiple pointer ids having identical DelegatedInkPoints
    CreateAndStoreDelegatedInkPoint(gfx::PointF(i * 5, i * 10), timestamp,
                                    kPointerIds[i]);
    timestamp += base::Milliseconds(5);
  }

  EXPECT_EQ(static_cast<int>(kPointerIds.size()), UniqueStoredPointerIds());
  for (int32_t pointer_id : kPointerIds)
    EXPECT_EQ(1, StoredPointsForPointerId(pointer_id));

  // Add more points so that the first pointer ID contains 4 DelegatedInkPoints,
  // and the third pointer id contains 2 DelegatedInkPoints
  const int kNumPointsForPointerId0 = 4;
  while (ink_points_size(kPointerIds[0]) < kNumPointsForPointerId0)
    CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerIds[0]);
  CreateAndStoreDelegatedInkPointFromPreviousPoint(kPointerIds[2]);

  // Confirm all the points got stored where they should have been.
  for (int32_t pointer_id : kPointerIds) {
    EXPECT_EQ(ink_points_size(pointer_id),
              StoredPointsForPointerId(pointer_id));
  }

  // Now provide metadata with a timestamp matching one of the points in the
  // first pointer id bucket to confirm that earlier points are removed and
  // later points remain.
  const int kInkPointForMetadata = 1;
  const float kDiameter = 1.f;
  gfx::DelegatedInkMetadata metadata = MakeAndSendMetadataFromStoredInkPoint(
      kPointerIds[0], kInkPointForMetadata, kDiameter, SkColors::kBlack,
      gfx::RectF());

  // 3 points should be enough for prediction to work, so the histogram should
  // have one in the *WithoutPrediction bucket that matches the difference
  // between the metadata and the final point, and one in the *WithPrediction
  // bucket that matches the amount of prediction that is being done (plus the
  // difference between the final point and the metadata).
  base::TimeDelta bucket_without_prediction =
      last_ink_point(kPointerIds[0]).timestamp() - metadata.timestamp();
  FinalizePathAndCheckHistograms(
      bucket_without_prediction,
      bucket_without_prediction + time_into_the_future());

  // Confirm the size, first, and last points of the first pointer ID are what
  // we expect.
  EXPECT_EQ(kNumPointsForPointerId0 - kInkPointForMetadata,
            StoredPointsForPointerId(kPointerIds[0]));
  EXPECT_EQ(metadata.point(),
            GetPointsForPointerId(kPointerIds[0]).begin()->second.point());
  EXPECT_EQ(last_ink_point(kPointerIds[0]).point(),
            GetPointsForPointerId(kPointerIds[0]).rbegin()->second.point());

  // Confirm that neither of the other pointer ids were impacted.
  for (uint64_t i = 1; i < kPointerIds.size(); ++i) {
    EXPECT_EQ(ink_points_size(kPointerIds[i]),
              StoredPointsForPointerId(kPointerIds[i]));
  }

  // Send a metadata whose point and timestamp doesn't match any stored
  // DelegatedInkPoint and confirm that it doesn't cause any changes to the
  // stored values. *WithoutPrediction histogram should record 0 improvement,
  // *WithPrediction* shouldn't record anything due to no valid pointer id.
  SendMetadata(gfx::DelegatedInkMetadata(
      gfx::PointF(100, 100), 5.6f, SK_ColorBLACK, base::TimeTicks::Min(),
      gfx::RectF(), base::TimeTicks::Min(), /*hovering*/ false,
      /*render_pass_id=*/0));
  FinalizePathAndCheckHistograms(base::Milliseconds(0), base::TimeDelta::Min());
  EXPECT_EQ(kNumPointsForPointerId0 - kInkPointForMetadata,
            StoredPointsForPointerId(kPointerIds[0]));
  for (uint64_t i = 1; i < kPointerIds.size(); ++i) {
    EXPECT_EQ(ink_points_size(kPointerIds[i]),
              StoredPointsForPointerId(kPointerIds[i]));
  }

  // Finally, send a metadata with a timestamp beyond all of the stored points.
  // This should result in all of the points being erased, but the pointer ids
  // will still exist as they contains the predictors as well.
  SendMetadata(gfx::DelegatedInkMetadata(
      gfx::PointF(100, 100), 5.6f, SK_ColorBLACK,
      base::TimeTicks::Now() + base::Milliseconds(1000), gfx::RectF(),
      base::TimeTicks::Now(), /*hovering*/ false, /*render_pass_id=*/0));
  FinalizePathAndCheckHistograms(base::Milliseconds(0), base::TimeDelta::Min());
  for (int i : kPointerIds)
    EXPECT_EQ(0, StoredPointsForPointerId(i));
}

// Confirm that the delegated ink trail histograms record latency correctly.
TEST_F(SkiaDelegatedInkRendererTest, LatencyHistograms) {
  SetUpRenderers();

  // Confirm that nothing is counted in the histograms when there is no metadata
  // or points to draw.
  FinalizePathAndCheckHistograms(base::TimeDelta::Min(),
                                 base::TimeDelta::Min());

  // Insert 4 arbitrary points into the ink renderer to later draw.
  base::TimeTicks timestamp = base::TimeTicks::Now();
  const int32_t kPointerId = 17;
  CreateAndStoreDelegatedInkPoint(gfx::PointF(20, 19), timestamp, kPointerId);
  CreateAndStoreDelegatedInkPoint(
      gfx::PointF(15, 19), timestamp + base::Milliseconds(8), kPointerId);
  CreateAndStoreDelegatedInkPoint(
      gfx::PointF(16, 28), timestamp + base::Milliseconds(16), kPointerId);
  CreateAndStoreDelegatedInkPoint(
      gfx::PointF(29, 35), timestamp + base::Milliseconds(24), kPointerId);

  // Provide a metadata so that points can be drawn, based on the first ink
  // point that was sent.
  const float kDiameter = 11.99f;
  MakeAndSendMetadataFromStoredInkPoint(/*index*/ 0, kDiameter,
                                        SkColors::kBlack, gfx::RectF());

  // *WithoutPrediction histogram should have one counted in the 24 ms bucket
  // because that's the difference between the latest point and the metadata.
  // *WithPrediction should be able to predict here, so it should contain 1 in
  // the bucket that is |kNumberOfMillisecondsIntoFutureToPredictPerPoint| *
  // |kNumberOfPointsToPredict| into the future from 24 ms bucket.
  base::TimeDelta bucket_without_prediction = base::Milliseconds(24);
  FinalizePathAndCheckHistograms(
      bucket_without_prediction,
      bucket_without_prediction + time_into_the_future());

  // Now provide metadata that matches the final ink point provided, so that
  // everything earlier is filtered out. Then the *WithoutPrediction histogram
  // will count 1 in the 0 ms bucket and the *WithPrediction histogram will
  // still be able to predict points, so it should have counted one.
  MakeAndSendMetadataFromStoredInkPoint(/*index*/ 3, kDiameter,
                                        SkColors::kBlack, gfx::RectF());
  bucket_without_prediction = base::Milliseconds(0);
  FinalizePathAndCheckHistograms(bucket_without_prediction,
                                 time_into_the_future());

  // DrawDelegatedInkTrail should clear the metadata, so finalizing the path
  // shouldn't record anything in the histograms.
  DrawDelegatedInkTrail();
  FinalizePathAndCheckHistograms(base::TimeDelta::Min(),
                                 base::TimeDelta::Min());

  // Send a few more points but no metadata to confirm that nothing is counted.
  timestamp = base::TimeTicks::Now();
  CreateAndStoreDelegatedInkPoint(gfx::PointF(85, 56), timestamp, kPointerId);
  CreateAndStoreDelegatedInkPoint(
      gfx::PointF(96, 70), timestamp + base::Milliseconds(2), kPointerId);
  CreateAndStoreDelegatedInkPoint(
      gfx::PointF(112, 94), timestamp + base::Milliseconds(10), kPointerId);
  FinalizePathAndCheckHistograms(base::TimeDelta::Min(),
                                 base::TimeDelta::Min());
}

// Confirm that a delegated ink trail will still be drawn if the point and
// metadata are close enough.
TEST_F(SkiaDelegatedInkRendererTest, DrawTrailWhenMetadataIsCloseEnough) {
  SetUpRenderers();

  // Insert 3 points, then create a metadata that is not exactly the same as
  // the first point, but within DelegatedInkPointRendererBase::kEpsilon of
  // the point so that a trail is drawn.
  base::TimeTicks timestamp = base::TimeTicks::Now();
  base::TimeTicks timestamp2 = timestamp + base::Milliseconds(8);
  gfx::PointF point(45.f, 78.f);
  gfx::PointF point2(68.f, 89.f);
  const int32_t kPointerId = 17;
  CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
  CreateAndStoreDelegatedInkPoint(point2, timestamp2, kPointerId);
  CreateAndStoreDelegatedInkPoint(
      gfx::PointF(80.f, 70.f), timestamp2 + base::Milliseconds(8), kPointerId);

  gfx::DelegatedInkMetadata metadata(
      gfx::PointF(point.x() - 1.0f, point.y() - 1.0f), 45.f, SK_ColorBLACK,
      timestamp, gfx::RectF(0, 0, 100, 100), base::TimeTicks::Now(),
      /*hovering*/ false, /*render_pass_id=*/0);
  SendMetadata(metadata);

  // If the metadata was close enough, then a trail should be drawn with all
  // three points.
  ink_renderer()->FinalizePathForDraw();
  EXPECT_EQ(GetPathPointCount(), 3 + points_to_predict());

  // Now send a metadata with a point that is slightly further away from the
  // second point, such that the distance between them is greater than the
  // kEpsilon value to confirm that if it gets too far away we won't use it for
  // drawing.
  metadata = gfx::DelegatedInkMetadata(
      gfx::PointF(point2.x() - 1.01f, point2.y() - 1.0f), 45.f, SK_ColorBLACK,
      timestamp2, gfx::RectF(0, 0, 100, 100), base::TimeTicks::Now(),
      /*hovering*/ false, /*render_pass_id=*/0);
  SendMetadata(metadata);

  ink_renderer()->FinalizePathForDraw();
  EXPECT_EQ(GetPathPointCount(), 0);
}

// Tests that the OutstandingPointsToDraw histogram is fired correctly.
TEST_F(SkiaDelegatedInkRendererTest, SkiaDelegatedInkOutstandingPointsToDraw) {
  const std::string kHistogramName =
      "Renderer.DelegatedInkTrail.Skia.OutstandingPointsToDraw";
  const base::HistogramTester histogram_tester;
  const int32_t kPointerId = 17;
  SetUpRenderers();

  ink_renderer()->ReportPointsDrawn();
  // No histogram should be fired when there are no points to draw.
  histogram_tester.ExpectTotalCount(kHistogramName, 0);

  // Add one point, a histogram with a count of one should be fired.
  const base::TimeTicks timestamp = base::TimeTicks::Now();
  const gfx::PointF point(45.f, 78.f);
  CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
  SendMetadata(gfx::DelegatedInkMetadata(
      gfx::PointF(point.x(), point.y()), 45.f, SK_ColorBLACK, timestamp,
      gfx::RectF(0, 0, 100, 100), base::TimeTicks::Now(),
      /*hovering=*/false, /*render_pass_id=*/0));
  ink_renderer()->ReportPointsDrawn();
  histogram_tester.ExpectUniqueSample(kHistogramName, 1, 1);

  // Add two point, a histogram with a count of two and three should be fired.
  CreateAndStoreDelegatedInkPoint(point + gfx::Vector2d(1, 1),
                                  timestamp + base::Milliseconds(10),
                                  kPointerId);
  ink_renderer()->ReportPointsDrawn();
  histogram_tester.ExpectTotalCount(kHistogramName, 2);
  histogram_tester.ExpectBucketCount(kHistogramName, 2, 1);
  CreateAndStoreDelegatedInkPoint(point + gfx::Vector2d(2, 2),
                                  timestamp + base::Milliseconds(20),
                                  kPointerId);
  ink_renderer()->ReportPointsDrawn();
  histogram_tester.ExpectBucketCount(kHistogramName, 3, 1);
  histogram_tester.ExpectTotalCount(kHistogramName, 3);
}

// Tests that the TimeToDrawMillis histogram is fired correctly.
TEST_F(SkiaDelegatedInkRendererTest, SkiaDelegatedInkTimeToDrawMillis) {
  const std::string kHistogramName =
      "Renderer.DelegatedInkTrail.Skia.TimeToDrawPointsMillis";
  const base::HistogramTester histogram_tester;
  constexpr int32_t kPointerId = 1u;
  SetUpRenderers();

  ink_renderer()->ReportPointsDrawn();
  // No histogram should be fired when there are no points to draw.
  histogram_tester.ExpectTotalCount(kHistogramName, 0);

  // Add one point to the trail and ensure one histogram instance is fired.
  const base::TimeTicks timestamp = base::TimeTicks::Now();
  const gfx::PointF point(45.f, 78.f);
  CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
  SendMetadata(gfx::DelegatedInkMetadata(
      gfx::PointF(point.x(), point.y()), 45.f, SK_ColorBLACK, timestamp,
      gfx::RectF(0, 0, 100, 100), base::TimeTicks::Now(),
      /*hovering=*/false, /*render_pass_id=*/0));
  ink_renderer()->ReportPointsDrawn();
  histogram_tester.ExpectTotalCount(kHistogramName, 1);

  // Add two points to the trail and ensure that the histogram is fired three
  // times (three points, four total histogram fires accounting for the
  // previous).
  CreateAndStoreDelegatedInkPoint(point + gfx::Vector2d(1, 1),
                                  timestamp + base::Milliseconds(1),
                                  kPointerId);
  CreateAndStoreDelegatedInkPoint(point + gfx::Vector2d(2, 2),
                                  timestamp + base::Milliseconds(2),
                                  kPointerId);
  ink_renderer()->ReportPointsDrawn();
  histogram_tester.ExpectTotalCount(kHistogramName, 4);
}

TEST_F(SkiaDelegatedInkRendererTest,
       SkiaDelegatedInkTimeFromDelegatedInkToApiPaint) {
  const std::string kHistogramName =
      "Renderer.DelegatedInkTrail.Skia.TimeFromDelegatedInkToApiPaint";
  const base::HistogramTester histogram_tester;
  constexpr int32_t kPointerId = 1u;
  const auto create_metadata = [](gfx::PointF& p, base::TimeTicks& t) {
    return gfx::DelegatedInkMetadata(p, /*diameter=*/45.f, SK_ColorBLACK, t,
                                     gfx::RectF(0, 0, 100, 100), t,
                                     /*hovering=*/false, /*render_pass_id=*/0);
  };
  SetUpRenderers();

  ink_renderer()->ReportPointsDrawn();
  // No histogram should be fired when `metadata_paint_time_` is not set.
  histogram_tester.ExpectTotalCount(kHistogramName, 0);

  // Original timestamp and coordinates to be advanced for subsequent points
  // sent.
  base::TimeTicks timestamp = base::TimeTicks::Now();
  gfx::PointF point(45.f, 78.f);
  const auto advance_point = [&]() {
    timestamp += base::Milliseconds(10);
    point += gfx::Vector2d(3.f, 3.f);
  };

  // Set up a trail, create a point and a metadata and call ReportPointsDrawn.
  CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
  SendMetadata(create_metadata(point, timestamp));
  ink_renderer()->ReportPointsDrawn();
  EXPECT_NE(std::nullopt, GetPointsForPointerId(kPointerId)
                              .find(timestamp)
                              ->second.paint_timestamp());
  ink_renderer()->ReportPointsDrawn();

  // Add two delegated ink points to the trail and paint them.
  advance_point();
  CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
  advance_point();
  CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
  ink_renderer()->ReportPointsDrawn();
  // After drawing the points, all of them should have a `paint_timestamp_` set.
  for (auto& [_, p] : GetPointsForPointerId(kPointerId)) {
    EXPECT_NE(std::nullopt, p.paint_timestamp());
  }

  // Send a metadata that matches the last painted point.
  SendMetadata(create_metadata(point, timestamp));
  ink_renderer()->ReportPointsDrawn();
  histogram_tester.ExpectTotalCount(kHistogramName, 1);

  // The histogram should not be fired when the metadata has not been updated.
  ink_renderer()->ReportPointsDrawn();
  histogram_tester.ExpectTotalCount(kHistogramName, 1);
  // Send same metadata as before and report drawing. The histogram should not
  // be fired.
  SendMetadata(create_metadata(point, timestamp));
  ink_renderer()->ReportPointsDrawn();
  histogram_tester.ExpectTotalCount(kHistogramName, 1);

  // Send a new point, draw, a new metadata that matches the new point, draw
  // again and ensure that a new histogram is fired.
  advance_point();
  CreateAndStoreDelegatedInkPoint(point, timestamp, kPointerId);
  ink_renderer()->ReportPointsDrawn();
  SendMetadata(create_metadata(point, timestamp));
  ink_renderer()->ReportPointsDrawn();
  histogram_tester.ExpectTotalCount(kHistogramName, 2);
}

enum class DelegatedInkType { kPlatformInk, kSkiaInk };

class DelegatedInkDisplayTest
    : public SkiaDelegatedInkRendererTest,
      public testing::WithParamInterface<DelegatedInkType> {
 public:
  void SetUpGpuDisplaySkiaWithPlatformInk(const RendererSettings& settings) {
    scoped_refptr<TestContextProvider> provider =
        TestContextProvider::CreateGLES();
    provider->BindToCurrentSequence();
    std::unique_ptr<FakeSkiaOutputSurface> skia_output_surface =
        FakeSkiaOutputSurface::Create3d(std::move(provider));
    // Set the delegated ink capability on the output surface to true so that
    // path can be tested in Display::DrawAndSwap
    skia_output_surface->UsePlatformDelegatedInkForTesting();
    skia_output_surface_ = skia_output_surface.get();

    CreateDisplaySchedulerAndDisplay(settings, kArbitraryFrameSinkId,
                                     std::move(skia_output_surface));
  }

  void SetUpGpuDisplay() {
    if (GetParam() == DelegatedInkType::kSkiaInk) {
      SetUpRenderers();
    } else {
      // Set up the display to use the Skia renderer.
      SetUpGpuDisplaySkiaWithPlatformInk(RendererSettings());

      display_->Initialize(&client_, manager_.surface_manager());
    }
  }

  void SubmitCompositorFrameWithInkMetadata(
      CompositorRenderPassList* pass_list,
      const LocalSurfaceId& local_surface_id,
      const gfx::DelegatedInkMetadata& metadata) {
    CompositorFrame frame = CompositorFrameBuilder()
                                .SetRenderPassList(std::move(*pass_list))
                                .AddDelegatedInkMetadata(metadata)
                                .Build();
    pass_list->clear();

    support_->SubmitCompositorFrame(local_surface_id, std::move(frame));
  }

  const gfx::DelegatedInkMetadata* GetMetadataFromTestRenderer() {
    return ink_renderer_->last_metadata();
  }
};

struct DelegatedInkDisplayTestPassToString {
  std::string operator()(
      const testing::TestParamInfo<DelegatedInkType> type) const {
    return type.param == DelegatedInkType::kPlatformInk ? "PlatformInk"
                                                        : "SkiaInk";
  }
};

INSTANTIATE_TEST_SUITE_P(DelegatedInkTrails,
                         DelegatedInkDisplayTest,
                         testing::Values(DelegatedInkType::kPlatformInk,
                                         DelegatedInkType::kSkiaInk),
                         DelegatedInkDisplayTestPassToString());

// Confirm that delegated ink metadata is not ever sent to both the delegated
// ink renderer and the output surface (for platform delegated ink), only one
// or the other.
TEST_P(DelegatedInkDisplayTest, MetadataOnlySentToSkiaRendererOrOutputSurface) {
  SetUpGpuDisplay();

  id_allocator_.GenerateId();
  display_->SetLocalSurfaceId(id_allocator_.GetCurrentLocalSurfaceId(), 1.f);
  display_->Resize(gfx::Size(100, 100));

  CompositorRenderPassList pass_list;
  auto pass = CompositorRenderPass::Create();
  pass->output_rect = gfx::Rect(0, 0, 100, 100);
  pass->damage_rect = gfx::Rect(10, 10, 1, 1);
  pass->id = CompositorRenderPassId{1u};
  pass_list.push_back(std::move(pass));

  gfx::DelegatedInkMetadata metadata(
      gfx::PointF(5, 5), 3.5f, SK_ColorBLACK, base::TimeTicks::Now(),
      gfx::RectF(0, 0, 20, 20), base::TimeTicks::Now(), false,
      /*render_pass_id=*/0);

  SubmitCompositorFrameWithInkMetadata(
      &pass_list, id_allocator_.GetCurrentLocalSurfaceId(), metadata);
  DrawAndSwapParams ink_trail_params;
  ink_trail_params.expected_display_time = base::TimeTicks::Now();
  display_->DrawAndSwap(ink_trail_params);

  // Confirm that the metadata correctly made it to either the skia output
  // surface, or the delegated ink renderer.
  const gfx::DelegatedInkMetadata* retrieved_metadata =
      GetParam() == DelegatedInkType::kPlatformInk
          ? skia_output_surface_->last_delegated_ink_metadata()
          : GetMetadataFromTestRenderer();
  EXPECT_TRUE(retrieved_metadata);
  EXPECT_EQ(retrieved_metadata->point(), metadata.point());
  EXPECT_EQ(retrieved_metadata->diameter(), metadata.diameter());
  EXPECT_EQ(retrieved_metadata->color(), metadata.color());
  EXPECT_EQ(retrieved_metadata->timestamp(), metadata.timestamp());
  EXPECT_EQ(retrieved_metadata->presentation_area(),
            metadata.presentation_area());
  EXPECT_EQ(retrieved_metadata->is_hovering(), metadata.is_hovering());

  // Confirm that metadata wasn't sent to the SkiaOutputSurface if Skia was
  // used for drawing, or confirm that the DelegatedInkPointRenderer wasn't
  // created if platform ink is being used.
  if (GetParam() == DelegatedInkType::kPlatformInk)
    EXPECT_FALSE(ink_renderer());
  else
    EXPECT_FALSE(skia_output_surface_->last_delegated_ink_metadata());
}

// Check that a pending delegated ink point renderer sent to the display
// correctly goes to either the renderer or the output surface depending on if
// the platform supports delegated ink and the feature flag is enabled or not.
TEST_P(DelegatedInkDisplayTest,
       InkRendererRemoteGoesToSkiaRendererOrOutputSurface) {
  SetUpGpuDisplay();

  mojo::Remote<gfx::mojom::DelegatedInkPointRenderer> ink_renderer_remote;
  display_->InitDelegatedInkPointRendererReceiver(
      ink_renderer_remote.BindNewPipeAndPassReceiver());

  if (GetParam() == DelegatedInkType::kPlatformInk) {
    EXPECT_TRUE(skia_output_surface_
                    ->ContainsDelegatedInkPointRendererReceiverForTesting());
    EXPECT_FALSE(ink_renderer());
  } else {
    EXPECT_FALSE(skia_output_surface_
                     ->ContainsDelegatedInkPointRendererReceiverForTesting());
    EXPECT_TRUE(ink_renderer());
    EXPECT_TRUE(ink_renderer_remote.is_bound());
  }
}

using UnsupportedRendererDelegatedInkTest = DisplayTest;

// Confirm that trying to use delegated ink trails on SoftwareRenderer silently
// fails.
TEST_F(UnsupportedRendererDelegatedInkTest,
       DelegatedInkSilentlyFailsOnSoftwareRenderer) {
  SetUpSoftwareDisplay(RendererSettings());
  display_->Initialize(client_.get(), manager_.surface_manager());

  // Should silently bail early from here. Test will crash if we actually try to
  // initialize the delegated ink point renderer.
  mojo::Remote<gfx::mojom::DelegatedInkPointRenderer> ink_renderer_remote;
  display_->InitDelegatedInkPointRendererReceiver(
      ink_renderer_remote.BindNewPipeAndPassReceiver());
}

class DisplayVisibilityTest : public DisplayTest {
 public:
  DisplayVisibilityTest() = default;
};

TEST_F(DisplayVisibilityTest, DiscardBuffersOnVisibilityChangeGated) {
  if constexpr (!features::kAllowVizBufferQueueDiscardOnVisibilityChange) {
    GTEST_SKIP() << "VizBufferQueueDiscardOnVisibilityChange is not available "
                    "on this platform.";
  }

  SetUpGpuDisplay(RendererSettings());
  skia_output_surface_->SetRendererAllocatesImagesForTesting(true);
  display_->Initialize(client_.get(), manager_.surface_manager());

  gfx::Size size(100, 100);
  display_->Resize(size);

  // Submit a frame to allocate some buffers in SkiaRenderer's BufferQueue
  ParentLocalSurfaceIdAllocator allocator;
  allocator.GenerateId();
  LocalSurfaceId local_surface_id = allocator.GetCurrentLocalSurfaceId();

  CompositorRenderPassList pass_list;
  auto pass = CompositorRenderPass::Create();
  pass->SetNew(CompositorRenderPassId{1}, gfx::Rect(size), gfx::Rect(size),
               gfx::Transform());
  pass_list.push_back(std::move(pass));

  SubmitCompositorFrame(&pass_list, local_surface_id);
  display_->SetLocalSurfaceId(local_surface_id, 1.f);

  // Draw a frame to ensure resources are allocated
  DrawAndSwapParams params;
  params.expected_display_time = base::TimeTicks::Now();
  EXPECT_TRUE(display_->DrawAndSwap(params));

  // Verify we allocated some buffers (they are not destroyed yet)
  EXPECT_TRUE(skia_output_surface_->destroyed_mailboxes().empty());

  // Enabled, buffers are discarded.
  {
    base::test::ScopedFeatureList feature_list;
    feature_list.InitAndEnableFeature(
        features::kVizBufferQueueDiscardOnVisibilityChange);

    display_->SetVisible(false);
    EXPECT_FALSE(skia_output_surface_->destroyed_mailboxes().empty());
  }

  // Reset display and surface for next test
  display_->SetVisible(true);
  skia_output_surface_->clear_destroyed_mailboxes();

  // Draw again to allocate buffers again
  pass = CompositorRenderPass::Create();
  pass->SetNew(CompositorRenderPassId{1}, gfx::Rect(size), gfx::Rect(size),
               gfx::Transform());
  pass_list.push_back(std::move(pass));
  SubmitCompositorFrame(&pass_list, local_surface_id);
  EXPECT_TRUE(display_->DrawAndSwap(params));
  EXPECT_TRUE(skia_output_surface_->destroyed_mailboxes().empty());

  // Disabled, buffers are not discarded.
  {
    base::test::ScopedFeatureList feature_list;
    feature_list.InitAndDisableFeature(
        features::kVizBufferQueueDiscardOnVisibilityChange);

    display_->SetVisible(false);
    EXPECT_TRUE(skia_output_surface_->destroyed_mailboxes().empty());
  }
}

}  // namespace viz
