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

#include "cc/layers/layer_impl.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <utility>

#include "base/atomic_sequence_num.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "cc/base/math_util.h"
#include "cc/base/simple_enclosed_region.h"
#include "cc/benchmarks/micro_benchmark_impl.h"
#include "cc/debug/debug_colors.h"
#include "cc/debug/layer_tree_debug_state.h"
#include "cc/input/scroll_state.h"
#include "cc/layers/layer.h"
#include "cc/trees/clip_node.h"
#include "cc/trees/draw_property_utils.h"
#include "cc/trees/effect_node.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/layer_tree_settings.h"
#include "cc/trees/mutator_host.h"
#include "cc/trees/proxy.h"
#include "cc/trees/scroll_node.h"
#include "cc/trees/transform_node.h"
#include "components/viz/client/client_resource_provider.h"
#include "components/viz/common/frame_sinks/copy_output_request.h"
#include "components/viz/common/quads/compositor_render_pass.h"
#include "components/viz/common/quads/debug_border_draw_quad.h"
#include "components/viz/common/resources/resource_id.h"
#include "components/viz/common/traced_value.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/quad_f.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/transform_util.h"
#include "ui/gfx/geometry/vector2d_conversions.h"

namespace cc {

namespace {

template <typename T>
std::unique_ptr<T> ClonePtr(const std::unique_ptr<T>& value) {
  return value ? std::make_unique<T>(*value) : nullptr;
}

const char* LayerTypeAsString(mojom::LayerType type) {
  switch (type) {
    case mojom::LayerType::kLayer:
      return "cc::LayerImpl";
    case mojom::LayerType::kSolidColor:
      return "cc::SolidColorLayerImpl";
    case mojom::LayerType::kTexture:
      return "cc::TextureLayerImpl";
    case mojom::LayerType::kSurface:
      return "cc::SurfaceLayerImpl";
    case mojom::LayerType::kPicture:
      return "cc::PictureLayerImpl";
    case mojom::LayerType::kTileDisplay:
      return "cc::TileDisplayLayerImpl";
    case mojom::LayerType::kMirror:
      return "cc::MirrorLayerImpl";
    case mojom::LayerType::kHeadsUpDisplay:
      return "cc::HeadsUpDisplayLayerImpl";
    case mojom::LayerType::kUIResource:
      return "cc::UIResourceLayerImpl";
    case mojom::LayerType::kNinePatch:
      return "cc::NinePatchLayerImpl";
    case mojom::LayerType::kSolidColorScrollbar:
      return "cc::SolidColorScrollbarLayerImpl";
    case mojom::LayerType::kPaintedScrollbar:
      return "cc::PaintedScrollbarLayerImpl";
    case mojom::LayerType::kNinePatchThumbScrollbar:
      return "cc::NinePatchThumbScrollbarLayerImpl";
    case mojom::LayerType::kVideo:
      return "cc::VideoLayerImpl";
    case mojom::LayerType::kViewTransitionContent:
      return "cc::ViewTransitionContentLayerImpl";
  }
}

base::AtomicSequenceNumber g_next_stable_id_for_shared_quad_state;

}  // namespace

LayerImpl::RareProperties::RareProperties() = default;
LayerImpl::RareProperties::RareProperties(const RareProperties&) = default;
LayerImpl::RareProperties::~RareProperties() = default;

int LayerImpl::GetNextStableIdForSharedQuadState() {
  // Stable IDs start from 1.
  return g_next_stable_id_for_shared_quad_state.GetNext() + 1;
}

LayerImpl::LayerImpl(LayerTreeImpl* tree_impl, int id)
    : layer_id_(id),
      layer_tree_impl_(tree_impl),
      stable_id_for_shared_quad_state_(GetNextStableIdForSharedQuadState()),
      transform_tree_index_(kInvalidPropertyNodeId),
      effect_tree_index_(kInvalidPropertyNodeId),
      clip_tree_index_(kInvalidPropertyNodeId),
      scroll_tree_index_(kInvalidPropertyNodeId) {
  DCHECK_GT(layer_id_, 0);
  DCHECK_GT(stable_id_for_shared_quad_state_, 0);

  DCHECK(layer_tree_impl_);
  layer_tree_impl_->RegisterLayer(this);

  layer_impl_utils_ = std::make_unique<LayerImplUtils>(this);

  SetNeedsPushProperties(LayerImpl::kChangedAllProperties);
}

LayerImpl::~LayerImpl() {
  layer_tree_impl_->UnregisterLayer(this);
  TRACE_EVENT_OBJECT_DELETED_WITH_ID(
      TRACE_DISABLED_BY_DEFAULT("cc.debug"), "cc::LayerImpl", this);
}

mojom::LayerType LayerImpl::GetLayerType() const {
  return mojom::LayerType::kLayer;
}

ElementListType LayerImpl::GetElementTypeForAnimation() const {
  return IsActive() ? ElementListType::ACTIVE : ElementListType::PENDING;
}

void LayerImpl::UpdateDebugInfo(LayerDebugInfo* debug_info) {
  SetNeedsPushProperties();

  // nullptr means we have stopped collecting debug info.
  if (!debug_info) {
    debug_info_.reset();
    return;
  }
  auto new_invalidations = std::move(debug_info->invalidations);
  if (!debug_info_) {
    debug_info_ = std::make_unique<LayerDebugInfo>(*debug_info);
    debug_info_->invalidations = std::move(new_invalidations);
    return;
  }
  // Accumulate invalidations until we draw the layer.
  auto existing_invalidations = std::move(debug_info_->invalidations);
  *debug_info_ = *debug_info;
  debug_info_->invalidations.insert(debug_info_->invalidations.begin(),
                                    existing_invalidations.begin(),
                                    existing_invalidations.end());
}

void LayerImpl::SetHasTransformNode(bool val) {
  if (has_transform_node_ == val) {
    return;
  }

  has_transform_node_ = val;
  SetNeedsPushProperties(LayerImpl::kChangedPropertyTreeIndex);
}

void LayerImpl::SetTransformTreeIndex(int index) {
  if (transform_tree_index_ == index) {
    return;
  }

  transform_tree_index_ = index;
  SetNeedsPushProperties(LayerImpl::kChangedPropertyTreeIndex);
}

void LayerImpl::SetClipTreeIndex(int index) {
  if (clip_tree_index_ == index) {
    return;
  }

  clip_tree_index_ = index;
  SetNeedsPushProperties(LayerImpl::kChangedPropertyTreeIndex);
}

void LayerImpl::SetEffectTreeIndex(int index) {
  if (effect_tree_index_ == index) {
    return;
  }

  effect_tree_index_ = index;
  SetNeedsPushProperties(LayerImpl::kChangedPropertyTreeIndex);
}

int LayerImpl::render_target_effect_tree_index() const {
  EffectNode* effect_node = GetEffectTree().Node(effect_tree_index_);

  return GetEffectTree().GetRenderSurface(effect_tree_index_)
             ? effect_node->id
             : effect_node->target_id;
}

void LayerImpl::SetScrollTreeIndex(int index) {
  if (scroll_tree_index_ == index) {
    return;
  }

  scroll_tree_index_ = index;
  SetNeedsPushProperties(LayerImpl::kChangedPropertyTreeIndex);
}

void LayerImpl::SetOffsetToTransformParent(const gfx::Vector2dF& offset) {
  if (offset_to_transform_parent_ == offset) {
    return;
  }

  offset_to_transform_parent_ = offset;
  SetNeedsPushProperties();
}

void LayerImpl::PopulateSharedQuadState(viz::SharedQuadState* state,
                                        bool contents_opaque) const {
  EffectNode* effect_node = GetEffectTree().Node(effect_tree_index_);
  std::optional<gfx::Rect> clip_rect;
  if (draw_properties_.is_clipped) {
    clip_rect = draw_properties_.clip_rect;
  }
  state->SetAll(draw_properties_.target_space_transform, gfx::Rect(bounds()),
                draw_properties_.visible_layer_rect,
                draw_properties_.mask_filter_info, clip_rect, contents_opaque,
                draw_properties_.opacity,
                effect_node->HasRenderSurface() ? SkBlendMode::kSrcOver
                                                : effect_node->blend_mode,
                GetSortingContextId(),
                static_cast<uint32_t>(stable_id_for_shared_quad_state()),
                draw_properties_.is_fast_rounded_corner);
}

void LayerImpl::PopulateScaledSharedQuadState(viz::SharedQuadState* state,
                                              float layer_to_content_scale,
                                              bool contents_opaque) const {
  gfx::Size scaled_bounds =
      gfx::ScaleToCeiledSize(bounds(), layer_to_content_scale);
  gfx::Rect scaled_visible_layer_rect =
      gfx::ScaleToEnclosingRect(visible_layer_rect(), layer_to_content_scale);
  scaled_visible_layer_rect.Intersect(gfx::Rect(scaled_bounds));

  PopulateScaledSharedQuadStateWithContentRects(
      state, layer_to_content_scale, gfx::Rect(scaled_bounds),
      scaled_visible_layer_rect, contents_opaque);
}

void LayerImpl::PopulateScaledSharedQuadStateWithContentRects(
    viz::SharedQuadState* state,
    float layer_to_content_scale,
    const gfx::Rect& content_rect,
    const gfx::Rect& visible_content_rect,
    bool contents_opaque) const {
  gfx::Transform scaled_draw_transform =
      GetScaledDrawTransform(layer_to_content_scale);

  EffectNode* effect_node = GetEffectTree().Node(effect_tree_index_);
  std::optional<gfx::Rect> clip_rect;
  if (draw_properties().is_clipped) {
    clip_rect = draw_properties().clip_rect;
  }
  state->SetAll(scaled_draw_transform, content_rect, visible_content_rect,
                draw_properties().mask_filter_info, clip_rect, contents_opaque,
                draw_properties().opacity,
                effect_node->HasRenderSurface() ? SkBlendMode::kSrcOver
                                                : effect_node->blend_mode,
                GetSortingContextId(),
                static_cast<uint32_t>(stable_id_for_shared_quad_state()),
                draw_properties().is_fast_rounded_corner);
}

bool LayerImpl::WillDraw(DrawMode draw_mode,
                         viz::ClientResourceProvider* resource_provider) {
  if (visible_layer_rect().IsEmpty() ||
      draw_properties().occlusion_in_content_space.IsOccluded(
          visible_layer_rect())) {
    return false;
  }

  // Resourceless mode does not support non-default blend mode. If we draw,
  // the result will be just like kSrcOver which is not too bad for blend modes
  // other than kDstIn. For kDstIn mode, we should ignore the source because
  // otherwise we would draw a bad black mask over the destination.
  if (draw_mode == DRAW_MODE_RESOURCELESS_SOFTWARE) {
    const auto* effect_node = GetEffectTree().Node(effect_tree_index());
    if (effect_node && effect_node->blend_mode == SkBlendMode::kDstIn)
      return false;
  }

  return true;
}

bool LayerImpl::ShowDebugBorders(DebugBorderType type) const {
  return layer_tree_impl()->debug_state().show_debug_borders.test(type);
}

void LayerImpl::GetDebugBorderProperties(SkColor4f* color, float* width) const {
  float device_scale_factor =
      layer_tree_impl() ? layer_tree_impl()->device_scale_factor() : 1;

  if (draws_content_) {
    *color = DebugColors::ContentLayerBorderColor();
    *width = DebugColors::ContentLayerBorderWidth(device_scale_factor);
    return;
  }

  *color = DebugColors::ContainerLayerBorderColor();
  *width = DebugColors::ContainerLayerBorderWidth(device_scale_factor);
}

void LayerImpl::AppendDebugBorderQuad(
    viz::CompositorRenderPass* render_pass,
    const gfx::Rect& quad_rect,
    const viz::SharedQuadState* shared_quad_state,
    AppendQuadsData* append_quads_data) const {
  SkColor4f color;
  float width;
  GetDebugBorderProperties(&color, &width);
  AppendDebugBorderQuad(render_pass, quad_rect, shared_quad_state,
                        append_quads_data, color, width);
}

void LayerImpl::AppendDebugBorderQuad(
    viz::CompositorRenderPass* render_pass,
    const gfx::Rect& quad_rect,
    const viz::SharedQuadState* shared_quad_state,
    AppendQuadsData* append_quads_data,
    SkColor4f color,
    float width) const {
  if (!ShowDebugBorders(DebugBorderType::LAYER))
    return;

  // This is the debug border quad layer size. The mojo serialization will fail
  // if the area overflows, so just drop this debug border quad in that case to
  // avoid crashes.
  if (!quad_rect.size().GetCheckedArea().IsValid())
    return;

  gfx::Rect visible_quad_rect(quad_rect);
  auto* debug_border_quad =
      render_pass->CreateAndAppendDrawQuad<viz::DebugBorderDrawQuad>();
  debug_border_quad->SetNew(shared_quad_state, quad_rect, visible_quad_rect,
                            color, width);
  if (contents_opaque()) {
    // When opaque, draw a second inner border that is thicker than the outer
    // border, but more transparent.
    static const float kFillOpacity = 0.3f;
    SkColor4f fill_color = color;
    fill_color.fA *= kFillOpacity;
    float fill_width = width * 3;
    gfx::Rect fill_rect = quad_rect;
    fill_rect.Inset(fill_width / 2.f);
    if (fill_rect.IsEmpty())
      return;
    gfx::Rect visible_fill_rect =
        gfx::IntersectRects(visible_quad_rect, fill_rect);
    auto* fill_quad =
        render_pass->CreateAndAppendDrawQuad<viz::DebugBorderDrawQuad>();
    fill_quad->SetNew(shared_quad_state, fill_rect, visible_fill_rect,
                      fill_color, fill_width);
  }
}

void LayerImpl::GetContentsResourceId(viz::ResourceId* resource_id,
                                      gfx::Size* resource_size,
                                      gfx::SizeF* resource_uv_size) const {
  NOTREACHED();
}

gfx::Vector2dF LayerImpl::ScrollBy(const gfx::Vector2dF& scroll) {
  ScrollTree& scroll_tree = GetScrollTree();
  ScrollNode* scroll_node = scroll_tree.Node(scroll_tree_index());
  DCHECK(scroll_node);
  return scroll_tree.ScrollBy(*scroll_node, scroll, layer_tree_impl());
}

void LayerImpl::SetTouchActionRegion(TouchActionRegion region) {
  // Avoid recalculating the cached |all_touch_action_regions_| value.
  if (touch_action_region_ == region)
    return;
  touch_action_region_ = std::move(region);
  all_touch_action_regions_ = nullptr;
  SetNeedsPushProperties();
}

const Region& LayerImpl::GetAllTouchActionRegions() const {
  if (!all_touch_action_regions_) {
    all_touch_action_regions_ =
        std::make_unique<Region>(touch_action_region_.GetAllRegions());
  } else {
    // Ensure the cached value of |all_touch_action_regions_| is up to date.
    DCHECK_EQ(touch_action_region_.GetAllRegions(), *all_touch_action_regions_);
  }
  return *all_touch_action_regions_;
}

void LayerImpl::SetCaptureBounds(viz::RegionCaptureBounds bounds) {
  if (rare_properties_ || !bounds.IsEmpty()) {
    EnsureRareProperties().capture_bounds = std::move(bounds);
    SetNeedsPushProperties();
  }
}

std::unique_ptr<LayerImpl> LayerImpl::CreateLayerImpl(
    LayerTreeImpl* tree_impl) const {
  return LayerImpl::Create(tree_impl, layer_id_);
}

bool LayerImpl::IsSnappedToPixelGridInTarget() {
  return false;
}

void LayerImpl::PushPropertiesTo(LayerImpl* layer) {
  DCHECK(layer->IsActive());

  if (GetChangeFlag(kChangedPropertyTreeIndex)) {
    layer->transform_tree_index_ = transform_tree_index_;
    layer->has_transform_node_ = has_transform_node_;
    layer->effect_tree_index_ = effect_tree_index_;
    layer->clip_tree_index_ = clip_tree_index_;
    layer->scroll_tree_index_ = scroll_tree_index_;
  }

  if (GetChangeFlag(kChangedGeneralProperty)) {
    // The element id should be set first because other setters may
    // depend on it. Referencing element id on a layer is
    // deprecated. http://crbug.com/709137
    layer->SetElementId(element_id_);

    layer->offset_to_transform_parent_ = offset_to_transform_parent_;
    layer->contents_opaque_ = contents_opaque_;
    layer->contents_opaque_for_text_ = contents_opaque_for_text_;
    layer->should_check_backface_visibility_ =
        should_check_backface_visibility_;
    layer->draws_content_ = draws_content_;
    layer->hit_test_opaqueness_ = hit_test_opaqueness_;
    layer->touch_action_region_ = touch_action_region_;
    layer->all_touch_action_regions_ = ClonePtr(all_touch_action_regions_);
    layer->background_color_ = background_color_;
    layer->safe_opaque_background_color_ = safe_opaque_background_color_;

    if (layer_property_changed_not_from_property_trees_ ||
        layer_property_changed_from_property_trees_) {
      layer->layer_tree_impl()->set_needs_update_draw_properties();
    }
    if (layer_property_changed_not_from_property_trees_) {
      layer->layer_property_changed_not_from_property_trees_ = true;
    }
    if (layer_property_changed_from_property_trees_) {
      layer->layer_property_changed_from_property_trees_ = true;
    }

#if BUILDFLAG(ARKWEB_SAME_LAYER)
    layer_impl_utils_->LayerImplPushPropertiesTo(layer);
#endif

    layer->SetBounds(bounds_);

    layer->UnionUpdateRect(update_rect_);

    layer->UpdateDebugInfo(debug_info_.get());

    if (rare_properties_) {
      layer->rare_properties_ =
          std::make_unique<RareProperties>(*rare_properties_);
    } else {
      layer->rare_properties_.reset();
    }
  }

  if (layer_tree_impl()->settings().TreesInVizInClientProcess()) {
    // Ensure updates also propagate to the display tree on its next update.
    layer->SetNeedsPushProperties(changed_properties_);
  }

  // Reset any state that should be cleared for the next update.
  ResetChangeTracking();

#if BUILDFLAG(ARKWEB_CUSTOM_VIDEO_PLAYER)
  layer->layer_impl_utils()->SetShouldInterceptTouchEvent(
      layer_impl_utils_->ShouldInterceptTouchEvent());
#endif
}

bool LayerImpl::IsAffectedByPageScale() const {
  TransformTree& transform_tree = GetTransformTree();
  return transform_tree.Node(transform_tree_index())
      ->in_subtree_of_page_scale_layer;
}

#if BUILDFLAG(ARKWEB_WEBGL)
bool LayerImpl::ShouldDeferImplInvalidation() const {
  return false;
}
#endif

DamageReasonSet LayerImpl::GetDamageReasonsFromLayerPropertyChange() const {
  DamageReasonSet reasons;
  if (layer_property_changed_not_from_property_trees_ ||
      layer_property_changed_from_property_trees_ ||
      GetPropertyTrees()->full_tree_damaged()) {
    reasons.Put(DamageReason::kUntracked);
  }
  if (transform_tree_index() != kInvalidPropertyNodeId) {
    TransformNode* transform_node =
        GetTransformTree().Node(transform_tree_index());
    reasons.PutAll(transform_node->damage_reasons());
  }
  if (effect_tree_index() != kInvalidPropertyNodeId) {
    EffectNode* effect_node = GetEffectTree().Node(effect_tree_index());
    if (effect_node && effect_node->effect_changed) {
      reasons.Put(DamageReason::kUntracked);
    }
  }
  return reasons;
}

bool LayerImpl::LayerPropertyChanged() const {
  return layer_property_changed_not_from_property_trees_ ||
         LayerPropertyChangedFromPropertyTrees();
}

bool LayerImpl::LayerPropertyChangedFromPropertyTrees() const {
  if (layer_property_changed_from_property_trees_ ||
      GetPropertyTrees()->full_tree_damaged())
    return true;
  if (transform_tree_index() == kInvalidPropertyNodeId)
    return false;
  TransformNode* transform_node =
      GetTransformTree().Node(transform_tree_index());
  if (transform_node && transform_node->transform_changed()) {
    return true;
  }
  if (effect_tree_index() == kInvalidPropertyNodeId)
    return false;
  EffectNode* effect_node = GetEffectTree().Node(effect_tree_index());
  if (effect_node && effect_node->effect_changed)
    return true;
  return false;
}

bool LayerImpl::LayerPropertyChangedNotFromPropertyTrees() const {
  return layer_property_changed_not_from_property_trees_;
}

void LayerImpl::NoteLayerPropertyChanged() {
  layer_property_changed_not_from_property_trees_ = true;
  SetNeedsPushProperties();
  layer_tree_impl()->set_needs_update_draw_properties();
}

void LayerImpl::NoteLayerPropertyChangedFromPropertyTrees() {
  layer_property_changed_from_property_trees_ = true;
  SetNeedsPushProperties();
  layer_tree_impl()->set_needs_update_draw_properties();
}

void LayerImpl::ValidateQuadResourcesInternal(viz::DrawQuad* quad) const {
#if DCHECK_IS_ON()
  const viz::ClientResourceProvider* resource_provider =
      layer_tree_impl_->resource_provider();
  if (quad->resource_id != viz::kInvalidResourceId) {
    resource_provider->ValidateResource(quad->resource_id);
  }
#endif
}

gfx::Transform LayerImpl::GetScaledDrawTransform(
    float layer_to_content_scale) const {
  gfx::Transform scaled_draw_transform =
      draw_properties_.target_space_transform;
  scaled_draw_transform.Scale(SK_Scalar1 / layer_to_content_scale,
                              SK_Scalar1 / layer_to_content_scale);
  return scaled_draw_transform;
}

void LayerImpl::ResetChangeTracking() {
  layer_property_changed_not_from_property_trees_ = false;
  layer_property_changed_from_property_trees_ = false;
  needs_push_properties_ = false;
  changed_properties_ = 0;

  update_rect_.SetRect(0, 0, 0, 0);
  if (debug_info_)
    debug_info_->invalidations.clear();
}

bool LayerImpl::IsActive() const {
  return layer_tree_impl_->IsActiveTree();
}

gfx::Size LayerImpl::bounds() const {
  if (!is_inner_viewport_scroll_layer_)
    return bounds_;

  auto viewport_bounds_delta = gfx::ToCeiledVector2d(
      GetPropertyTrees()->inner_viewport_scroll_bounds_delta());
  return gfx::Size(bounds_.width() + viewport_bounds_delta.x(),
                   bounds_.height() + viewport_bounds_delta.y());
}

void LayerImpl::SetBounds(const gfx::Size& bounds) {
  if (bounds_ == bounds)
    return;

  bounds_ = bounds;
  NoteLayerPropertyChanged();
  SetNeedsPushProperties();
}

bool LayerImpl::IsScrollbarLayer() const {
  return false;
}

bool LayerImpl::IsScrollerOrScrollbar() const {
  DCHECK(!layer_tree_impl()->settings().enable_hit_test_opaqueness);
  return IsScrollbarLayer() ||
         GetScrollTree().FindNodeFromElementId(element_id());
}

void LayerImpl::SetDrawsContent(bool draws_content) {
  if (draws_content_ == draws_content)
    return;

  draws_content_ = draws_content;
  NoteLayerPropertyChanged();
  SetNeedsPushProperties();
}

void LayerImpl::SetHitTestOpaqueness(HitTestOpaqueness opaqueness) {
  if (hit_test_opaqueness_ == opaqueness) {
    return;
  }

  hit_test_opaqueness_ = opaqueness;
  NoteLayerPropertyChanged();
  SetNeedsPushProperties();
}

bool LayerImpl::HitTestable() const {
  EffectTree& effect_tree = GetEffectTree();
  // TODO(sunxd): remove or refactor SetHideLayerAndSubtree, or move this logic
  // to subclasses of Layer. See https://crbug.com/595843 and
  // https://crbug.com/931865.
  // The bit |subtree_hidden| can only be true for ui::Layers. Other layers are
  // not supposed to set this bit.
  if (const EffectNode* node = effect_tree.Node(effect_tree_index())) {
    if (node->subtree_hidden) {
      return false;
    }
  }
  return hit_test_opaqueness_ != HitTestOpaqueness::kTransparent;
}

bool LayerImpl::OpaqueToHitTest() const {
  return HitTestable() && hit_test_opaqueness_ == HitTestOpaqueness::kOpaque &&
         !GetEffectTree()
              .Node(effect_tree_index())
              ->node_or_ancestor_has_fast_rounded_corner;
}

void LayerImpl::SetBackgroundColor(SkColor4f background_color) {
  if (background_color_ == background_color)
    return;

  background_color_ = background_color;
  NoteLayerPropertyChanged();
  SetNeedsPushProperties();
}

void LayerImpl::SetSafeOpaqueBackgroundColor(SkColor4f background_color) {
  if (safe_opaque_background_color_ == background_color) {
    return;
  }

  safe_opaque_background_color_ = background_color;
  SetNeedsPushProperties();
}

void LayerImpl::SetContentsOpaque(bool opaque) {
  if (contents_opaque_ == opaque && contents_opaque_for_text_ == opaque) {
    return;
  }

  contents_opaque_ = opaque;
  contents_opaque_for_text_ = opaque;
  SetNeedsPushProperties();
}

void LayerImpl::SetContentsOpaqueForText(bool opaque) {
  DCHECK(!contents_opaque_ || opaque);

  if (contents_opaque_for_text_ == opaque) {
    return;
  }

  contents_opaque_for_text_ = opaque;
  SetNeedsPushProperties();
}

float LayerImpl::Opacity() const {
  if (const EffectNode* node = GetEffectTree().Node(effect_tree_index()))
    return node->opacity;
  else
    return 1.f;
}

void LayerImpl::SetElementId(ElementId element_id) {
  if (element_id == element_id_)
    return;

  TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"), "LayerImpl::SetElementId",
               "element", element_id.ToString());
  element_id_ = element_id;
  SetNeedsPushProperties();
}

void LayerImpl::SetShouldCheckBackfaceVisibility(
    bool should_check_backface_visibility) {
  if (should_check_backface_visibility_ == should_check_backface_visibility) {
    return;
  }

  should_check_backface_visibility_ = should_check_backface_visibility;
  SetNeedsPushProperties();
}

void LayerImpl::UnionUpdateRect(const gfx::Rect& update_rect) {
  if (update_rect_ == update_rect) {
    return;
  }

  update_rect_.Union(update_rect);
  SetNeedsPushProperties();
}

gfx::Rect LayerImpl::GetDamageRect() const {
  return gfx::Rect();
}

DamageReasonSet LayerImpl::GetDamageReasons() const {
  DamageReasonSet reasons = GetDamageReasonsFromLayerPropertyChange();
  if (!update_rect_.IsEmpty() || !GetDamageRect().IsEmpty()) {
    reasons.Put(DamageReason::kUntracked);
  }
  return reasons;
}

void LayerImpl::SetCurrentScrollOffset(const gfx::PointF& scroll_offset) {
  DCHECK(IsActive());
  if (GetScrollTree().SetScrollOffset(element_id(), scroll_offset)) {
    layer_tree_impl()->DidUpdateScrollOffset(
        element_id(), /*pushed_from_main_or_pending_tree=*/false);
  }
}

SimpleEnclosedRegion LayerImpl::VisibleOpaqueRegion() const {
  if (contents_opaque())
    return SimpleEnclosedRegion(visible_layer_rect());
  return SimpleEnclosedRegion();
}

void LayerImpl::DidBeginTracing() {}

void LayerImpl::ReleaseResources() {}

void LayerImpl::OnPurgeMemory() {
  ReleaseResources();
}

void LayerImpl::ReleaseTileResources() {}

void LayerImpl::RecreateTileResources() {}

void LayerImpl::SetNeedsPushProperties(uint8_t changed_props) {
  // We never push properties from the active tree unless using a LayerContext.
  if (layer_tree_impl()->IsActiveTree() &&
      !layer_tree_impl()->settings().TreesInVizInClientProcess()) {
    return;
  }

  if (::features::IsCCSlimmingEnabled()) {
    changed_properties_ |= changed_props;
  } else {
    changed_properties_ = LayerImpl::kChangedAllProperties;
  }
  if (!needs_push_properties_) {
    needs_push_properties_ = true;
    layer_tree_impl()->AddLayerShouldPushProperties(this);
  }
}

void LayerImpl::GetAllPrioritizedTilesForTracing(
    std::vector<PrioritizedTile>* prioritized_tiles) const {
}

void LayerImpl::AsValueInto(base::trace_event::TracedValue* state) const {
  // The output is consumed at least by
  // 1. DevTools for showing layer tree information for frame snapshots in
  //    performance timeline (third_party/devtools-frontend/src/front_end/
  //    models/timeline_model/TracingLayerTree.ts),
  // 2. trace_viewer
  //    (third_party/catapult/tracing/tracing/extras/chrome/cc/layer_impl.html)
  //    Note that trace_viewer uses "namingStyle" style instead of
  //    "naming_style". The difference is intentional and the names are
  //    converted automatically, but we need to keep this in mind when we
  //    search trace_viewer code for the usage of the names here.
  // When making changes here, we need to make sure we won't break these
  // consumers.
  viz::TracedValue::MakeDictIntoImplicitSnapshotWithCategory(
      TRACE_DISABLED_BY_DEFAULT("cc.debug"), state, "cc::LayerImpl",
      LayerTypeAsString(GetLayerType()), viz::TracedValue::Id(this));
  state->SetInteger("layer_id", id());
  MathUtil::AddToTracedValue("bounds", bounds_, state);

  state->SetDouble("opacity", Opacity());

  // For backward-compatibility of DevTools front-end.
  MathUtil::AddToTracedValue("position", gfx::PointF(), state);

  state->SetInteger("transform_tree_index", transform_tree_index());
  state->SetInteger("clip_tree_index", clip_tree_index());
  state->SetInteger("effect_tree_index", effect_tree_index());
  state->SetInteger("scroll_tree_index", scroll_tree_index());

  state->SetInteger("sorting_context_id", GetSortingContextId());

  state->SetInteger("draws_content", draws_content());
  state->SetInteger("gpu_memory_usage",
                    base::saturated_cast<int>(GPUMemoryUsageInBytes()));

  if (element_id_)
    element_id_.AddToTracedValue(state);

  if (!ScreenSpaceTransform().IsIdentity())
    MathUtil::AddToTracedValue("screen_space_transform", ScreenSpaceTransform(),
                               state);

  bool clipped;
  gfx::QuadF layer_quad =
      MathUtil::MapQuad(ScreenSpaceTransform(),
                        gfx::QuadF(gfx::RectF(gfx::Rect(bounds()))), &clipped);
  MathUtil::AddToTracedValue("layer_quad", layer_quad, state);
  if (!GetAllTouchActionRegions().IsEmpty()) {
    state->BeginArray("all_touch_action_regions");
    GetAllTouchActionRegions().AsValueInto(state);
    state->EndArray();
  }

  state->BeginArray("wheel_event_handler_region");
  wheel_event_handler_region().AsValueInto(state);
  state->EndArray();

  // TODO(crbug.com/358408565): At least DevTools reads from trace using this
  // name.
  state->BeginArray("non_fast_scrollable_region");
  main_thread_scroll_hit_test_region().AsValueInto(state);
  state->EndArray();

  state->SetBoolean("hit_testable", HitTestable());
  state->SetBoolean("opaque_to_hit_test", OpaqueToHitTest());
  state->SetBoolean("contents_opaque", contents_opaque());

  if (debug_info_) {
    state->SetString("layer_name", debug_info_->name);
    if (debug_info_->owner_node_id)
      state->SetInteger("owner_node", debug_info_->owner_node_id);

    if (debug_info_->compositing_reasons.size()) {
      state->BeginArray("compositing_reasons");
      for (const char* reason : debug_info_->compositing_reasons)
        state->AppendString(reason);
      state->EndArray();
    }

    if (debug_info_->compositing_reason_ids.size()) {
      state->BeginArray("compositing_reason_ids");
      for (const char* reason_id : debug_info_->compositing_reason_ids)
        state->AppendString(reason_id);
      state->EndArray();
    }

    if (debug_info_->invalidations.size()) {
      state->BeginArray("annotated_invalidation_rects");
      for (auto& invalidation : debug_info_->invalidations) {
        state->BeginDictionary();
        MathUtil::AddToTracedValue("geometry_rect", invalidation.rect, state);
        state->SetString("reason", invalidation.reason);
        state->SetString("client", invalidation.client);
        state->EndDictionary();
      }
      state->EndArray();
    }
  }
}

std::string LayerImpl::ToString() const {
  base::trace_event::TracedValueJSON value;
  AsValueInto(&value);
  return value.ToFormattedJSON();
}

size_t LayerImpl::GPUMemoryUsageInBytes() const { return 0; }

void LayerImpl::RunMicroBenchmark(MicroBenchmarkImpl* benchmark) {
  benchmark->RunOnLayer(this);
}

gfx::Transform LayerImpl::DrawTransform() const {
  // Only drawn layers have up-to-date draw properties.
  if (!contributes_to_drawn_render_surface()) {
      return draw_property_utils::DrawTransform(this, GetTransformTree(),
                                                GetEffectTree());
  }

  return draw_properties().target_space_transform;
}

gfx::Transform LayerImpl::ScreenSpaceTransform() const {
  // Only drawn layers have up-to-date draw properties.
  if (!contributes_to_drawn_render_surface()) {
    return draw_property_utils::ScreenSpaceTransform(this, GetTransformTree());
  }

  return draw_properties().screen_space_transform;
}

int LayerImpl::GetSortingContextId() const {
  return GetTransformTree().Node(transform_tree_index())->sorting_context_id;
}

Region LayerImpl::GetInvalidationRegionForDebugging() {
  return Region(update_rect_);
}

gfx::Rect LayerImpl::GetEnclosingVisibleRectInTargetSpace() const {
  return GetScaledEnclosingVisibleRectInTargetSpace(1.0f);
}

gfx::Rect LayerImpl::GetScaledEnclosingVisibleRectInTargetSpace(
    float scale) const {
  // TODO(oshima): Define an utility function to scale layer and conslidate with
  // the logic in ComputeDrawPropertiesOfVisibleLayers() in
  // draw_property_util.cc.
  DCHECK_GT(scale, 0.0);

  bool only_draws_visible_content = GetPropertyTrees()
                                        ->effect_tree()
                                        .Node(effect_tree_index())
                                        ->only_draws_visible_content;
  gfx::Rect drawable_bounds = visible_layer_rect();
  if (!only_draws_visible_content) {
    drawable_bounds = gfx::Rect(bounds());
  }
  gfx::Transform scaled_draw_transform = GetScaledDrawTransform(scale);
  gfx::Rect scaled_bounds = ScaleToEnclosingRect(drawable_bounds, scale);

  return MathUtil::MapEnclosingClippedRect(scaled_draw_transform,
                                           scaled_bounds);
}

RenderSurfaceImpl* LayerImpl::render_target() {
  return GetEffectTree().GetRenderSurface(render_target_effect_tree_index());
}

const RenderSurfaceImpl* LayerImpl::render_target() const {
  return GetEffectTree().GetRenderSurface(render_target_effect_tree_index());
}

gfx::Vector2dF LayerImpl::GetIdealContentsScale() const {
  const auto& transform = ScreenSpaceTransform();
  std::optional<gfx::Vector2dF> transform_scales =
      gfx::TryComputeTransform2dScaleComponents(transform);
  if (transform_scales) {
    // TODO(crbug.com/40176440): Remove this scale cap.
    float scale_cap = GetPreferredRasterScale(*transform_scales);
    transform_scales->SetToMin(gfx::Vector2dF(scale_cap, scale_cap));
    return *transform_scales;
  }

  // TryComputeTransform2dScaleComponents couldn't compute a scale because of
  // perspective components in the transform.

  float page_scale = IsAffectedByPageScale()
                         ? layer_tree_impl()->current_page_scale_factor()
                         : 1.f;
  float device_scale = layer_tree_impl()->device_scale_factor();

  float default_scale = page_scale * device_scale;

  // TODO(crbug.com/40176440): This function should return a 2D scale.
  float scale = gfx::ComputeApproximateMaxScale(transform);

  const int kMaxTilesToCoverLayerDimension = 5;
  // Cap the scale in a way that it should be covered by at most
  // |kMaxTilesToCoverLayerDimension|^2 default tile sizes. If this is left
  // uncapped, then we can fairly easily use too much memory (or too many
  // tiles). See crbug.com/752382 for an example of such a page. Note that
  // because this is an approximation anyway, it's fine to use a smaller scale
  // that desired. On top of this, the layer has a perspective transform so
  // technically it could all be within the viewport, so it's important for us
  // to have a reasonable scale here. The scale we use would also be at least
  // |default_scale|, as checked below.
  float scale_cap = std::min(
      (layer_tree_impl()->settings().default_tile_size.width() - 2) *
          kMaxTilesToCoverLayerDimension / static_cast<float>(bounds().width()),
      (layer_tree_impl()->settings().default_tile_size.height() - 2) *
          kMaxTilesToCoverLayerDimension /
          static_cast<float>(bounds().height()));
  scale = std::min(scale, scale_cap);

  // Since we're approximating the scale anyway, round it to the nearest
  // integer to prevent jitter when animating the transform.
  scale = std::round(scale);

  // Don't let the scale fall below the default scale.
  scale = std::max(scale, default_scale);
  return gfx::Vector2dF(scale, scale);
}

float LayerImpl::GetIdealContentsScaleKey() const {
  return GetPreferredRasterScale(GetIdealContentsScale());
}

float LayerImpl::GetPreferredRasterScale(
    gfx::Vector2dF raster_space_scale_factor) {
  constexpr float kMaxScaleRatio = 5.f;
  float lower_scale =
      std::min(raster_space_scale_factor.x(), raster_space_scale_factor.y());
  float higher_scale =
      std::max(raster_space_scale_factor.x(), raster_space_scale_factor.y());
  return std::min(kMaxScaleRatio * lower_scale, higher_scale);
}

void LayerImpl::SetFilterQuality(PaintFlags::FilterQuality filter_quality) {
  if (GetFilterQuality() == filter_quality) {
    return;
  }
  EnsureRareProperties().filter_quality = filter_quality;
  SetNeedsPushProperties();
}

void LayerImpl::SetDynamicRangeLimit(
    PaintFlags::DynamicRangeLimitMixture dynamic_range_limit) {
  if (GetDynamicRangeLimit() == dynamic_range_limit) {
    return;
  }
  EnsureRareProperties().dynamic_range_limit = dynamic_range_limit;
  NoteLayerPropertyChanged();
  SetNeedsPushProperties();
}

PropertyTrees* LayerImpl::GetPropertyTrees() const {
  return layer_tree_impl_->property_trees();
}

ClipTree& LayerImpl::GetClipTree() const {
  return GetPropertyTrees()->clip_tree_mutable();
}

EffectTree& LayerImpl::GetEffectTree() const {
  return GetPropertyTrees()->effect_tree_mutable();
}

ScrollTree& LayerImpl::GetScrollTree() const {
  return GetPropertyTrees()->scroll_tree_mutable();
}

TransformTree& LayerImpl::GetTransformTree() const {
  return GetPropertyTrees()->transform_tree_mutable();
}

void LayerImpl::EnsureValidPropertyTreeIndices() const {
  DCHECK(GetTransformTree().Node(transform_tree_index()));
  DCHECK(GetEffectTree().Node(effect_tree_index()));
  DCHECK(GetClipTree().Node(clip_tree_index()));
  DCHECK(GetScrollTree().Node(scroll_tree_index()));
}

bool LayerImpl::is_surface_layer() const {
  return false;
}

std::string LayerImpl::DebugName() const {
  return debug_info_ ? debug_info_->name : "";
}

gfx::ContentColorUsage LayerImpl::GetContentColorUsage() const {
  return gfx::ContentColorUsage::kSRGB;
}

viz::ViewTransitionElementResourceId LayerImpl::ViewTransitionResourceId()
    const {
  return viz::ViewTransitionElementResourceId();
}

}  // namespace cc