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

#include "base/time/time.h"
#include "cc/test/fake_picture_layer.h"
#include "cc/test/fake_recording_source.h"
#include "cc/test/layer_tree_test.h"
#include "cc/test/property_tree_test_utils.h"
#include "cc/trees/layer_tree_host.h"
#include "cc/trees/layer_tree_impl.h"
#include "components/viz/common/quads/compositor_frame.h"
#include "components/viz/common/quads/compositor_render_pass_draw_quad.h"

namespace cc {
namespace {

class LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin
    : public LayerTreeTest {
 protected:
  void SetupTree() override {
    // The masked layer has bounds 50x50, but it has a child that causes
    // the surface bounds to be larger. It also has a parent that clips the
    // masked layer and its surface.

    SetInitialRootBounds(gfx::Size(100, 100));
    LayerTreeTest::SetupTree();
    Layer* root = layer_tree_host()->root_layer();

    scoped_refptr<FakePictureLayer> content_layer =
        FakePictureLayer::Create(&client_);

    mask_client_.set_fill_with_nonsolid_color(true);
    auto mask_layer = FakePictureLayer::Create(&mask_client_);
    content_layer->SetMaskLayer(mask_layer);

    gfx::Size layer_size(100, 100);
    content_layer->SetBounds(layer_size);

    gfx::Size mask_size(100, 100);
    mask_layer->SetBounds(mask_size);
    mask_client_.set_bounds(mask_size);
    mask_layer_id_ = mask_layer->id();

    scoped_refptr<Layer> clip_layer = Layer::Create();
    clip_layer->SetBounds(gfx::Size(50, 50));
    clip_layer->SetMasksToBounds(true);

    scoped_refptr<Layer> scroll_layer = Layer::Create();
    scroll_layer->SetBounds(layer_size);
    scroll_layer->SetScrollable(gfx::Size(50, 50));
    scroll_layer->SetMasksToBounds(true);
    scroll_layer->SetElementId(
        LayerIdToElementIdForTesting(scroll_layer->id()));

    root->AddChild(clip_layer);
    clip_layer->AddChild(scroll_layer);
    scroll_layer->AddChild(content_layer);

    client_.set_bounds(root->bounds());
    scroll_layer->SetScrollOffset(gfx::PointF(50, 50));
  }

  void BeginTest() override { PostSetNeedsCommitToMainThread(); }

  void DisplayReceivedCompositorFrameOnThread(
      const viz::CompositorFrame& frame) override {
    EXPECT_EQ(3u, frame.render_pass_list.size());
    viz::CompositorRenderPass* root_pass = frame.render_pass_list.back().get();
    EXPECT_EQ(2u, root_pass->quad_list.size());

    // There's a solid color quad under everything.
    EXPECT_EQ(viz::DrawQuad::Material::kSolidColor,
              root_pass->quad_list.back()->material);

    EXPECT_EQ(viz::DrawQuad::Material::kCompositorRenderPass,
              root_pass->quad_list.front()->material);
    const viz::CompositorRenderPassDrawQuad* render_pass_quad =
        viz::CompositorRenderPassDrawQuad::MaterialCast(
            root_pass->quad_list.front());
    gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect(
        render_pass_quad->shared_quad_state->quad_to_target_transform,
        render_pass_quad->rect);
    EXPECT_EQ(gfx::Rect(0, 0, 50, 50), rect_in_target_space);

    // We use kDstIn blend mode instead of the mask feature of RenderPass.
    EXPECT_EQ(gfx::RectF(), render_pass_quad->mask_uv_rect);
    viz::CompositorRenderPass* mask_pass = frame.render_pass_list[1].get();
    EXPECT_EQ(SkBlendMode::kDstIn,
              mask_pass->quad_list.front()->shared_quad_state->blend_mode);
    EndTest();
    ++display_count_;
  }

  void AfterTest() override { EXPECT_EQ(1, display_count_); }

  int mask_layer_id_;
  FakeContentLayerClient client_;
  FakeContentLayerClient mask_client_;
  int display_count_ = 0;
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOrigin);

class LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOriginWithLayerList
    : public LayerTreeTest {
 protected:
  LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOriginWithLayerList() {
    SetUseLayerLists();
  }

  void SetupTree() override {
    // The masked layer has bounds 50x50, but it has a child that causes
    // the surface bounds to be larger. It also has a parent that clips the
    // masked layer and its surface.

    SetInitialRootBounds(gfx::Size(100, 100));
    LayerTreeTest::SetupTree();

    Layer* root = layer_tree_host()->root_layer();

    gfx::Size layer_size(100, 100);
    SetupViewport(root, gfx::Size(50, 50), layer_size);

    auto* scroll = layer_tree_host()->OuterViewportScrollLayerForTesting();
    SetScrollOffset(scroll, gfx::PointF(50, 50));

    client_.set_bounds(root->bounds());
    client_.set_fill_with_nonsolid_color(true);
    auto content_layer = FakePictureLayer::Create(&client_);
    content_layer->SetBounds(layer_size);
    CopyProperties(scroll, content_layer.get());
    root->AddChild(content_layer);

    auto mask_layer = FakePictureLayer::Create(&client_);
    SetupMaskProperties(content_layer.get(), mask_layer.get());
    root->AddChild(mask_layer);

    mask_layer_id_ = mask_layer->id();
  }

  void BeginTest() override { PostSetNeedsCommitToMainThread(); }

  void DisplayReceivedCompositorFrameOnThread(
      const viz::CompositorFrame& frame) override {
    EXPECT_EQ(1u, frame.render_pass_list.size());
    viz::CompositorRenderPass* pass = frame.render_pass_list.back().get();
    EXPECT_EQ(3u, pass->quad_list.size());

    // There's a solid color quad under everything.
    EXPECT_EQ(viz::DrawQuad::Material::kSolidColor,
              pass->quad_list.back()->material);

    EXPECT_EQ(viz::DrawQuad::Material::kTiledContent,
              pass->quad_list.ElementAt(1)->material);

    auto* mask_quad = pass->quad_list.front();
    EXPECT_EQ(viz::DrawQuad::Material::kTiledContent, mask_quad->material);
    gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect(
        mask_quad->shared_quad_state->quad_to_target_transform,
        mask_quad->rect);
    EXPECT_EQ(gfx::Rect(0, 0, 50, 50), rect_in_target_space);
    // We use kDstIn blend mode for mask.
    EXPECT_EQ(SkBlendMode::kDstIn, mask_quad->shared_quad_state->blend_mode);
    EndTest();
    ++display_count_;
  }

  void AfterTest() override { EXPECT_EQ(1, display_count_); }

  int mask_layer_id_;
  FakeContentLayerClient client_;
  int display_count_ = 0;
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeTestMaskLayerForSurfaceWithContentRectNotAtOriginWithLayerList);

class LayerTreeTestMaskLayerForSurfaceWithClippedLayer : public LayerTreeTest {
 protected:
  void SetupTree() override {
    // The masked layer has bounds 50x50, but it has a child that causes
    // the surface bounds to be larger. It also has a parent that clips the
    // masked layer and its surface.

    scoped_refptr<Layer> root = Layer::Create();

    scoped_refptr<Layer> clipping_layer = Layer::Create();
    root->AddChild(clipping_layer);

    scoped_refptr<FakePictureLayer> content_layer =
        FakePictureLayer::Create(&client_);
    clipping_layer->AddChild(content_layer);

    scoped_refptr<FakePictureLayer> content_child_layer =
        FakePictureLayer::Create(&client_);
    content_layer->AddChild(content_child_layer);

    mask_client_.set_fill_with_nonsolid_color(true);
    auto mask_layer = FakePictureLayer::Create(&mask_client_);
    content_layer->SetMaskLayer(mask_layer);

    gfx::Size root_size(100, 100);
    root->SetBounds(root_size);

    gfx::PointF clipping_origin(20.f, 10.f);
    gfx::Size clipping_size(10, 20);
    clipping_layer->SetBounds(clipping_size);
    clipping_layer->SetPosition(clipping_origin);
    clipping_layer->SetMasksToBounds(true);

    gfx::Size layer_size(50, 50);
    content_layer->SetBounds(layer_size);
    content_layer->SetPosition(gfx::PointF() -
                               clipping_origin.OffsetFromOrigin());

    gfx::Size child_size(50, 50);
    content_child_layer->SetBounds(child_size);
    content_child_layer->SetPosition(gfx::PointF(20.f, 0.f));

    gfx::Size mask_size(50, 50);
    mask_layer->SetBounds(mask_size);
    mask_client_.set_bounds(mask_size);
    mask_layer_id_ = mask_layer->id();

    layer_tree_host()->SetRootLayer(root);
    LayerTreeTest::SetupTree();
    client_.set_bounds(root->bounds());
  }

  void BeginTest() override { PostSetNeedsCommitToMainThread(); }

  void DisplayReceivedCompositorFrameOnThread(
      const viz::CompositorFrame& frame) override {
    EXPECT_EQ(3u, frame.render_pass_list.size());
    viz::CompositorRenderPass* root_pass = frame.render_pass_list.back().get();
    EXPECT_EQ(2u, root_pass->quad_list.size());

    // There's a solid color quad under everything.
    EXPECT_EQ(viz::DrawQuad::Material::kSolidColor,
              root_pass->quad_list.back()->material);

    // The surface is clipped to 10x20.
    EXPECT_EQ(viz::DrawQuad::Material::kCompositorRenderPass,
              root_pass->quad_list.front()->material);
    const viz::CompositorRenderPassDrawQuad* render_pass_quad =
        viz::CompositorRenderPassDrawQuad::MaterialCast(
            root_pass->quad_list.front());
    gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect(
        render_pass_quad->shared_quad_state->quad_to_target_transform,
        render_pass_quad->rect);
    EXPECT_EQ(gfx::Rect(20, 10, 10, 20).ToString(),
              rect_in_target_space.ToString());

    // We use kDstIn blend mode instead of the mask feature of RenderPass.
    EXPECT_EQ(gfx::RectF(), render_pass_quad->mask_uv_rect);
    viz::CompositorRenderPass* mask_pass = frame.render_pass_list[1].get();
    EXPECT_EQ(SkBlendMode::kDstIn,
              mask_pass->quad_list.front()->shared_quad_state->blend_mode);
    EndTest();
    ++display_count_;
  }

  void AfterTest() override { EXPECT_EQ(1, display_count_); }

  int mask_layer_id_;
  FakeContentLayerClient client_;
  FakeContentLayerClient mask_client_;
  int display_count_ = 0;
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeTestMaskLayerForSurfaceWithClippedLayer);

class LayerTreeTestMaskLayerForSurfaceWithDifferentScale
    : public LayerTreeTest {
 protected:
  void SetupTree() override {
    // The masked layer has bounds 50x50, but it has a child that causes
    // the surface bounds to be larger. It also has a parent that clips the
    // masked layer and its surface.

    scoped_refptr<Layer> root = Layer::Create();

    scoped_refptr<Layer> clipping_scaling_layer = Layer::Create();
    root->AddChild(clipping_scaling_layer);

    scoped_refptr<FakePictureLayer> content_layer =
        FakePictureLayer::Create(&client_);
    clipping_scaling_layer->AddChild(content_layer);

    scoped_refptr<FakePictureLayer> content_child_layer =
        FakePictureLayer::Create(&client_);
    content_layer->AddChild(content_child_layer);

    mask_client_.set_fill_with_nonsolid_color(true);
    auto mask_layer = FakePictureLayer::Create(&mask_client_);
    content_layer->SetMaskLayer(mask_layer);

    gfx::Size root_size(100, 100);
    root->SetBounds(root_size);

    gfx::Transform scale;
    scale.Scale(2, 2);
    gfx::PointF clipping_origin(20.f, 10.f);
    gfx::Size clipping_size(10, 20);
    clipping_scaling_layer->SetBounds(clipping_size);
    clipping_scaling_layer->SetPosition(clipping_origin);
    // This changes scale between contributing layer and render surface to 2.
    clipping_scaling_layer->SetTransform(scale);
    clipping_scaling_layer->SetMasksToBounds(true);

    gfx::Size layer_size(50, 50);
    content_layer->SetBounds(layer_size);
    content_layer->SetPosition(gfx::PointF() -
                               clipping_origin.OffsetFromOrigin());

    gfx::Size child_size(50, 50);
    content_child_layer->SetBounds(child_size);
    content_child_layer->SetPosition(gfx::PointF(20.f, 0.f));

    gfx::Size mask_size(50, 50);
    mask_layer->SetBounds(mask_size);
    mask_client_.set_bounds(mask_size);
    mask_layer_id_ = mask_layer->id();

    layer_tree_host()->SetRootLayer(root);
    LayerTreeTest::SetupTree();
    client_.set_bounds(root->bounds());
  }

  void BeginTest() override { PostSetNeedsCommitToMainThread(); }

  void DisplayReceivedCompositorFrameOnThread(
      const viz::CompositorFrame& frame) override {
    EXPECT_EQ(3u, frame.render_pass_list.size());
    viz::CompositorRenderPass* root_pass = frame.render_pass_list.back().get();
    EXPECT_EQ(2u, root_pass->quad_list.size());

    // There's a solid color quad under everything.
    EXPECT_EQ(viz::DrawQuad::Material::kSolidColor,
              root_pass->quad_list.back()->material);

    // The surface is clipped to 10x20, and then scaled by 2, which ends up
    // being 20x40.
    EXPECT_EQ(viz::DrawQuad::Material::kCompositorRenderPass,
              root_pass->quad_list.front()->material);
    const viz::CompositorRenderPassDrawQuad* render_pass_quad =
        viz::CompositorRenderPassDrawQuad::MaterialCast(
            root_pass->quad_list.front());
    gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect(
        render_pass_quad->shared_quad_state->quad_to_target_transform,
        render_pass_quad->rect);
    EXPECT_EQ(gfx::Rect(20, 10, 20, 40).ToString(),
              rect_in_target_space.ToString());
    gfx::Rect visible_rect_in_target_space = MathUtil::MapEnclosingClippedRect(
        render_pass_quad->shared_quad_state->quad_to_target_transform,
        render_pass_quad->visible_rect);
    EXPECT_EQ(gfx::Rect(20, 10, 20, 40).ToString(),
              visible_rect_in_target_space.ToString());

    // We use kDstIn blend mode instead of the mask feature of RenderPass.
    EXPECT_EQ(gfx::RectF(), render_pass_quad->mask_uv_rect);
    viz::CompositorRenderPass* mask_pass = frame.render_pass_list[1].get();
    EXPECT_EQ(SkBlendMode::kDstIn,
              mask_pass->quad_list.front()->shared_quad_state->blend_mode);
    EndTest();
    ++display_count_;
  }

  void AfterTest() override { EXPECT_EQ(1, display_count_); }

  int mask_layer_id_;
  FakeContentLayerClient client_;
  FakeContentLayerClient mask_client_;
  int display_count_ = 0;
};

SINGLE_AND_MULTI_THREAD_TEST_F(
    LayerTreeTestMaskLayerForSurfaceWithDifferentScale);

class LayerTreeTestMaskLayerWithScaling : public LayerTreeTest {
 protected:
  void SetupTree() override {
    // Root
    //  |
    //  +-- Scaling Layer (adds a 2x scale)
    //       |
    //       +-- Content Layer
    //             +--Mask

    scoped_refptr<Layer> root = Layer::Create();

    scoped_refptr<Layer> scaling_layer = Layer::Create();
    root->AddChild(scaling_layer);

    scoped_refptr<FakePictureLayer> content_layer =
        FakePictureLayer::Create(&client_);
    scaling_layer->AddChild(content_layer);

    mask_client_.set_fill_with_nonsolid_color(true);
    auto mask_layer = FakePictureLayer::Create(&mask_client_);
    content_layer->SetMaskLayer(mask_layer);

    gfx::Size root_size(100, 100);
    root->SetBounds(root_size);

    gfx::Size scaling_layer_size(50, 50);
    scaling_layer->SetBounds(scaling_layer_size);
    gfx::Transform scale;
    scale.Scale(2.f, 2.f);
    scaling_layer->SetTransform(scale);

    content_layer->SetBounds(scaling_layer_size);
    mask_layer->SetBounds(scaling_layer_size);
    mask_client_.set_bounds(scaling_layer_size);

    layer_tree_host()->SetRootLayer(root);
    LayerTreeTest::SetupTree();
    client_.set_bounds(root->bounds());
  }

  void BeginTest() override { PostSetNeedsCommitToMainThread(); }

  void DisplayReceivedCompositorFrameOnThread(
      const viz::CompositorFrame& frame) override {
    EXPECT_EQ(3u, frame.render_pass_list.size());
    viz::CompositorRenderPass* root_pass = frame.render_pass_list.back().get();
    EXPECT_EQ(2u, root_pass->quad_list.size());

    // There's a solid color quad under everything.
    EXPECT_EQ(viz::DrawQuad::Material::kSolidColor,
              root_pass->quad_list.back()->material);

    EXPECT_EQ(viz::DrawQuad::Material::kCompositorRenderPass,
              root_pass->quad_list.front()->material);
    const viz::CompositorRenderPassDrawQuad* render_pass_quad =
        viz::CompositorRenderPassDrawQuad::MaterialCast(
            root_pass->quad_list.front());
    gfx::Rect rect_in_target_space = MathUtil::MapEnclosingClippedRect(
        render_pass_quad->shared_quad_state->quad_to_target_transform,
        render_pass_quad->rect);

    // We use kDstIn blend mode instead of the mask feature of RenderPass.
    EXPECT_EQ(gfx::RectF(), render_pass_quad->mask_uv_rect);
    viz::CompositorRenderPass* mask_pass = frame.render_pass_list[1].get();
    EXPECT_EQ(SkBlendMode::kDstIn,
              mask_pass->quad_list.front()->shared_quad_state->blend_mode);

    switch (display_count_) {
      case 0:
        // Check that the tree scaling is correctly taken into account for the
        // mask, that should fully map onto the quad.
        EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
                  rect_in_target_space.ToString());
        break;
      case 1:
        // Applying a DSF should change the render surface size, but won't
        // affect which part of the mask is used.
        EXPECT_EQ(gfx::Rect(0, 0, 200, 200).ToString(),
                  rect_in_target_space.ToString());
        EndTest();
        break;
    }
    ++display_count_;
  }

  void DidCommitAndDrawFrame() override {
    switch (commit_count_) {
      case 0:
        gfx::Size double_root_size(200, 200);
        GenerateNewLocalSurfaceId();
        layer_tree_host()->SetViewportRectAndScale(
            gfx::Rect(double_root_size), 2.f, GetCurrentLocalSurfaceId());
        break;
    }
    ++commit_count_;
  }

  void AfterTest() override {
    EXPECT_EQ(2, commit_count_);
    EXPECT_EQ(2, display_count_);
  }

  FakeContentLayerClient client_;
  FakeContentLayerClient mask_client_;
  int commit_count_ = 0;
  int display_count_ = 0;
};

SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeTestMaskLayerWithScaling);

class LayerTreeTestMaskWithNonExactTextureSize : public LayerTreeTest {
 protected:
  void SetupTree() override {
    // The masked layer has bounds 100x100, but is allocated a 120x150 texture.

    scoped_refptr<Layer> root = Layer::Create();

    scoped_refptr<FakePictureLayer> content_layer =
        FakePictureLayer::Create(&client_);
    root->AddChild(content_layer);

    mask_client_.set_fill_with_nonsolid_color(true);
    auto mask_layer = FakePictureLayer::Create(&mask_client_);
    content_layer->SetMaskLayer(mask_layer);

    gfx::Size root_size(100, 100);
    root->SetBounds(root_size);

    gfx::Size layer_size(100, 100);
    content_layer->SetBounds(layer_size);

    gfx::Size mask_size(100, 100);
    gfx::Size mask_texture_size(120, 150);
    mask_layer->SetBounds(mask_size);
    mask_client_.set_bounds(mask_size);
    mask_layer->set_fixed_tile_size(mask_texture_size);

    layer_tree_host()->SetRootLayer(root);
    LayerTreeTest::SetupTree();
    client_.set_bounds(root->bounds());
  }

  void BeginTest() override { PostSetNeedsCommitToMainThread(); }

  void DisplayReceivedCompositorFrameOnThread(
      const viz::CompositorFrame& frame) override {
    EXPECT_EQ(3u, frame.render_pass_list.size());
    viz::CompositorRenderPass* root_pass = frame.render_pass_list.back().get();
    EXPECT_EQ(2u, root_pass->quad_list.size());

    // There's a solid color quad under everything.
    EXPECT_EQ(viz::DrawQuad::Material::kSolidColor,
              root_pass->quad_list.back()->material);

    // The surface is 100x100
    EXPECT_EQ(viz::DrawQuad::Material::kCompositorRenderPass,
              root_pass->quad_list.front()->material);
    const viz::CompositorRenderPassDrawQuad* render_pass_quad =
        viz::CompositorRenderPassDrawQuad::MaterialCast(
            root_pass->quad_list.front());
    EXPECT_EQ(gfx::Rect(0, 0, 100, 100).ToString(),
              render_pass_quad->rect.ToString());

    // We use kDstIn blend mode instead of the mask feature of RenderPass.
    EXPECT_EQ(gfx::RectF(), render_pass_quad->mask_uv_rect);
    viz::CompositorRenderPass* mask_pass = frame.render_pass_list[1].get();
    EXPECT_EQ(SkBlendMode::kDstIn,
              mask_pass->quad_list.front()->shared_quad_state->blend_mode);
    EndTest();
    ++display_count_;
  }

  void AfterTest() override { EXPECT_EQ(1, display_count_); }

  int mask_layer_id_;
  FakeContentLayerClient client_;
  FakeContentLayerClient mask_client_;
  int display_count_ = 0;
};

SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeTestMaskWithNonExactTextureSize);

}  // namespace
}  // namespace cc
