blob: a1c2340d2fe91921a8aa16ee5b8535265a850ee9 [file] [log] [blame]
// 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_