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

#include "components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h"

#include <memory>

#include "base/test/test_mock_time_task_runner.h"
#include "cc/test/fake_layer_tree_frame_sink_client.h"
#include "components/viz/common/display/renderer_settings.h"
#include "components/viz/common/frame_sinks/begin_frame_source.h"
#include "components/viz/common/frame_sinks/delay_based_time_source.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/service/display/display.h"
#include "components/viz/service/display/display_scheduler.h"
#include "components/viz/service/frame_sinks/compositor_frame_sink_support_manager.h"
#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
#include "components/viz/test/begin_frame_args_test.h"
#include "components/viz/test/compositor_frame_helpers.h"
#include "components/viz/test/fake_output_surface.h"
#include "components/viz/test/test_context_provider.h"
#include "components/viz/test/test_gpu_memory_buffer_manager.h"
#include "components/viz/test/test_shared_bitmap_manager.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace viz {
namespace {

static constexpr FrameSinkId kArbitraryFrameSinkId(1, 1);

class TestDirectLayerTreeFrameSink : public DirectLayerTreeFrameSink {
 public:
  using DirectLayerTreeFrameSink::DirectLayerTreeFrameSink;
};

class TestCompositorFrameSinkSupportManager
    : public CompositorFrameSinkSupportManager {
 public:
  explicit TestCompositorFrameSinkSupportManager(
      FrameSinkManagerImpl* frame_sink_manager)
      : frame_sink_manager_(frame_sink_manager) {}
  ~TestCompositorFrameSinkSupportManager() override = default;

  std::unique_ptr<CompositorFrameSinkSupport> CreateCompositorFrameSinkSupport(
      mojom::CompositorFrameSinkClient* client,
      const FrameSinkId& frame_sink_id,
      bool is_root,
      bool needs_sync_points) override {
    return std::make_unique<CompositorFrameSinkSupport>(
        client, frame_sink_manager_, frame_sink_id, is_root, needs_sync_points);
  }

 private:
  FrameSinkManagerImpl* const frame_sink_manager_;

  DISALLOW_COPY_AND_ASSIGN(TestCompositorFrameSinkSupportManager);
};

class DirectLayerTreeFrameSinkTest : public testing::Test {
 public:
  DirectLayerTreeFrameSinkTest()
      : task_runner_(base::MakeRefCounted<base::TestMockTimeTaskRunner>(
            base::TestMockTimeTaskRunner::Type::kStandalone)),
        display_size_(1920, 1080),
        display_rect_(display_size_),
        frame_sink_manager_(&bitmap_manager_),
        support_manager_(&frame_sink_manager_),
        context_provider_(TestContextProvider::Create()) {
    auto display_output_surface = FakeOutputSurface::Create3d();
    display_output_surface_ = display_output_surface.get();

    begin_frame_source_ = std::make_unique<BackToBackBeginFrameSource>(
        std::make_unique<DelayBasedTimeSource>(task_runner_.get()));

    int max_frames_pending = 2;
    auto scheduler = std::make_unique<DisplayScheduler>(
        begin_frame_source_.get(), task_runner_.get(), max_frames_pending);

    display_ = std::make_unique<Display>(
        &bitmap_manager_, RendererSettings(), kArbitraryFrameSinkId,
        std::move(display_output_surface), std::move(scheduler), task_runner_);
    layer_tree_frame_sink_ = std::make_unique<TestDirectLayerTreeFrameSink>(
        kArbitraryFrameSinkId, &support_manager_, &frame_sink_manager_,
        display_.get(), nullptr /* display_client */, context_provider_,
        nullptr, task_runner_, &gpu_memory_buffer_manager_,
        false /* use_viz_hit_test */);
    layer_tree_frame_sink_->BindToClient(&layer_tree_frame_sink_client_);
    display_->Resize(display_size_);
    display_->SetVisible(true);

    EXPECT_FALSE(
        layer_tree_frame_sink_client_.did_lose_layer_tree_frame_sink_called());
  }

  ~DirectLayerTreeFrameSinkTest() override {
    layer_tree_frame_sink_->DetachFromClient();
  }

  void SwapBuffersWithDamage(const gfx::Rect& damage_rect) {
    auto frame = CompositorFrameBuilder()
                     .AddRenderPass(display_rect_, damage_rect)
                     .Build();
    layer_tree_frame_sink_->SubmitCompositorFrame(std::move(frame));
  }

  void SendRenderPassList(RenderPassList* pass_list) {
    auto frame = CompositorFrameBuilder()
                     .SetRenderPassList(std::move(*pass_list))
                     .Build();
    pass_list->clear();
    layer_tree_frame_sink_->SubmitCompositorFrame(std::move(frame));
  }

  void SetUp() override {
    // Draw the first frame to start in an "unlocked" state.
    SwapBuffersWithDamage(display_rect_);

    EXPECT_EQ(0u, display_output_surface_->num_sent_frames());
    task_runner_->RunUntilIdle();
    EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
  }

 protected:
  scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;

  const gfx::Size display_size_;
  const gfx::Rect display_rect_;
  TestSharedBitmapManager bitmap_manager_;
  FrameSinkManagerImpl frame_sink_manager_;
  TestCompositorFrameSinkSupportManager support_manager_;
  TestGpuMemoryBufferManager gpu_memory_buffer_manager_;

  scoped_refptr<TestContextProvider> context_provider_;
  FakeOutputSurface* display_output_surface_ = nullptr;
  std::unique_ptr<BackToBackBeginFrameSource> begin_frame_source_;
  std::unique_ptr<Display> display_;
  cc::FakeLayerTreeFrameSinkClient layer_tree_frame_sink_client_;
  std::unique_ptr<TestDirectLayerTreeFrameSink> layer_tree_frame_sink_;
};

TEST_F(DirectLayerTreeFrameSinkTest, DamageTriggersSwapBuffers) {
  SwapBuffersWithDamage(display_rect_);
  EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
  task_runner_->RunUntilIdle();
  EXPECT_EQ(2u, display_output_surface_->num_sent_frames());
}

TEST_F(DirectLayerTreeFrameSinkTest, NoDamageDoesNotTriggerSwapBuffers) {
  SwapBuffersWithDamage(gfx::Rect());
  EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
  task_runner_->RunUntilIdle();
  EXPECT_EQ(1u, display_output_surface_->num_sent_frames());
}

// Test that hit_test_region_list are created correctly for the browser.
TEST_F(DirectLayerTreeFrameSinkTest, HitTestRegionList) {
  RenderPassList pass_list;

  // Add a DrawQuad that is not a SurfaceDrawQuad. |hit_test_region_list_|
  // shouldn't have any child regions.
  auto pass1 = RenderPass::Create();
  pass1->output_rect = display_rect_;
  pass1->id = 1;
  auto* shared_quad_state1 = pass1->CreateAndAppendSharedQuadState();
  gfx::Rect rect1(display_rect_);
  shared_quad_state1->SetAll(
      gfx::Transform(), rect1 /* quad_layer_rect */,
      rect1 /* visible_quad_layer_rect */, rect1 /*clip_rect */,
      false /* is_clipped */, false /* are_contents_opaque */,
      0.5f /* opacity */, SkBlendMode::kSrcOver, 0 /* sorting_context_id */);
  auto* quad1 = pass1->quad_list.AllocateAndConstruct<SolidColorDrawQuad>();
  quad1->SetNew(shared_quad_state1, rect1 /* rect */, rect1 /* visible_rect */,
                SK_ColorBLACK, false /* force_anti_aliasing_off */);
  pass_list.push_back(std::move(pass1));
  SendRenderPassList(&pass_list);
  task_runner_->RunUntilIdle();

  const auto* hit_test_region_list =
      frame_sink_manager_.hit_test_manager()->GetActiveHitTestRegionList(
          display_.get(), display_->CurrentSurfaceId().frame_sink_id());
  EXPECT_TRUE(hit_test_region_list);
  EXPECT_EQ(display_rect_, hit_test_region_list->bounds);
  EXPECT_EQ(HitTestRegionFlags::kHitTestMouse |
                HitTestRegionFlags::kHitTestTouch |
                HitTestRegionFlags::kHitTestMine,
            hit_test_region_list->flags);
  EXPECT_FALSE(hit_test_region_list->regions.size());

  // Add a SurfaceDrawQuad to one render pass, and add a SolidColorDrawQuad to
  // another render pass, and add a SurfaceDrawQuad with a transform that's not
  // invertible. |hit_test_region_list_| should contain one child
  // region corresponding to that first SurfaceDrawQuad.
  const SurfaceId child_surface_id(
      FrameSinkId(1, 1), LocalSurfaceId(1, base::UnguessableToken::Create()));
  auto pass2 = RenderPass::Create();
  pass2->output_rect = display_rect_;
  pass2->id = 2;
  auto* shared_quad_state2 = pass2->CreateAndAppendSharedQuadState();
  gfx::Rect rect2 = gfx::Rect(20, 20);
  gfx::Transform transform2;
  transform2.Translate(-200, -100);
  shared_quad_state2->SetAll(
      transform2, rect2 /* quad_layer_rect */,
      rect2 /* visible_quad_layer_rect */, rect2 /*clip_rect */,
      false /* is_clipped */, false /* are_contents_opaque */,
      0.5f /* opacity */, SkBlendMode::kSrcOver, 0 /* sorting_context_id */);
  auto* quad2 = pass2->quad_list.AllocateAndConstruct<SurfaceDrawQuad>();
  quad2->SetNew(shared_quad_state2, rect2 /* rect */, rect2 /* visible_rect */,
                child_surface_id /* primary_surface_id */,
                base::Optional<SurfaceId>() /* fallback_surface_id */,
                SK_ColorBLACK, false /* stretch_content_to_fill_bounds */);
  pass_list.push_back(std::move(pass2));

  auto pass3 = RenderPass::Create();
  pass3->output_rect = display_rect_;
  pass3->id = 3;
  auto* shared_quad_state3 = pass3->CreateAndAppendSharedQuadState();
  gfx::Rect rect3(display_rect_);
  shared_quad_state3->SetAll(
      gfx::Transform(), rect3 /* quad_layer_rect */,
      rect3 /* visible_quad_layer_rect */, rect3 /*clip_rect */,
      false /* is_clipped */, false /* are_contents_opaque */,
      0.5f /* opacity */, SkBlendMode::kSrcOver, 0 /* sorting_context_id */);
  auto* quad3 = pass3->quad_list.AllocateAndConstruct<SolidColorDrawQuad>();
  quad3->SetNew(shared_quad_state3, rect3 /* rect */, rect3 /* visible_rect */,
                SK_ColorBLACK, false /* force_anti_aliasing_off */);
  pass_list.push_back(std::move(pass3));
  SendRenderPassList(&pass_list);
  task_runner_->RunUntilIdle();

  const SurfaceId child_surface_id4(
      FrameSinkId(4, 1), LocalSurfaceId(1, base::UnguessableToken::Create()));
  auto pass4 = RenderPass::Create();
  pass4->output_rect = display_rect_;
  pass4->id = 4;
  auto* shared_quad_state4 = pass4->CreateAndAppendSharedQuadState();
  gfx::Rect rect4(display_rect_);
  // A degenerate matrix of all zeros is not invertible.
  gfx::Transform transform4;
  transform4.matrix().set(0, 0, 0.f);
  transform4.matrix().set(1, 1, 0.f);
  transform4.matrix().set(2, 2, 0.f);
  transform4.matrix().set(3, 3, 0.f);
  shared_quad_state4->SetAll(
      transform4, rect4 /* quad_layer_rect */,
      rect4 /* visible_quad_layer_rect */, rect4 /*clip_rect */,
      false /* is_clipped */, false /* are_contents_opaque */,
      0.5f /* opacity */, SkBlendMode::kSrcOver, 0 /* sorting_context_id */);
  auto* quad4 = pass4->quad_list.AllocateAndConstruct<SurfaceDrawQuad>();
  quad4->SetNew(shared_quad_state4, rect4 /* rect */, rect4 /* visible_rect */,
                child_surface_id4 /* primary_surface_id */,
                base::Optional<SurfaceId>() /* fallback_surface_id */,
                SK_ColorBLACK, false /* stretch_content_to_fill_bounds */);
  pass_list.push_back(std::move(pass4));

  const auto* hit_test_region_list1 =
      frame_sink_manager_.hit_test_manager()->GetActiveHitTestRegionList(
          display_.get(), display_->CurrentSurfaceId().frame_sink_id());
  EXPECT_TRUE(hit_test_region_list1);
  EXPECT_EQ(display_rect_, hit_test_region_list1->bounds);
  EXPECT_EQ(HitTestRegionFlags::kHitTestMouse |
                HitTestRegionFlags::kHitTestTouch |
                HitTestRegionFlags::kHitTestMine,
            hit_test_region_list1->flags);
  EXPECT_EQ(1u, hit_test_region_list1->regions.size());
  EXPECT_EQ(child_surface_id.frame_sink_id(),
            hit_test_region_list1->regions[0].frame_sink_id);
  EXPECT_EQ(HitTestRegionFlags::kHitTestMouse |
                HitTestRegionFlags::kHitTestTouch |
                HitTestRegionFlags::kHitTestChildSurface,
            hit_test_region_list1->regions[0].flags);
  EXPECT_EQ(gfx::Rect(20, 20), hit_test_region_list1->regions[0].rect);
  gfx::Transform transform2_inverse;
  EXPECT_TRUE(transform2.GetInverse(&transform2_inverse));
  EXPECT_EQ(transform2_inverse, hit_test_region_list1->regions[0].transform);
}

}  // namespace
}  // namespace viz
