910e62b5创建于 1月15日历史提交
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CC_TILES_TILING_COVERAGE_ITERATOR_H_
#define CC_TILES_TILING_COVERAGE_ITERATOR_H_

#include <algorithm>
#include <concepts>
#include <utility>

#include "base/check.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "cc/base/tiling_data.h"
#include "cc/cc_export.h"
#include "cc/tiles/tile_index.h"
#include "cc/tiles/tiling_internal.h"
#include "ui/gfx/geometry/axis_transform2d.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/size.h"

namespace cc {

// TilingCoverageIterator iterates over a generic tiling to expose the minimal
// set of tiles required to cover a given content rectangle.
//
// Iteration terminates once either the content area has been fully covered by
// by visited tiles, or all applicable tiles in the tiling have been visited.
template <typename T>
  requires internal::Tiling<T>
class CC_EXPORT TilingCoverageIterator {
 public:
  using Tile = typename T::Tile;

  TilingCoverageIterator() = default;

  // Constructs an iterable coverage view for `tiling` which attempts to fully
  // cover the content area given by `coverage_rect`, a rectangle that has been
  // pre-scaled by `coverage_scale` relative to layer space.
  TilingCoverageIterator(const T* tiling,
                         float coverage_scale,
                         const gfx::Rect& coverage_rect)
      : tiling_(tiling),
        coverage_rect_max_bounds_(
            ComputeCoverageRectMaxBounds(*tiling,
                                         tiling->raster_size(),
                                         coverage_scale)),
        coverage_rect_(
            gfx::IntersectRects(coverage_rect, coverage_rect_max_bounds_)),
        coverage_to_content_(
            gfx::PreScaleAxisTransform2d(tiling->raster_transform(),
                                         1 / coverage_scale)) {
    if (coverage_rect_.IsEmpty()) {
      return;
    }
    const gfx::Rect wanted_texels =
        ComputeWantedTexels(coverage_to_content_, coverage_rect_);
    const TilingData& data = *tiling->tiling_data();
    top_left_.i = data.LastBorderTileXIndexFromSrcCoord(wanted_texels.x());
    top_left_.j = data.LastBorderTileYIndexFromSrcCoord(wanted_texels.y());
    bottom_right_.i =
        1 +
        std::max(data.FirstBorderTileXIndexFromSrcCoord(wanted_texels.right()),
                 top_left_.i);
    bottom_right_.j =
        1 +
        std::max(data.FirstBorderTileYIndexFromSrcCoord(wanted_texels.bottom()),
                 top_left_.j);
    index_ = top_left_;
    AdvanceUntilTileIsRelevant();
  }

  TilingCoverageIterator(const TilingCoverageIterator&) = default;
  TilingCoverageIterator& operator=(const TilingCoverageIterator&) = default;
  ~TilingCoverageIterator() = default;

  // Returns true if and only if this iterator has been initialized for a
  // specific tiling and has not yet advanced to the end of its coverage. Other
  // methods on this object may only be called when this returns true, and the
  // value returned here may only change after assigning or incrementing the
  // iterator.
  bool IsValid() const { return index_.j < bottom_right_.j; }
  explicit operator bool() const { return IsValid(); }

  // Advances the iterator to the next unvisited tile which covers some portion
  // of the coverage rect.
  TilingCoverageIterator& operator++() {
    if (IsValid()) {
      IncrementIndex();
      AdvanceUntilTileIsRelevant();
    }
    return *this;
  }

  // The index of the current tile.
  const TileIndex& index() const { return index_; }
  int i() const { return index_.i; }
  int j() const { return index_.j; }

  // The current tile.
  Tile* operator*() const { return current_tile_; }
  Tile* operator->() const { return current_tile_; }

  // The rect covered by the current tile within the space of the coverage rect.
  const gfx::Rect& geometry_rect() const { return geometry_rect_; }

  // The rect in texture space of the current tile's intersection with the
  // coverage rect.
  gfx::RectF texture_rect() const {
    auto tex_origin = gfx::PointF(tiling_->tiling_data()
                                      ->TileBoundsWithBorder(index_.i, index_.j)
                                      .origin());

    // Convert from coverage space => content space => texture space.
    gfx::RectF texture_rect =
        coverage_to_content_.MapRect(gfx::RectF(geometry_rect_));
    texture_rect.Offset(-tex_origin.OffsetFromOrigin());
    return texture_rect;
  }

 private:
  static gfx::Rect ComputeCoverageRectMaxBounds(const T& tiling,
                                                const gfx::Size& layer_bounds,
                                                float coverage_scale) {
    gfx::Rect tiling_rect_in_layer_space =
        gfx::ToEnclosingRect(tiling.raster_transform().InverseMapRect(
            gfx::RectF(tiling.tiling_data()->tiling_rect())));
    tiling_rect_in_layer_space.Intersect(gfx::Rect(layer_bounds));
    return gfx::ScaleToEnclosingRect(tiling_rect_in_layer_space,
                                     coverage_scale);
  }

  static gfx::Rect ComputeWantedTexels(
      const gfx::AxisTransform2d& coverage_to_content,
      const gfx::Rect& coverage_rect) {
    gfx::RectF content_rect =
        coverage_to_content.MapRect(gfx::RectF(coverage_rect));
    content_rect.Offset(-0.5f, -0.5f);
    return gfx::ToEnclosingRect(content_rect);
  }

  void IncrementIndex() {
    ++index_.i;
    if (index_.i >= bottom_right_.i) {
      index_.i = top_left_.i;
      ++index_.j;
    }
  }

  void AdvanceUntilTileIsRelevant() {
    const TilingData& data = *tiling_->tiling_data();
    gfx::Rect last_geometry_rect;
    Tile* next_tile = nullptr;
    while (IsValid()) {
      // Calculate the current geometry rect. As we reserved overlap between
      // tiles to accommodate bilinear filtering and rounding errors in
      // destination space, the geometry rect might overlap on the edges.
      //
      // We allow the tile to overreach by 1/1024 texels to avoid seams between
      // tiles. The constant 1/1024 is picked by the fact that with bilinear
      // filtering, the maximum error in color value introduced by clamping
      // error in both u/v axis can't exceed
      // 255 * (1 - (1 - 1/1024) * (1 - 1/1024)) ~= 0.498
      // i.e. The color value can never flip over a rounding threshold.
      gfx::RectF texel_extent = data.TexelExtent(index_.i, index_.j);
      constexpr float kEpsilon = 1. / 1024.f;
      texel_extent.Inset(-kEpsilon);

      // Convert texel_extent to coverage scale, which is what we have to report
      // geometry_rect in.
      //
      // We also adjust external edges to cover the whole recorded bounds in
      // dest space if any edge of the tiling rect touches the recorded edge.
      //
      // For external edges, extend the tile to scaled recorded bounds. This is
      // needed to fully cover the coverage space because the sample extent
      // doesn't cover the last 0.5 texel to the recorded edge, and also the
      // coverage space can be rounded up for up to 1 pixel. This overhang will
      // never be sampled as the AA fragment shader clamps sample coordinate and
      // antialiasing itself.
      gfx::Rect geometry_rect = gfx::ToEnclosedRect(
          coverage_to_content_.InverseMapRect(texel_extent));
      geometry_rect.SetByBounds(
          index_.i == 0 ? coverage_rect_max_bounds_.x() : geometry_rect.x(),
          index_.j == 0 ? coverage_rect_max_bounds_.y() : geometry_rect.y(),
          index_.i == data.num_tiles_x() - 1 ? coverage_rect_max_bounds_.right()
                                             : geometry_rect.right(),
          index_.j == data.num_tiles_y() - 1
              ? coverage_rect_max_bounds_.bottom()
              : geometry_rect.bottom());
      geometry_rect.Intersect(coverage_rect_);
      if (!geometry_rect.IsEmpty()) {
        next_tile = tiling_->TileAt(index_);
        last_geometry_rect = std::exchange(geometry_rect_, geometry_rect);
        break;
      }

      IncrementIndex();
    }

    current_tile_ = next_tile;
    if (last_geometry_rect.IsEmpty()) {
      // First tile or end of iteration. Nothing more to do in either case.
      return;
    }

    // Iteration happens left->right, top->bottom.  Running off the bottom-right
    // edge is handled by the intersection above.  Here we make sure that the
    // new current geometry rect doesn't overlap with the previous one.
    int min_left, min_top;
    const bool new_row = index_.i == top_left_.i;
    if (new_row) {
      min_left = coverage_rect_.x();
      min_top = last_geometry_rect.bottom();
    } else {
      min_left = last_geometry_rect.right();
      min_top = last_geometry_rect.y();
    }
    const int inset_left = std::max(0, min_left - geometry_rect_.x());
    const int inset_top = std::max(0, min_top - geometry_rect_.y());
    geometry_rect_.Inset(gfx::Insets::TLBR(inset_top, inset_left, 0, 0));

#if DCHECK_IS_ON()
    // Sometimes we run into an extreme case where we are at the edge of integer
    // precision. When doing so, rect calculations may end up changing values
    // unexpectedly. Unfortunately, there isn't much we can do at this point, so
    // we just do the correctness checks if both y and x offsets are
    // 'reasonable', meaning they are less than the specified value.
    static constexpr int kReasonableOffsetForDcheck = 100'000'000;
    if (!new_row && geometry_rect_.x() <= kReasonableOffsetForDcheck &&
        geometry_rect_.y() <= kReasonableOffsetForDcheck) {
      DCHECK_EQ(last_geometry_rect.right(), geometry_rect_.x());
      DCHECK_EQ(last_geometry_rect.bottom(), geometry_rect_.bottom());
      DCHECK_EQ(last_geometry_rect.y(), geometry_rect_.y());
    }
#endif
  }

  RAW_PTR_EXCLUSION const T* tiling_;
  gfx::Rect coverage_rect_max_bounds_;
  gfx::Rect coverage_rect_;
  gfx::AxisTransform2d coverage_to_content_;
  TileIndex top_left_;
  TileIndex bottom_right_;

  TileIndex index_;
  gfx::Rect geometry_rect_;
  RAW_PTR_EXCLUSION Tile* current_tile_ = nullptr;
};

}  // namespace cc

#endif  // CC_TILES_TILING_COVERAGE_ITERATOR_H_