// Copyright 2014 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.

#ifndef CC_PAINT_DISPLAY_ITEM_LIST_H_
#define CC_PAINT_DISPLAY_ITEM_LIST_H_

#include <stddef.h>

#include <memory>
#include <utility>
#include <vector>

#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/trace_event/trace_event.h"
#include "cc/base/rtree.h"
#include "cc/paint/discardable_image_map.h"
#include "cc/paint/image_id.h"
#include "cc/paint/paint_export.h"
#include "cc/paint/paint_op_buffer.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "ui/gfx/color_space.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"

class SkCanvas;

namespace gpu {
namespace raster {
class RasterImplementation;
class RasterImplementationGLES;
}  // namespace raster
}  // namespace gpu

namespace base {
namespace trace_event {
class TracedValue;
}
}

namespace cc {
class PaintWorkletImageProvider;

// DisplayItemList is a container of paint operations. One can populate the list
// using StartPaint, followed by push{,_with_data,_with_array} functions
// specialized with ops coming from paint_op_buffer.h. Internally, the
// DisplayItemList contains a PaintOpBuffer and defers op saving to it.
// Additionally, it store some meta information about the paint operations.
// Specifically, it creates an rtree to assist in rasterization: when
// rasterizing a rect, it queries the rtree to extract only the byte offsets of
// the ops required and replays those into a canvas.
class CC_PAINT_EXPORT DisplayItemList
    : public base::RefCountedThreadSafe<DisplayItemList> {
 public:
  // TODO(vmpstr): It would be cool if we didn't need this, and instead used
  // PaintOpBuffer directly when we needed to release this as a paint op buffer.
  enum UsageHint { kTopLevelDisplayItemList, kToBeReleasedAsPaintOpBuffer };

  explicit DisplayItemList(UsageHint = kTopLevelDisplayItemList);

  void Raster(SkCanvas* canvas,
              ImageProvider* image_provider = nullptr,
              PaintWorkletImageProvider* = nullptr) const;

  void StartPaint() {
#if DCHECK_IS_ON()
    DCHECK(!IsPainting());
    current_range_start_ = paint_op_buffer_.size();
#endif
  }

  // Push functions construct a new op on the paint op buffer, while maintaining
  // bookkeeping information. Must be called after invoking StartPaint().
  // Returns the id (which is an opaque value) of the operation that can be used
  // in UpdateSaveLayerBounds().
  template <typename T, typename... Args>
  size_t push(Args&&... args) {
#if DCHECK_IS_ON()
    DCHECK(IsPainting());
#endif
    size_t offset = paint_op_buffer_.next_op_offset();
    if (usage_hint_ == kTopLevelDisplayItemList)
      offsets_.push_back(offset);
    paint_op_buffer_.push<T>(std::forward<Args>(args)...);
    return offset;
  }

  // Called by blink::PaintChunksToCcLayer when an effect ends, to update the
  // bounds of a SaveLayer[Alpha]Op which was emitted when the effect started.
  // This is needed because blink doesn't know the bounds when an effect starts.
  // Don't add other mutation methods like this if there is better alternative.
  void UpdateSaveLayerBounds(size_t id, const SkRect& bounds) {
    paint_op_buffer_.UpdateSaveLayerBounds(id, bounds);
  }

  void EndPaintOfUnpaired(const gfx::Rect& visual_rect) {
#if DCHECK_IS_ON()
    DCHECK(IsPainting());
    current_range_start_ = kNotPainting;
#endif
    if (usage_hint_ == kToBeReleasedAsPaintOpBuffer)
      return;

    while (visual_rects_.size() < paint_op_buffer_.size())
      visual_rects_.push_back(visual_rect);
    GrowCurrentBeginItemVisualRect(visual_rect);
  }

  void EndPaintOfPairedBegin(const gfx::Rect& visual_rect = gfx::Rect()) {
#if DCHECK_IS_ON()
    DCHECK(IsPainting());
    DCHECK_LT(current_range_start_, paint_op_buffer_.size());
    current_range_start_ = kNotPainting;
#endif
    if (usage_hint_ == kToBeReleasedAsPaintOpBuffer)
      return;

    DCHECK_LT(visual_rects_.size(), paint_op_buffer_.size());
    size_t count = paint_op_buffer_.size() - visual_rects_.size();
    for (size_t i = 0; i < count; ++i)
      visual_rects_.push_back(visual_rect);
    begin_paired_indices_.push_back(
        std::make_pair(visual_rects_.size() - 1, count));
  }

  void EndPaintOfPairedEnd() {
#if DCHECK_IS_ON()
    DCHECK(IsPainting());
    DCHECK_LT(current_range_start_, paint_op_buffer_.size());
    current_range_start_ = kNotPainting;
#endif
    if (usage_hint_ == kToBeReleasedAsPaintOpBuffer)
      return;

    DCHECK(begin_paired_indices_.size());
    size_t last_begin_index = begin_paired_indices_.back().first;
    size_t last_begin_count = begin_paired_indices_.back().second;
    DCHECK_GT(last_begin_count, 0u);
    DCHECK_GE(last_begin_index, last_begin_count - 1);

    // Copy the visual rect at |last_begin_index| to all indices that constitute
    // the begin item. Note that because we possibly reallocate the
    // |visual_rects_| buffer below, we need an actual copy instead of a const
    // reference which can become dangling.
    auto visual_rect = visual_rects_[last_begin_index];
    for (size_t i = last_begin_index - last_begin_count + 1;
         i < last_begin_index; ++i) {
      visual_rects_[i] = visual_rect;
    }
    begin_paired_indices_.pop_back();

    // Copy the visual rect of the matching begin item to the end item(s).
    while (visual_rects_.size() < paint_op_buffer_.size())
      visual_rects_.push_back(visual_rect);

    // The block that ended needs to be included in the bounds of the enclosing
    // block.
    GrowCurrentBeginItemVisualRect(visual_rect);
  }

  // Called after all items are appended, to process the items.
  void Finalize();

  int NumSlowPaths() const { return paint_op_buffer_.numSlowPaths(); }
  bool HasNonAAPaint() const { return paint_op_buffer_.HasNonAAPaint(); }

  // This gives the total number of PaintOps.
  size_t TotalOpCount() const { return paint_op_buffer_.total_op_count(); }
  size_t BytesUsed() const;

  const DiscardableImageMap& discardable_image_map() const {
    return image_map_;
  }
  base::flat_map<PaintImage::Id, PaintImage::DecodingMode>
  TakeDecodingModeMap() {
    return image_map_.TakeDecodingModeMap();
  }

  void EmitTraceSnapshot() const;
  void GenerateDiscardableImagesMetadata();

  gfx::Rect VisualRectForTesting(int index) { return visual_rects_[index]; }

  // Generate a PaintRecord from this DisplayItemList, leaving |this| in
  // an empty state.
  sk_sp<PaintRecord> ReleaseAsRecord();

  // If a rectangle is solid color, returns that color. |max_ops_to_analyze|
  // indicates the maximum number of draw ops we consider when determining if a
  // rectangle is solid color.
  bool GetColorIfSolidInRect(const gfx::Rect& rect,
                             SkColor* color,
                             int max_ops_to_analyze = 1);

 private:
  FRIEND_TEST_ALL_PREFIXES(DisplayItemListTest, AsValueWithNoOps);
  FRIEND_TEST_ALL_PREFIXES(DisplayItemListTest, AsValueWithOps);
  friend gpu::raster::RasterImplementation;
  friend gpu::raster::RasterImplementationGLES;

  ~DisplayItemList();

  void Reset();

  std::unique_ptr<base::trace_event::TracedValue> CreateTracedValue(
      bool include_items) const;

  // If we're currently within a paired display item block, unions the
  // given visual rect with the begin display item's visual rect.
  void GrowCurrentBeginItemVisualRect(const gfx::Rect& visual_rect) {
    DCHECK_EQ(usage_hint_, kTopLevelDisplayItemList);
    if (!begin_paired_indices_.empty())
      visual_rects_[begin_paired_indices_.back().first].Union(visual_rect);
  }

  // RTree stores indices into the paint op buffer.
  // TODO(vmpstr): Update the rtree to store offsets instead.
  RTree<size_t> rtree_;
  DiscardableImageMap image_map_;
  PaintOpBuffer paint_op_buffer_;

  // The visual rects associated with each of the display items in the
  // display item list. These rects are intentionally kept separate because they
  // are used to decide which ops to walk for raster.
  std::vector<gfx::Rect> visual_rects_;
  // Byte offsets associated with each of the ops.
  std::vector<size_t> offsets_;
  // A stack of pairs of indices and counts. The indices are into the
  // |visual_rects_| for each paired begin range that hasn't been closed. The
  // counts refer to the number of visual rects in that begin sequence that end
  // with the index.
  std::vector<std::pair<size_t, size_t>> begin_paired_indices_;

#if DCHECK_IS_ON()
  // While recording a range of ops, this is the position in the PaintOpBuffer
  // where the recording started.
  bool IsPainting() const { return current_range_start_ != kNotPainting; }
  const size_t kNotPainting = static_cast<size_t>(-1);
  size_t current_range_start_ = kNotPainting;
#endif

  UsageHint usage_hint_;

  friend class base::RefCountedThreadSafe<DisplayItemList>;
  FRIEND_TEST_ALL_PREFIXES(DisplayItemListTest, BytesUsed);
  DISALLOW_COPY_AND_ASSIGN(DisplayItemList);
};

}  // namespace cc

#endif  // CC_PAINT_DISPLAY_ITEM_LIST_H_
