// Copyright 2020 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/common/quads/render_pass_internal.h"

#include <stddef.h>

#include <unordered_map>

#include "base/containers/flat_set.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/traced_value.h"
#include "cc/base/math_util.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/quads/solid_color_draw_quad.h"
#include "components/viz/common/traced_value.h"

namespace viz {
namespace {
const size_t kDefaultNumSharedQuadStatesToReserve = 32;
const size_t kDefaultNumQuadsToReserve = 128;
}  // namespace

RenderPassInternal::RenderPassInternal()
    : RenderPassInternal(kDefaultNumSharedQuadStatesToReserve,
                         kDefaultNumQuadsToReserve) {}
// Each layer usually produces one shared quad state, so the number of layers
// is a good hint for what to reserve here.
RenderPassInternal::RenderPassInternal(size_t num_layers)
    : RenderPassInternal(num_layers, kDefaultNumQuadsToReserve) {}
RenderPassInternal::RenderPassInternal(size_t shared_quad_state_list_size,
                                       size_t quad_list_size)
    : shared_quad_state_list(alignof(SharedQuadState),
                             sizeof(SharedQuadState),
                             shared_quad_state_list_size),
      quad_list(quad_list_size) {}

RenderPassInternal::~RenderPassInternal() {
#if DCHECK_IS_ON()
  base::flat_set<const SharedQuadState*> valid_states(
      shared_quad_state_list.begin(), shared_quad_state_list.end());
  for (const auto* quad : quad_list) {
    DCHECK(valid_states.contains(quad->shared_quad_state.get()));
  }
#endif
}

SharedQuadState* RenderPassInternal::CreateAndAppendSharedQuadState() {
  return shared_quad_state_list.AllocateAndConstruct<SharedQuadState>();
}

void RenderPassInternal::ReplaceExistingQuadWithSolidColor(
    QuadList::Iterator at,
    SkColor4f color,
    SkBlendMode blend_mode) {
  const SharedQuadState* shared_quad_state = at->shared_quad_state;
  if (shared_quad_state->are_contents_opaque ||
      shared_quad_state->blend_mode != blend_mode) {
    auto* new_shared_quad_state =
        shared_quad_state_list.AllocateAndCopyFrom(shared_quad_state);
    new_shared_quad_state->are_contents_opaque = false;
    new_shared_quad_state->blend_mode = blend_mode;
    shared_quad_state = new_shared_quad_state;
  }

  const gfx::Rect rect = at->rect;
  // TODO(crbug.com/40219248) This function should be called with an SkColor4f
  quad_list.ReplaceExistingElement<SolidColorDrawQuad>(at)->SetAll(
      shared_quad_state, rect, /*visible_rect=*/rect,
      /*needs_blending=*/false, color,
      /*force_anti_aliasing_off=*/true);
}

void RenderPassInternal::ReplaceExistingQuadWithHolePunch(
    QuadList::Iterator quad,
    bool* quad_was_opaque) {
  // If the `quad` is translucent and uses SrcOver blend mode, we can achieve
  // the same result as compositing with `quad` on top, if we replace `quad`
  // with a solid color quad with DstOut blend mode, and rely on SrcOver
  // blending of the root surface with video on bottom. Essentially,
  //
  // SrcOver_quad(V, B, V_alpha) = SrcOver_premul(DstOut(BLACK, B, V_alpha), V)
  // where
  //    V is the video quad
  //    B is the background
  //    SrcOver_quad uses opacity of source quad (V_alpha)
  //    SrcOver_premul uses alpha channel and assumes premultipled alpha
  //
  // This also applies to quads with a mask filter for rounded corners.
  if (quad->ShouldDrawWithBlending() &&
      quad->shared_quad_state->blend_mode == SkBlendMode::kSrcOver) {
    ReplaceExistingQuadWithSolidColor(quad, SkColors::kBlack,
                                      SkBlendMode::kDstOut);
    if (quad_was_opaque) {
      *quad_was_opaque = false;
    }
  } else {
    // When the opacity == 1.0, drawing with transparent will be done without
    // blending and will have the proper effect of completely clearing the
    // layer.
    ReplaceExistingQuadWithSolidColor(quad, SkColors::kTransparent,
                                      SkBlendMode::kSrcOver);
    if (quad_was_opaque) {
      *quad_was_opaque = true;
    }
  }
}

void RenderPassInternal::AsValueInto(
    base::trace_event::TracedValue* value,
    const std::unordered_map<ResourceId, size_t>& resource_id_to_index_map)
    const {
  cc::MathUtil::AddToTracedValue("output_rect", output_rect, value);
  cc::MathUtil::AddToTracedValue("damage_rect", damage_rect, value);
  cc::MathUtil::AddToTracedValue("transform_to_root_target",
                                 transform_to_root_target, value);

  value->SetBoolean("has_transparent_background", has_transparent_background);
  value->SetBoolean("cache_render_pass", cache_render_pass);
  value->SetBoolean("has_damage_from_contributing_content",
                    has_damage_from_contributing_content);
  value->SetBoolean("generate_mipmap", generate_mipmap);
  value->SetInteger("copy_requests",
                    base::saturated_cast<int>(copy_requests.size()));

  value->SetInteger("shared_quad_state_list_size",
                    shared_quad_state_list.size());
  std::unordered_map<const SharedQuadState*, size_t> sqs_pointer_to_index_map;
  size_t index = 0;
  value->BeginArray("shared_quad_state_list");
  for (auto* shared_quad_state : shared_quad_state_list) {
    value->BeginDictionary();
    sqs_pointer_to_index_map.emplace(shared_quad_state, index);
    shared_quad_state->AsValueInto(value);
    value->SetInteger("index", index++);
    value->EndDictionary();
  }
  value->EndArray();

  value->SetInteger("quad_list_size", quad_list.size());
  index = 0;
  value->BeginArray("quad_list");
  for (auto* quad : quad_list) {
    value->BeginDictionary();
    quad->AsValueInto(value, sqs_pointer_to_index_map,
                      resource_id_to_index_map);
    value->SetInteger("index", index++);
    value->EndDictionary();
  }
  value->EndArray();
}

}  // namespace viz
