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

#include "cc/raster/raster_source.h"

#include <stddef.h>

#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "cc/base/math_util.h"
#include "cc/base/region.h"
#include "cc/debug/debug_colors.h"
#include "cc/paint/display_item_list.h"
#include "cc/paint/image_provider.h"
#include "cc/paint/skia_paint_canvas.h"
#include "components/viz/common/traced_value.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColorSpaceXformCanvas.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "ui/gfx/geometry/axis_transform2d.h"
#include "ui/gfx/geometry/rect_conversions.h"

namespace cc {
namespace {

// These enum values are persisted to logs and must never by renumbered or
// reused.
enum class RasterSourceClearType {
  kNone = 0,
  kFull = 1,
  kBorder = 2,
  kCount = 3
};

void TrackRasterSourceNeededClear(RasterSourceClearType clear_type) {
  UMA_HISTOGRAM_ENUMERATION("Renderer4.RasterSourceClearType", clear_type,
                            RasterSourceClearType::kCount);
}

}  // namespace

RasterSource::RasterSource(const RecordingSource* other)
    : display_list_(other->display_list_),
      painter_reported_memory_usage_(other->painter_reported_memory_usage_),
      background_color_(other->background_color_),
      requires_clear_(other->requires_clear_),
      is_solid_color_(other->is_solid_color_),
      solid_color_(other->solid_color_),
      recorded_viewport_(other->recorded_viewport_),
      size_(other->size_),
      slow_down_raster_scale_factor_for_debug_(
          other->slow_down_raster_scale_factor_for_debug_),
      recording_scale_factor_(other->recording_scale_factor_) {}
RasterSource::~RasterSource() = default;

void RasterSource::ClearForOpaqueRaster(
    SkCanvas* raster_canvas,
    const gfx::Size& content_size,
    const gfx::Rect& canvas_bitmap_rect,
    const gfx::Rect& canvas_playback_rect) const {
  // The last texel of this content is not guaranteed to be fully opaque, so
  // inset by one to generate the fully opaque coverage rect.  This rect is
  // in device space.
  SkIRect coverage_device_rect =
      SkIRect::MakeWH(content_size.width() - canvas_bitmap_rect.x() - 1,
                      content_size.height() - canvas_bitmap_rect.y() - 1);

  // If not fully covered, we need to clear one texel inside the coverage
  // rect (because of blending during raster) and one texel outside the canvas
  // bitmap rect (because of bilinear filtering during draw).  See comments
  // in RasterSource.
  SkIRect device_column = SkIRect::MakeXYWH(coverage_device_rect.right(), 0, 2,
                                            coverage_device_rect.bottom());
  // row includes the corner, column excludes it.
  SkIRect device_row = SkIRect::MakeXYWH(0, coverage_device_rect.bottom(),
                                         coverage_device_rect.right() + 2, 2);

  bool right_edge = content_size.width() == canvas_playback_rect.right();
  bool bottom_edge = content_size.height() == canvas_playback_rect.bottom();

  // If the playback rect is touching either edge of the content rect
  // extend it by one pixel to include the extra texel outside the canvas
  // bitmap rect that was added to device column and row above.
  SkIRect playback_device_rect =
      SkIRect::MakeXYWH(canvas_playback_rect.x() - canvas_bitmap_rect.x(),
                        canvas_playback_rect.y() - canvas_bitmap_rect.y(),
                        canvas_playback_rect.width() + (right_edge ? 1 : 0),
                        canvas_playback_rect.height() + (bottom_edge ? 1 : 0));

  // Intersect the device column and row with the playback rect and only
  // clear inside of that rect if needed.
  RasterSourceClearType clear_type = RasterSourceClearType::kNone;
  if (device_column.intersect(playback_device_rect)) {
    clear_type = RasterSourceClearType::kBorder;
    raster_canvas->save();
    raster_canvas->clipRect(SkRect::MakeFromIRect(device_column),
                            SkClipOp::kIntersect, false);
    raster_canvas->drawColor(background_color_, SkBlendMode::kSrc);
    raster_canvas->restore();
  }
  if (device_row.intersect(playback_device_rect)) {
    clear_type = RasterSourceClearType::kBorder;
    raster_canvas->save();
    raster_canvas->clipRect(SkRect::MakeFromIRect(device_row),
                            SkClipOp::kIntersect, false);
    raster_canvas->drawColor(background_color_, SkBlendMode::kSrc);
    raster_canvas->restore();
  }
  TrackRasterSourceNeededClear(clear_type);
}

void RasterSource::PlaybackToCanvas(
    SkCanvas* input_canvas,
    const gfx::ColorSpace& target_color_space,
    const gfx::Size& content_size,
    const gfx::Rect& canvas_bitmap_rect,
    const gfx::Rect& canvas_playback_rect,
    const gfx::AxisTransform2d& raster_transform,
    const PlaybackSettings& settings) const {
  SkIRect raster_bounds = gfx::RectToSkIRect(canvas_bitmap_rect);
  if (!canvas_playback_rect.IsEmpty() &&
      !raster_bounds.intersect(gfx::RectToSkIRect(canvas_playback_rect)))
    return;
  // Treat all subnormal values as zero for performance.
  ScopedSubnormalFloatDisabler disabler;

  // TODO(enne): color transform needs to be replicated in gles2_cmd_decoder
  SkCanvas* raster_canvas = input_canvas;
  std::unique_ptr<SkCanvas> color_transform_canvas;
  if (target_color_space.IsValid()) {
    color_transform_canvas = SkCreateColorSpaceXformCanvas(
        input_canvas, target_color_space.ToSkColorSpace());
    raster_canvas = color_transform_canvas.get();
  }

  bool is_partial_raster = canvas_bitmap_rect != canvas_playback_rect;
  if (!requires_clear_) {
    // Clear opaque raster sources.  Opaque rasters sources guarantee that all
    // pixels inside the opaque region are painted.  However, due to scaling
    // it's possible that the last row and column might include pixels that
    // are not painted.  Because this raster source is required to be opaque,
    // we may need to do extra clearing outside of the clip.  This needs to
    // be done for both full and partial raster.
    ClearForOpaqueRaster(raster_canvas, content_size, canvas_bitmap_rect,
                         canvas_playback_rect);
  } else if (!is_partial_raster) {
    // For non-opaque raster sources that are rastering the full tile,
    // just clear the entire canvas (even if stretches past the canvas
    // bitmap rect) as it's cheap to do so.
    TrackRasterSourceNeededClear(RasterSourceClearType::kFull);
    raster_canvas->clear(SK_ColorTRANSPARENT);
  }

  raster_canvas->save();
  raster_canvas->translate(-canvas_bitmap_rect.x(), -canvas_bitmap_rect.y());
  raster_canvas->clipRect(SkRect::MakeFromIRect(raster_bounds));
  raster_canvas->translate(raster_transform.translation().x(),
                           raster_transform.translation().y());
  raster_canvas->scale(raster_transform.scale() / recording_scale_factor_,
                       raster_transform.scale() / recording_scale_factor_);

  if (is_partial_raster && requires_clear_) {
    // TODO(enne): Should this be considered a partial clear?
    TrackRasterSourceNeededClear(RasterSourceClearType::kFull);
    // Because Skia treats painted regions as transparent by default, we don't
    // need to clear outside of the playback rect in the same way that
    // ClearForOpaqueRaster must handle.
    raster_canvas->clear(SK_ColorTRANSPARENT);
  }

  PlaybackToCanvas(raster_canvas, settings.image_provider);
  raster_canvas->restore();
}

void RasterSource::PlaybackToCanvas(SkCanvas* raster_canvas,
                                    ImageProvider* image_provider) const {
  // TODO(enne): Temporary CHECK debugging for http://crbug.com/823835
  CHECK(display_list_.get());
  int repeat_count = std::max(1, slow_down_raster_scale_factor_for_debug_);
  for (int i = 0; i < repeat_count; ++i)
    display_list_->Raster(raster_canvas, image_provider);
}

sk_sp<SkPicture> RasterSource::GetFlattenedPicture() {
  TRACE_EVENT0("cc", "RasterSource::GetFlattenedPicture");

  SkPictureRecorder recorder;
  SkCanvas* canvas = recorder.beginRecording(size_.width(), size_.height());
  if (!size_.IsEmpty()) {
    canvas->clear(SK_ColorTRANSPARENT);
    PlaybackToCanvas(canvas, nullptr);
  }

  return recorder.finishRecordingAsPicture();
}

size_t RasterSource::GetMemoryUsage() const {
  if (!display_list_)
    return 0;
  return display_list_->BytesUsed() + painter_reported_memory_usage_;
}

bool RasterSource::PerformSolidColorAnalysis(gfx::Rect layer_rect,
                                             SkColor* color) const {
  TRACE_EVENT0("cc", "RasterSource::PerformSolidColorAnalysis");

  layer_rect.Intersect(gfx::Rect(size_));
  layer_rect = gfx::ScaleToRoundedRect(layer_rect, recording_scale_factor_);
  return display_list_->GetColorIfSolidInRect(layer_rect, color);
}

void RasterSource::GetDiscardableImagesInRect(
    const gfx::Rect& layer_rect,
    std::vector<const DrawImage*>* images) const {
  DCHECK_EQ(0u, images->size());
  display_list_->discardable_image_map().GetDiscardableImagesInRect(layer_rect,
                                                                    images);
}

base::flat_map<PaintImage::Id, PaintImage::DecodingMode>
RasterSource::TakeDecodingModeMap() {
  return display_list_->TakeDecodingModeMap();
}

bool RasterSource::CoversRect(const gfx::Rect& layer_rect) const {
  if (size_.IsEmpty())
    return false;
  gfx::Rect bounded_rect = layer_rect;
  bounded_rect.Intersect(gfx::Rect(size_));
  return recorded_viewport_.Contains(bounded_rect);
}

gfx::Size RasterSource::GetSize() const {
  return size_;
}

gfx::Size RasterSource::GetContentSize(float content_scale) const {
  return gfx::ScaleToCeiledSize(GetSize(), content_scale);
}

bool RasterSource::IsSolidColor() const {
  return is_solid_color_;
}

SkColor RasterSource::GetSolidColor() const {
  DCHECK(IsSolidColor());
  return solid_color_;
}

bool RasterSource::HasRecordings() const {
  return !!display_list_.get();
}

gfx::Rect RasterSource::RecordedViewport() const {
  return recorded_viewport_;
}

void RasterSource::AsValueInto(base::trace_event::TracedValue* array) const {
  if (display_list_.get())
    viz::TracedValue::AppendIDRef(display_list_.get(), array);
}

void RasterSource::DidBeginTracing() {
  if (display_list_.get())
    display_list_->EmitTraceSnapshot();
}

RasterSource::PlaybackSettings::PlaybackSettings() = default;

RasterSource::PlaybackSettings::PlaybackSettings(const PlaybackSettings&) =
    default;

RasterSource::PlaybackSettings::PlaybackSettings(PlaybackSettings&&) = default;

RasterSource::PlaybackSettings::~PlaybackSettings() = default;

}  // namespace cc
