// Copyright 2019 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/layers/tile_size_calculator.h"

#include "cc/base/math_util.h"
#include "cc/layers/picture_layer_impl.h"
#include "cc/trees/layer_tree_impl.h"

namespace cc {
namespace {

// When making odd-sized tiles, round them up to increase the chances
// of using the same tile size.
const int kTileRoundUp = 64;

// Round GPU default tile sizes to a multiple of 32. This helps prevent
// rounding errors during compositing.
const int kGpuDefaultTileRoundUp = 32;

// For performance reasons and to support compressed tile textures, tile
// width and height should be an even multiple of 4 in size.
const int kTileMinimalAlignment = 4;

// This function converts the given |device_pixels_size| to the expected size
// of content which was generated to fill it at 100%.  This takes into account
// the ceil operations that occur as device pixels are converted to/from DIPs
// (content size must be a whole number of DIPs).
gfx::Size ApplyDsfAdjustment(const gfx::Size& device_pixels_size, float dsf) {
  gfx::Size content_size_in_dips =
      gfx::ScaleToCeiledSize(device_pixels_size, 1.0f / dsf);
  gfx::Size content_size_in_dps =
      gfx::ScaleToCeiledSize(content_size_in_dips, dsf);
  return content_size_in_dps;
}

// For GPU rasterization, we pick an ideal tile size using the viewport so we
// don't need any settings. The current approach uses 4 tiles to cover the
// viewport vertically.
gfx::Size CalculateGpuTileSize(const gfx::Size& base_tile_size,
                               const gfx::Size& content_bounds,
                               const gfx::Size& max_tile_size,
                               int min_height_for_gpu_raster_tile) {
  int tile_width = base_tile_size.width();

  // Increase the height proportionally as the width decreases, and pad by our
  // border texels to make the tiles exactly match the viewport.
  int divisor = 4;
  if (content_bounds.width() <= base_tile_size.width() / 2)
    divisor = 2;
  if (content_bounds.width() <= base_tile_size.width() / 4)
    divisor = 1;
  int tile_height =
      MathUtil::UncheckedRoundUp(base_tile_size.height(), divisor) / divisor;

  // Grow default sizes to account for overlapping border texels.
  tile_width += 2 * PictureLayerTiling::kBorderTexels;
  tile_height += 2 * PictureLayerTiling::kBorderTexels;

  // Round GPU default tile sizes to a multiple of kGpuDefaultTileAlignment.
  // This helps prevent rounding errors in our CA path. https://crbug.com/632274
  tile_width = MathUtil::UncheckedRoundUp(tile_width, kGpuDefaultTileRoundUp);
  tile_height = MathUtil::UncheckedRoundUp(tile_height, kGpuDefaultTileRoundUp);

  tile_height = std::max(tile_height, min_height_for_gpu_raster_tile);

  if (!max_tile_size.IsEmpty()) {
    tile_width = std::min(tile_width, max_tile_size.width());
    tile_height = std::min(tile_height, max_tile_size.height());
  }

  return gfx::Size(tile_width, tile_height);
}

}  // namespace

// AffectingParams.
bool TileSizeCalculator::AffectingParams::operator==(
    const AffectingParams& other) {
  return max_texture_size == other.max_texture_size &&
         use_gpu_rasterization == other.use_gpu_rasterization &&
         device_scale_factor == other.device_scale_factor &&
         max_tile_size == other.max_tile_size &&
         gpu_raster_max_texture_size == other.gpu_raster_max_texture_size &&
         max_untiled_layer_size == other.max_untiled_layer_size &&
         default_tile_size == other.default_tile_size &&
         layer_content_bounds == other.layer_content_bounds;
}

// TileSizeCalculator.
TileSizeCalculator::TileSizeCalculator(PictureLayerImpl* layer_impl)
    : layer_impl_(layer_impl) {}

bool TileSizeCalculator::IsAffectingParamsChanged() {
  AffectingParams new_params = GetAffectingParams();

  if (affecting_params_ == new_params)
    return false;

  affecting_params_ = new_params;
  return true;
}

TileSizeCalculator::AffectingParams TileSizeCalculator::GetAffectingParams() {
  AffectingParams params;
  LayerTreeImpl* layer_tree_impl = layer_impl()->layer_tree_impl();
  params.max_texture_size = layer_tree_impl->max_texture_size();
  params.use_gpu_rasterization = layer_tree_impl->use_gpu_rasterization();
  params.max_tile_size = layer_tree_impl->settings().max_gpu_raster_tile_size;
  params.min_height_for_gpu_raster_tile =
      layer_tree_impl->settings().min_height_for_gpu_raster_tile;
  params.gpu_raster_max_texture_size =
      layer_impl()->gpu_raster_max_texture_size();
  params.device_scale_factor = layer_tree_impl->device_scale_factor();
  params.max_untiled_layer_size =
      layer_tree_impl->settings().max_untiled_layer_size;
  params.default_tile_size = layer_tree_impl->settings().default_tile_size;
  params.layer_content_bounds = layer_impl()->content_bounds();
  return params;
}

gfx::Size TileSizeCalculator::CalculateTileSize() {
  gfx::Size content_bounds = layer_impl()->content_bounds();

  if (layer_impl()->is_backdrop_filter_mask()) {
    // Backdrop filter masks are not tiled, so if we can't cover the whole mask
    // with one tile, we shouldn't have such a tiling at all.
    DCHECK_LE(content_bounds.width(),
              layer_impl()->layer_tree_impl()->max_texture_size());
    DCHECK_LE(content_bounds.height(),
              layer_impl()->layer_tree_impl()->max_texture_size());
    return content_bounds;
  }

  // If |affecting_params_| is already computed and not changed, return
  // pre-calculated tile size.
  if (!IsAffectingParamsChanged())
    return tile_size_;

  int default_tile_width = 0;
  int default_tile_height = 0;
  if (affecting_params_.use_gpu_rasterization) {
    gfx::Size max_tile_size = affecting_params_.max_tile_size;

    // Calculate |base_tile_size| based on |gpu_raster_max_texture_size|,
    // adjusting for ceil operations that may occur due to DSF.
    gfx::Size base_tile_size =
        ApplyDsfAdjustment(affecting_params_.gpu_raster_max_texture_size,
                           affecting_params_.device_scale_factor);

    // Set our initial size assuming a |base_tile_size| equal to our
    // |viewport_size|.
    gfx::Size default_tile_size =
        CalculateGpuTileSize(base_tile_size, content_bounds, max_tile_size,
                             affecting_params_.min_height_for_gpu_raster_tile);

    // Use half-width GPU tiles when the content_width is greater than our
    // calculated tile size.
    if (content_bounds.width() > default_tile_size.width()) {
      // Divide width by 2 and round up.
      base_tile_size.set_width((base_tile_size.width() + 1) / 2);
      default_tile_size = CalculateGpuTileSize(
          base_tile_size, content_bounds, max_tile_size,
          affecting_params_.min_height_for_gpu_raster_tile);
    }

    default_tile_width = default_tile_size.width();
    default_tile_height = default_tile_size.height();
  } else {
    // For CPU rasterization we use tile-size settings.
    int max_untiled_content_width =
        affecting_params_.max_untiled_layer_size.width();
    int max_untiled_content_height =
        affecting_params_.max_untiled_layer_size.height();
    default_tile_width = affecting_params_.default_tile_size.width();
    default_tile_height = affecting_params_.default_tile_size.height();

    // If the content width is small, increase tile size vertically.
    // If the content height is small, increase tile size horizontally.
    // If both are less than the untiled-size, use a single tile.
    if (content_bounds.width() < default_tile_width)
      default_tile_height = max_untiled_content_height;
    if (content_bounds.height() < default_tile_height)
      default_tile_width = max_untiled_content_width;
    if (content_bounds.width() < max_untiled_content_width &&
        content_bounds.height() < max_untiled_content_height) {
      default_tile_height = max_untiled_content_height;
      default_tile_width = max_untiled_content_width;
    }
  }

  int tile_width = default_tile_width;
  int tile_height = default_tile_height;

  // Clamp the tile width/height to the content width/height to save space.
  if (content_bounds.width() < default_tile_width) {
    tile_width = std::min(tile_width, content_bounds.width());
    tile_width = MathUtil::UncheckedRoundUp(tile_width, kTileRoundUp);
    tile_width = std::min(tile_width, default_tile_width);
  }
  if (content_bounds.height() < default_tile_height) {
    tile_height = std::min(tile_height, content_bounds.height());
    tile_height = MathUtil::UncheckedRoundUp(tile_height, kTileRoundUp);
    tile_height = std::min(tile_height, default_tile_height);
  }

  // Ensure that tile width and height are properly aligned.
  tile_width = MathUtil::UncheckedRoundUp(tile_width, kTileMinimalAlignment);
  tile_height = MathUtil::UncheckedRoundUp(tile_height, kTileMinimalAlignment);

  // Under no circumstance should we be larger than the max texture size.
  tile_width = std::min(tile_width, affecting_params_.max_texture_size);
  tile_height = std::min(tile_height, affecting_params_.max_texture_size);

  // Store the calculated tile size.
  tile_size_ = gfx::Size(tile_width, tile_height);

  return tile_size_;
}

}  // namespace cc
