// 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 "base/memory/ptr_util.h"
#include "cc/debug/lap_timer.h"
#include "cc/output/compositor_frame.h"
#include "cc/output/delegated_frame_data.h"
#include "cc/quads/surface_draw_quad.h"
#include "cc/quads/texture_draw_quad.h"
#include "cc/surfaces/surface_aggregator.h"
#include "cc/surfaces/surface_factory.h"
#include "cc/surfaces/surface_factory_client.h"
#include "cc/surfaces/surface_manager.h"
#include "cc/test/fake_output_surface_client.h"
#include "cc/test/fake_resource_provider.h"
#include "cc/test/test_context_provider.h"
#include "cc/test/test_shared_bitmap_manager.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/perf/perf_test.h"

namespace cc {
namespace {

static constexpr FrameSinkId kArbitraryFrameSinkId(1, 1);
static const base::UnguessableToken kArbitraryToken =
    base::UnguessableToken::Create();

class EmptySurfaceFactoryClient : public SurfaceFactoryClient {
 public:
  void ReturnResources(const ReturnedResourceArray& resources) override {}
  void SetBeginFrameSource(BeginFrameSource* begin_frame_source) override {}
};

class SurfaceAggregatorPerfTest : public testing::Test {
 public:
  SurfaceAggregatorPerfTest()
      : factory_(kArbitraryFrameSinkId, &manager_, &empty_client_) {
    context_provider_ = TestContextProvider::Create();
    context_provider_->BindToCurrentThread();
    shared_bitmap_manager_.reset(new TestSharedBitmapManager);

    resource_provider_ = FakeResourceProvider::Create(
        context_provider_.get(), shared_bitmap_manager_.get());
  }

  void RunTest(int num_surfaces,
               int num_textures,
               float opacity,
               bool optimize_damage,
               bool full_damage,
               const std::string& name) {
    aggregator_.reset(new SurfaceAggregator(&manager_, resource_provider_.get(),
                                            optimize_damage));
    for (int i = 1; i <= num_surfaces; i++) {
      LocalFrameId local_frame_id(i, kArbitraryToken);
      factory_.Create(local_frame_id);
      std::unique_ptr<RenderPass> pass(RenderPass::Create());
      std::unique_ptr<DelegatedFrameData> frame_data(new DelegatedFrameData);

      SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
      for (int j = 0; j < num_textures; j++) {
        TransferableResource resource;
        resource.id = j;
        resource.is_software = true;
        frame_data->resource_list.push_back(resource);

        TextureDrawQuad* quad =
            pass->CreateAndAppendDrawQuad<TextureDrawQuad>();
        const gfx::Rect rect(0, 0, 1, 1);
        const gfx::Rect opaque_rect;
        // Half of rects should be visible with partial damage.
        gfx::Rect visible_rect =
            j % 2 == 0 ? gfx::Rect(0, 0, 1, 1) : gfx::Rect(1, 1, 1, 1);
        bool needs_blending = false;
        bool premultiplied_alpha = false;
        const gfx::PointF uv_top_left;
        const gfx::PointF uv_bottom_right;
        SkColor background_color = SK_ColorGREEN;
        const float vertex_opacity[4] = {0.f, 0.f, 1.f, 1.f};
        bool flipped = false;
        bool nearest_neighbor = false;
        quad->SetAll(sqs, rect, opaque_rect, visible_rect, needs_blending, j,
                     gfx::Size(), premultiplied_alpha, uv_top_left,
                     uv_bottom_right, background_color, vertex_opacity, flipped,
                     nearest_neighbor, false);
      }
      sqs = pass->CreateAndAppendSharedQuadState();
      sqs->opacity = opacity;
      if (i > 1) {
        SurfaceDrawQuad* surface_quad =
            pass->CreateAndAppendDrawQuad<SurfaceDrawQuad>();
        surface_quad->SetNew(sqs, gfx::Rect(0, 0, 1, 1), gfx::Rect(0, 0, 1, 1),
                             SurfaceId(kArbitraryFrameSinkId,
                                       LocalFrameId(i - 1, kArbitraryToken)));
      }

      frame_data->render_pass_list.push_back(std::move(pass));
      CompositorFrame frame;
      frame.delegated_frame_data = std::move(frame_data);
      factory_.SubmitCompositorFrame(local_frame_id, std::move(frame),
                                     SurfaceFactory::DrawCallback());
    }

    factory_.Create(LocalFrameId(num_surfaces + 1, kArbitraryToken));
    timer_.Reset();
    do {
      std::unique_ptr<RenderPass> pass(RenderPass::Create());
      std::unique_ptr<DelegatedFrameData> frame_data(new DelegatedFrameData);

      SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState();
      SurfaceDrawQuad* surface_quad =
          pass->CreateAndAppendDrawQuad<SurfaceDrawQuad>();
      surface_quad->SetNew(
          sqs, gfx::Rect(0, 0, 100, 100), gfx::Rect(0, 0, 100, 100),
          SurfaceId(kArbitraryFrameSinkId,
                    LocalFrameId(num_surfaces, kArbitraryToken)));

      if (full_damage)
        pass->damage_rect = gfx::Rect(0, 0, 100, 100);
      else
        pass->damage_rect = gfx::Rect(0, 0, 1, 1);

      frame_data->render_pass_list.push_back(std::move(pass));
      CompositorFrame frame;
      frame.delegated_frame_data = std::move(frame_data);
      factory_.SubmitCompositorFrame(
          LocalFrameId(num_surfaces + 1, kArbitraryToken), std::move(frame),
          SurfaceFactory::DrawCallback());

      CompositorFrame aggregated = aggregator_->Aggregate(
          SurfaceId(kArbitraryFrameSinkId,
                    LocalFrameId(num_surfaces + 1, kArbitraryToken)));
      timer_.NextLap();
    } while (!timer_.HasTimeLimitExpired());

    perf_test::PrintResult("aggregator_speed", "", name, timer_.LapsPerSecond(),
                           "runs/s", true);

    factory_.Destroy(LocalFrameId(num_surfaces + 1, kArbitraryToken));
    for (int i = 1; i <= num_surfaces; i++)
      factory_.Destroy(LocalFrameId(i, kArbitraryToken));
  }

 protected:
  SurfaceManager manager_;
  EmptySurfaceFactoryClient empty_client_;
  SurfaceFactory factory_;
  scoped_refptr<TestContextProvider> context_provider_;
  std::unique_ptr<SharedBitmapManager> shared_bitmap_manager_;
  std::unique_ptr<ResourceProvider> resource_provider_;
  std::unique_ptr<SurfaceAggregator> aggregator_;
  LapTimer timer_;
};

TEST_F(SurfaceAggregatorPerfTest, ManySurfacesOpaque) {
  RunTest(20, 100, 1.f, false, true, "many_surfaces_opaque");
}

TEST_F(SurfaceAggregatorPerfTest, ManySurfacesTransparent) {
  RunTest(20, 100, .5f, false, true, "many_surfaces_transparent");
}

TEST_F(SurfaceAggregatorPerfTest, FewSurfaces) {
  RunTest(3, 1000, 1.f, false, true, "few_surfaces");
}

TEST_F(SurfaceAggregatorPerfTest, ManySurfacesOpaqueDamageCalc) {
  RunTest(20, 100, 1.f, true, true, "many_surfaces_opaque_damage_calc");
}

TEST_F(SurfaceAggregatorPerfTest, ManySurfacesTransparentDamageCalc) {
  RunTest(20, 100, .5f, true, true, "many_surfaces_transparent_damage_calc");
}

TEST_F(SurfaceAggregatorPerfTest, FewSurfacesDamageCalc) {
  RunTest(3, 1000, 1.f, true, true, "few_surfaces_damage_calc");
}

TEST_F(SurfaceAggregatorPerfTest, FewSurfacesAggregateDamaged) {
  RunTest(3, 1000, 1.f, true, false, "few_surfaces_aggregate_damaged");
}

}  // namespace
}  // namespace cc
