// Copyright 2012 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/display/software_renderer.h"

#include <stdint.h>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/shared_memory.h"
#include "base/run_loop.h"
#include "cc/test/animation_test_common.h"
#include "cc/test/fake_output_surface_client.h"
#include "cc/test/geometry_test_utils.h"
#include "cc/test/pixel_test_utils.h"
#include "cc/test/render_pass_test_utils.h"
#include "cc/test/resource_provider_test_utils.h"
#include "components/viz/client/client_resource_provider.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/compositor_frame_metadata.h"
#include "components/viz/common/quads/render_pass.h"
#include "components/viz/common/quads/render_pass_draw_quad.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/quads/tile_draw_quad.h"
#include "components/viz/common/resources/bitmap_allocation.h"
#include "components/viz/common/resources/shared_bitmap.h"
#include "components/viz/service/display/display_resource_provider.h"
#include "components/viz/service/display/software_output_device.h"
#include "components/viz/test/fake_output_surface.h"
#include "components/viz/test/test_shared_bitmap_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/utils/SkNWayCanvas.h"
#include "ui/gfx/skia_util.h"

namespace viz {
namespace {

class SoftwareRendererTest : public testing::Test {
 public:
  void InitializeRenderer(
      std::unique_ptr<SoftwareOutputDevice> software_output_device) {
    output_surface_ =
        FakeOutputSurface::CreateSoftware(std::move(software_output_device));
    output_surface_->BindToClient(&output_surface_client_);

    shared_bitmap_manager_ = std::make_unique<TestSharedBitmapManager>();
    resource_provider_ = std::make_unique<DisplayResourceProvider>(
        DisplayResourceProvider::kSoftware, nullptr,
        shared_bitmap_manager_.get());
    renderer_ = std::make_unique<SoftwareRenderer>(
        &settings_, output_surface_.get(), resource_provider());
    renderer_->Initialize();
    renderer_->SetVisible(true);

    child_resource_provider_ = std::make_unique<ClientResourceProvider>(true);
  }

  void TearDown() override {
    if (child_resource_provider_)
      child_resource_provider_->ShutdownAndReleaseAllResources();
    child_resource_provider_ = nullptr;
  }

  DisplayResourceProvider* resource_provider() const {
    return resource_provider_.get();
  }

  ClientResourceProvider* child_resource_provider() const {
    return child_resource_provider_.get();
  }

  SoftwareRenderer* renderer() const { return renderer_.get(); }

  ResourceId AllocateAndFillSoftwareResource(const gfx::Size& size,
                                             const SkBitmap& source) {
    std::unique_ptr<base::SharedMemory> shm =
        bitmap_allocation::AllocateMappedBitmap(size, RGBA_8888);
    SkImageInfo info = SkImageInfo::MakeN32Premul(size.width(), size.height());
    source.readPixels(info, shm->memory(), info.minRowBytes(), 0, 0);

    // Registers the SharedBitmapId in the display compositor.
    SharedBitmapId shared_bitmap_id = SharedBitmap::GenerateId();
    shared_bitmap_manager_->ChildAllocatedSharedBitmap(
        bitmap_allocation::DuplicateAndCloseMappedBitmap(shm.get(), size,
                                                         RGBA_8888),
        shared_bitmap_id);

    // Makes a resource id that refers to the registered SharedBitmapId.
    return child_resource_provider_->ImportResource(
        TransferableResource::MakeSoftware(shared_bitmap_id, size, RGBA_8888),
        SingleReleaseCallback::Create(base::DoNothing()));
  }

  std::unique_ptr<SkBitmap> DrawAndCopyOutput(RenderPassList* list,
                                              float device_scale_factor,
                                              gfx::Size viewport_size) {
    std::unique_ptr<SkBitmap> bitmap_result;
    base::RunLoop loop;

    list->back()->copy_requests.push_back(std::make_unique<CopyOutputRequest>(
        CopyOutputRequest::ResultFormat::RGBA_BITMAP,
        base::BindOnce(&SoftwareRendererTest::SaveBitmapResult,
                       base::Unretained(&bitmap_result), loop.QuitClosure())));

    renderer()->DrawFrame(list, device_scale_factor, viewport_size);
    loop.Run();
    return bitmap_result;
  }

  static void SaveBitmapResult(std::unique_ptr<SkBitmap>* bitmap_result,
                               const base::Closure& quit_closure,
                               std::unique_ptr<CopyOutputResult> result) {
    DCHECK(!result->IsEmpty());
    DCHECK_EQ(result->format(), CopyOutputResult::Format::RGBA_BITMAP);
    *bitmap_result = std::make_unique<SkBitmap>(result->AsSkBitmap());
    DCHECK((*bitmap_result)->readyToDraw());
    quit_closure.Run();
  }

 protected:
  RendererSettings settings_;
  cc::FakeOutputSurfaceClient output_surface_client_;
  std::unique_ptr<FakeOutputSurface> output_surface_;
  std::unique_ptr<SharedBitmapManager> shared_bitmap_manager_;
  std::unique_ptr<DisplayResourceProvider> resource_provider_;
  std::unique_ptr<ClientResourceProvider> child_resource_provider_;
  std::unique_ptr<SoftwareRenderer> renderer_;
};

TEST_F(SoftwareRendererTest, SolidColorQuad) {
  gfx::Size outer_size(100, 100);
  gfx::Size inner_size(98, 98);
  gfx::Rect outer_rect(outer_size);
  gfx::Rect inner_rect(gfx::Point(1, 1), inner_size);
  gfx::Rect visible_rect(gfx::Point(1, 2), gfx::Size(98, 97));

  InitializeRenderer(std::make_unique<SoftwareOutputDevice>());

  int root_render_pass_id = 1;
  std::unique_ptr<RenderPass> root_render_pass = RenderPass::Create();
  root_render_pass->SetNew(root_render_pass_id, outer_rect, outer_rect,
                           gfx::Transform());
  SharedQuadState* shared_quad_state =
      root_render_pass->CreateAndAppendSharedQuadState();
  shared_quad_state->SetAll(gfx::Transform(), outer_rect, outer_rect,
                            outer_rect, false, true, 1.0, SkBlendMode::kSrcOver,
                            0);
  auto* inner_quad =
      root_render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
  inner_quad->SetNew(shared_quad_state, inner_rect, inner_rect, SK_ColorCYAN,
                     false);
  inner_quad->visible_rect = visible_rect;
  auto* outer_quad =
      root_render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
  outer_quad->SetNew(shared_quad_state, outer_rect, outer_rect, SK_ColorYELLOW,
                     false);

  RenderPassList list;
  list.push_back(std::move(root_render_pass));

  float device_scale_factor = 1.f;
  std::unique_ptr<SkBitmap> output =
      DrawAndCopyOutput(&list, device_scale_factor, outer_size);
  EXPECT_EQ(outer_rect.width(), output->info().width());
  EXPECT_EQ(outer_rect.height(), output->info().height());

  EXPECT_EQ(SK_ColorYELLOW, output->getColor(0, 0));
  EXPECT_EQ(SK_ColorYELLOW,
            output->getColor(outer_size.width() - 1, outer_size.height() - 1));
  EXPECT_EQ(SK_ColorYELLOW, output->getColor(1, 1));
  EXPECT_EQ(SK_ColorCYAN, output->getColor(1, 2));
  EXPECT_EQ(SK_ColorCYAN,
            output->getColor(inner_size.width() - 1, inner_size.height() - 1));
}

TEST_F(SoftwareRendererTest, TileQuad) {
  gfx::Size outer_size(100, 100);
  gfx::Size inner_size(98, 98);
  gfx::Rect outer_rect(outer_size);
  gfx::Rect inner_rect(gfx::Point(1, 1), inner_size);
  bool needs_blending = false;
  InitializeRenderer(std::make_unique<SoftwareOutputDevice>());

  SkBitmap yellow_tile;
  yellow_tile.allocN32Pixels(outer_size.width(), outer_size.height());
  yellow_tile.eraseColor(SK_ColorYELLOW);

  SkBitmap cyan_tile;
  cyan_tile.allocN32Pixels(inner_size.width(), inner_size.height());
  cyan_tile.eraseColor(SK_ColorCYAN);

  ResourceId resource_yellow =
      this->AllocateAndFillSoftwareResource(outer_size, yellow_tile);
  ResourceId resource_cyan =
      this->AllocateAndFillSoftwareResource(inner_size, cyan_tile);

  // Transfer resources to the parent, and get the resource map.
  std::unordered_map<ResourceId, ResourceId> resource_map =
      cc::SendResourceAndGetChildToParentMap(
          {resource_yellow, resource_cyan}, resource_provider(),
          child_resource_provider(), nullptr);
  ResourceId mapped_resource_yellow = resource_map[resource_yellow];
  ResourceId mapped_resource_cyan = resource_map[resource_cyan];

  gfx::Rect root_rect = outer_rect;

  int root_render_pass_id = 1;
  std::unique_ptr<RenderPass> root_render_pass = RenderPass::Create();
  root_render_pass->SetNew(root_render_pass_id, root_rect, root_rect,
                           gfx::Transform());
  SharedQuadState* shared_quad_state =
      root_render_pass->CreateAndAppendSharedQuadState();
  shared_quad_state->SetAll(gfx::Transform(), outer_rect, outer_rect,
                            outer_rect, false, true, 1.0, SkBlendMode::kSrcOver,
                            0);
  auto* inner_quad = root_render_pass->CreateAndAppendDrawQuad<TileDrawQuad>();
  inner_quad->SetNew(shared_quad_state, inner_rect, inner_rect, needs_blending,
                     mapped_resource_cyan, gfx::RectF(gfx::SizeF(inner_size)),
                     inner_size, false, false, false, false);
  auto* outer_quad = root_render_pass->CreateAndAppendDrawQuad<TileDrawQuad>();
  outer_quad->SetNew(shared_quad_state, outer_rect, outer_rect, needs_blending,
                     mapped_resource_yellow, gfx::RectF(gfx::SizeF(outer_size)),
                     outer_size, false, false, false, false);

  RenderPassList list;
  list.push_back(std::move(root_render_pass));

  float device_scale_factor = 1.f;
  std::unique_ptr<SkBitmap> output =
      DrawAndCopyOutput(&list, device_scale_factor, outer_size);
  EXPECT_EQ(outer_rect.width(), output->info().width());
  EXPECT_EQ(outer_rect.height(), output->info().height());

  EXPECT_EQ(SK_ColorYELLOW, output->getColor(0, 0));
  EXPECT_EQ(SK_ColorYELLOW,
            output->getColor(outer_size.width() - 1, outer_size.height() - 1));
  EXPECT_EQ(SK_ColorCYAN, output->getColor(1, 1));
  EXPECT_EQ(SK_ColorCYAN,
            output->getColor(inner_size.width() - 1, inner_size.height() - 1));
}

TEST_F(SoftwareRendererTest, TileQuadVisibleRect) {
  gfx::Size tile_size(100, 100);
  gfx::Rect tile_rect(tile_size);
  gfx::Rect visible_rect = tile_rect;
  bool needs_blending = false;
  visible_rect.Inset(1, 2, 3, 4);
  InitializeRenderer(std::make_unique<SoftwareOutputDevice>());

  SkBitmap cyan_tile;  // The lowest five rows are yellow.
  cyan_tile.allocN32Pixels(tile_size.width(), tile_size.height());
  cyan_tile.eraseColor(SK_ColorCYAN);
  cyan_tile.eraseArea(SkIRect::MakeLTRB(0, visible_rect.bottom() - 1,
                                        tile_rect.width(), tile_rect.bottom()),
                      SK_ColorYELLOW);

  ResourceId resource_cyan =
      AllocateAndFillSoftwareResource(tile_size, cyan_tile);

  // Transfer resources to the parent, and get the resource map.
  std::unordered_map<ResourceId, ResourceId> resource_map =
      cc::SendResourceAndGetChildToParentMap(
          {resource_cyan}, resource_provider(), child_resource_provider(),
          nullptr);
  ResourceId mapped_resource_cyan = resource_map[resource_cyan];

  gfx::Rect root_rect(tile_size);
  int root_render_pass_id = 1;
  std::unique_ptr<RenderPass> root_render_pass = RenderPass::Create();
  root_render_pass->SetNew(root_render_pass_id, root_rect, root_rect,
                           gfx::Transform());
  SharedQuadState* shared_quad_state =
      root_render_pass->CreateAndAppendSharedQuadState();
  shared_quad_state->SetAll(gfx::Transform(), tile_rect, tile_rect, tile_rect,
                            false, true, 1.0, SkBlendMode::kSrcOver, 0);
  auto* quad = root_render_pass->CreateAndAppendDrawQuad<TileDrawQuad>();
  quad->SetNew(shared_quad_state, tile_rect, tile_rect, needs_blending,
               mapped_resource_cyan, gfx::RectF(gfx::SizeF(tile_size)),
               tile_size, false, false, false, false);
  quad->visible_rect = visible_rect;

  RenderPassList list;
  list.push_back(std::move(root_render_pass));

  float device_scale_factor = 1.f;
  std::unique_ptr<SkBitmap> output =
      DrawAndCopyOutput(&list, device_scale_factor, tile_size);
  EXPECT_EQ(tile_rect.width(), output->info().width());
  EXPECT_EQ(tile_rect.height(), output->info().height());

  // Check portion of tile not in visible rect isn't drawn.
  const unsigned int kTransparent = SK_ColorTRANSPARENT;
  EXPECT_EQ(kTransparent, output->getColor(0, 0));
  EXPECT_EQ(kTransparent,
            output->getColor(tile_rect.width() - 1, tile_rect.height() - 1));
  EXPECT_EQ(kTransparent,
            output->getColor(visible_rect.x() - 1, visible_rect.y() - 1));
  EXPECT_EQ(kTransparent,
            output->getColor(visible_rect.right(), visible_rect.bottom()));
  // Ensure visible part is drawn correctly.
  EXPECT_EQ(SK_ColorCYAN, output->getColor(visible_rect.x(), visible_rect.y()));
  EXPECT_EQ(SK_ColorCYAN, output->getColor(visible_rect.right() - 2,
                                           visible_rect.bottom() - 2));
  // Ensure last visible line is correct.
  EXPECT_EQ(SK_ColorYELLOW, output->getColor(visible_rect.right() - 1,
                                             visible_rect.bottom() - 1));
}

TEST_F(SoftwareRendererTest, ShouldClearRootRenderPass) {
  float device_scale_factor = 1.f;
  gfx::Size viewport_size(100, 100);

  settings_.should_clear_root_render_pass = false;
  InitializeRenderer(std::make_unique<SoftwareOutputDevice>());

  RenderPassList list;

  // Draw a fullscreen green quad in a first frame.
  int root_clear_pass_id = 1;
  RenderPass* root_clear_pass =
      cc::AddRenderPass(&list, root_clear_pass_id, gfx::Rect(viewport_size),
                        gfx::Transform(), cc::FilterOperations());
  cc::AddQuad(root_clear_pass, gfx::Rect(viewport_size), SK_ColorGREEN);

  renderer()->DecideRenderPassAllocationsForFrame(list);

  std::unique_ptr<SkBitmap> output =
      DrawAndCopyOutput(&list, device_scale_factor, viewport_size);
  EXPECT_EQ(viewport_size.width(), output->info().width());
  EXPECT_EQ(viewport_size.height(), output->info().height());

  EXPECT_EQ(SK_ColorGREEN, output->getColor(0, 0));
  EXPECT_EQ(SK_ColorGREEN, output->getColor(viewport_size.width() - 1,
                                            viewport_size.height() - 1));

  list.clear();

  // Draw a smaller magenta rect without filling the viewport in a separate
  // frame.
  gfx::Rect smaller_rect(20, 20, 60, 60);

  int root_smaller_pass_id = 2;
  RenderPass* root_smaller_pass =
      cc::AddRenderPass(&list, root_smaller_pass_id, gfx::Rect(viewport_size),
                        gfx::Transform(), cc::FilterOperations());
  cc::AddQuad(root_smaller_pass, smaller_rect, SK_ColorMAGENTA);

  renderer()->DecideRenderPassAllocationsForFrame(list);

  output = DrawAndCopyOutput(&list, device_scale_factor, viewport_size);
  EXPECT_EQ(viewport_size.width(), output->info().width());
  EXPECT_EQ(viewport_size.height(), output->info().height());

  // If we didn't clear, the borders should still be green.
  EXPECT_EQ(SK_ColorGREEN, output->getColor(0, 0));
  EXPECT_EQ(SK_ColorGREEN, output->getColor(viewport_size.width() - 1,
                                            viewport_size.height() - 1));

  EXPECT_EQ(SK_ColorMAGENTA,
            output->getColor(smaller_rect.x(), smaller_rect.y()));
  EXPECT_EQ(SK_ColorMAGENTA, output->getColor(smaller_rect.right() - 1,
                                              smaller_rect.bottom() - 1));
}

TEST_F(SoftwareRendererTest, RenderPassVisibleRect) {
  float device_scale_factor = 1.f;
  gfx::Size viewport_size(100, 100);
  InitializeRenderer(std::make_unique<SoftwareOutputDevice>());

  RenderPassList list;

  // Pass drawn as inner quad is magenta.
  gfx::Rect smaller_rect(20, 20, 60, 60);
  int smaller_pass_id = 2;
  RenderPass* smaller_pass =
      cc::AddRenderPass(&list, smaller_pass_id, smaller_rect, gfx::Transform(),
                        cc::FilterOperations());
  cc::AddQuad(smaller_pass, smaller_rect, SK_ColorMAGENTA);

  // Root pass is green.
  int root_clear_pass_id = 1;
  RenderPass* root_clear_pass =
      AddRenderPass(&list, root_clear_pass_id, gfx::Rect(viewport_size),
                    gfx::Transform(), cc::FilterOperations());
  cc::AddRenderPassQuad(root_clear_pass, smaller_pass);
  cc::AddQuad(root_clear_pass, gfx::Rect(viewport_size), SK_ColorGREEN);

  // Interior pass quad has smaller visible rect.
  gfx::Rect interior_visible_rect(30, 30, 40, 40);
  root_clear_pass->quad_list.front()->visible_rect = interior_visible_rect;

  renderer()->DecideRenderPassAllocationsForFrame(list);

  std::unique_ptr<SkBitmap> output =
      DrawAndCopyOutput(&list, device_scale_factor, viewport_size);
  EXPECT_EQ(viewport_size.width(), output->info().width());
  EXPECT_EQ(viewport_size.height(), output->info().height());

  EXPECT_EQ(SK_ColorGREEN, output->getColor(0, 0));
  EXPECT_EQ(SK_ColorGREEN, output->getColor(viewport_size.width() - 1,
                                            viewport_size.height() - 1));

  // Part outside visible rect should remain green.
  EXPECT_EQ(SK_ColorGREEN,
            output->getColor(smaller_rect.x(), smaller_rect.y()));
  EXPECT_EQ(SK_ColorGREEN, output->getColor(smaller_rect.right() - 1,
                                            smaller_rect.bottom() - 1));

  EXPECT_EQ(SK_ColorMAGENTA, output->getColor(interior_visible_rect.x(),
                                              interior_visible_rect.y()));
  EXPECT_EQ(SK_ColorMAGENTA,
            output->getColor(interior_visible_rect.right() - 1,
                             interior_visible_rect.bottom() - 1));
}

class ClipTrackingCanvas : public SkNWayCanvas {
 public:
  ClipTrackingCanvas(int width, int height) : SkNWayCanvas(width, height) {}
  void onClipRect(const SkRect& rect,
                  SkClipOp op,
                  ClipEdgeStyle style) override {
    last_clip_rect_ = rect;
    SkNWayCanvas::onClipRect(rect, op, style);
  }

  SkRect last_clip_rect() const { return last_clip_rect_; }

 private:
  SkRect last_clip_rect_;
};

class PartialSwapSoftwareOutputDevice : public SoftwareOutputDevice {
 public:
  // SoftwareOutputDevice overrides.
  SkCanvas* BeginPaint(const gfx::Rect& damage_rect) override {
    damage_rect_at_start_ = damage_rect;
    canvas_.reset(new ClipTrackingCanvas(viewport_pixel_size_.width(),
                                         viewport_pixel_size_.height()));
    canvas_->addCanvas(SoftwareOutputDevice::BeginPaint(damage_rect));
    return canvas_.get();
  }

  void EndPaint() override {
    clip_rect_at_end_ = gfx::SkRectToRectF(canvas_->last_clip_rect());
    SoftwareOutputDevice::EndPaint();
  }

  gfx::Rect damage_rect_at_start() const { return damage_rect_at_start_; }
  gfx::RectF clip_rect_at_end() const { return clip_rect_at_end_; }

 private:
  std::unique_ptr<ClipTrackingCanvas> canvas_;
  gfx::Rect damage_rect_at_start_;
  gfx::RectF clip_rect_at_end_;
};

TEST_F(SoftwareRendererTest, PartialSwap) {
  float device_scale_factor = 1.f;
  gfx::Size viewport_size(100, 100);

  settings_.partial_swap_enabled = true;

  auto device_owned = std::make_unique<PartialSwapSoftwareOutputDevice>();
  auto* device = device_owned.get();
  InitializeRenderer(std::move(device_owned));

  RenderPassList list;

  int root_pass_id = 1;
  RenderPass* root_pass =
      AddRenderPass(&list, root_pass_id, gfx::Rect(viewport_size),
                    gfx::Transform(), cc::FilterOperations());
  cc::AddQuad(root_pass, gfx::Rect(viewport_size), SK_ColorGREEN);

  // Partial frame, we should pass this rect to the SoftwareOutputDevice.
  // partial swap is enabled.
  root_pass->damage_rect = gfx::Rect(2, 2, 3, 3);

  renderer()->DecideRenderPassAllocationsForFrame(list);
  renderer()->DrawFrame(&list, device_scale_factor, viewport_size);

  // The damage rect should be reported to the SoftwareOutputDevice.
  EXPECT_EQ(gfx::Rect(2, 2, 3, 3), device->damage_rect_at_start());
  // The SkCanvas should be clipped to the damage rect.
  EXPECT_EQ(gfx::RectF(2, 2, 3, 3), device->clip_rect_at_end());
}

}  // namespace
}  // namespace viz
