blob: 6ea46760840a0e915f42194100d77c9436697c1f [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_SET_COVERAGE_ITERATOR_H_
#define CC_TILES_TILING_SET_COVERAGE_ITERATOR_H_
#include <algorithm>
#include <concepts>
#include <memory>
#include <optional>
#include <vector>
#include "base/check.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "cc/base/region.h"
#include "cc/tiles/tile_index.h"
#include "cc/tiles/tiling_coverage_iterator.h"
#include "cc/tiles/tiling_internal.h"
#include "ui/gfx/geometry/rect.h"
namespace cc {
namespace internal {
// Tilings also need to provide a Cover() method which exposes an appropriate
// coverage iterator in order to be used with TilingSetCoverageIterator.
template <typename T>
concept TilingWithCover =
requires(const T t, const gfx::Rect& rect, float scale) {
{ t.Cover(rect, scale) } -> std::derived_from<TilingCoverageIterator<T>>;
};
} // namespace internal
// TilingSetCoverageIterator iterates over the best, minimal set of drawable
// tiles to cover a given output rectangle.
template <typename T>
requires internal::TilingWithCover<T>
class TilingSetCoverageIterator {
public:
using Container = std::vector<std::unique_ptr<T>>;
using Tile = typename T::Tile;
// Constructs an iterator to emit tiles filling `coverage_rect`, which is an
// output rectangle that has been pre-scaled by `coverage_scale`. The iterator
// will prefer to use tiles from tilings at `ideal_contents_scale`, falling
// back onto larger and then smaller raster scales to fill in gaps as needed
// where more ideal tiles aren't ready to draw.
//
// All tiles are drawn from `tilings`, which must contain one or more
// objects of some type T which conforms to TilingWithCover as defined above.
// `tilings` must be sorted in descending order of raster scale key.
TilingSetCoverageIterator(const Container& tilings,
const gfx::Rect& coverage_rect,
float coverage_scale,
float ideal_contents_scale)
: tilings_(tilings),
coverage_scale_(coverage_scale),
ideal_tiling_(FindIdealTiling(tilings_, ideal_contents_scale)),
missing_region_(coverage_rect) {
AdvanceUntilTileIsRelevant();
}
~TilingSetCoverageIterator() = default;
// Returns true if and only if this iterator has been initialized and any
// portion of the coverage rect remains uncovered by the union of all
// visited geometry rects so far. If true, at least CurrentTiling() and
// geometry_rect() are safe to call.
bool IsValid() const {
return (current_tiling_ && *current_tiling_ != tilings_.end()) ||
region_iter_ != current_region_.end();
}
explicit operator bool() const { return IsValid(); }
// The tiling for the current iterator position. If this returns null but
// IsValid() is true, then there are no more applicable tilings (and
// therefore no more tiles to draw); but geometry_rect() is still meaningful
// and should be checkerboarded.
T* CurrentTiling() const {
if (!current_tiling_ || current_tiling_ == tilings_.end()) {
return nullptr;
}
return current_tiling_.value()->get();
}
Tile* operator*() const {
return tiling_iter_.IsValid() ? *tiling_iter_ : nullptr;
}
Tile* operator->() const { return **this; }
// The current output rectangle in pre-scaled coverage space. This is always
// meaningful as long as IsValid() is true, even if there is no
// CurrentTiling() or tile to cover the rect. Geometry rects across all
// iterations are mutually non-overlapping and their total union comprises the
// full coverage rect over which this iterator was constructed.
gfx::Rect geometry_rect() const {
if (tiling_iter_.IsValid()) {
return tiling_iter_.geometry_rect();
}
if (region_iter_ != current_region_.end()) {
return *region_iter_;
}
return gfx::Rect();
}
gfx::RectF texture_rect() const {
if (tiling_iter_.IsValid()) {
return tiling_iter_.texture_rect();
}
return gfx::RectF();
}
TileResolution resolution() const {
const T* tiling = CurrentTiling();
DCHECK(tiling);
return tiling->resolution();
}
TilingSetCoverageIterator& operator++() {
DCHECK(IsValid());
AdvanceUntilTileIsRelevant();
return *this;
}
private:
using TilingIterator = typename Container::const_iterator;
static TilingIterator FindIdealTiling(const Container& tilings,
float ideal_contents_scale) {
if (tilings.empty()) {
return tilings.end();
}
// Determine the smallest-scale tiling with a scale higher than the ideal,
// or the first tiling if all scales are less than the ideal.
for (auto iter = tilings.begin(); iter != tilings.end(); ++iter) {
if ((*iter)->contents_scale_key() < ideal_contents_scale) {
return iter == tilings.begin() ? iter : iter - 1;
}
}
// If all scale factors are at least as large as the ideal, use the
// smallest (last) one.
return tilings.end() - 1;
}
void AdvanceTiling() {
// Order of tilings visited upon successive calls to this method is:
// 1. Ideal tiling index
// 2. Tiling indices < Ideal in decreasing order (higher res than ideal)
// 3. Tiling indices > Ideal in increasing order (lower res than ideal)
DCHECK(current_tiling_ != tilings_.end());
if (!current_tiling_) {
current_tiling_ = ideal_tiling_;
} else if (*current_tiling_ > ideal_tiling_) {
++*current_tiling_;
} else if (*current_tiling_ > tilings_.begin()) {
--*current_tiling_;
} else {
current_tiling_ = ideal_tiling_;
++*current_tiling_;
}
}
void AdvanceUntilTileIsRelevant() {
if (!IsValid() && current_tiling_) {
return;
}
if (tiling_iter_.IsValid()) {
++tiling_iter_;
}
while (true) {
for (; tiling_iter_.IsValid(); ++tiling_iter_) {
Tile* const tile = (**current_tiling_)->TileAt(tiling_iter_.index());
if (tile && tile->IsReadyToDraw()) {
return;
}
// For any tile which is not yet ready to draw, accumulate its
// coverage back into the uncovered region so that subsequent tilings
// may attempt to cover it.
missing_region_.Union(tiling_iter_.geometry_rect());
}
// If the set of current rects for this tiling is done, or if this is
// the first call at construction time, update the current tiling.
if (region_iter_ == current_region_.end()) {
AdvanceTiling();
current_region_.Swap(&missing_region_);
missing_region_.Clear();
region_iter_ = current_region_.begin();
if (region_iter_ == current_region_.end()) {
// Region is fully covered.
current_tiling_ = tilings_.end();
return;
}
if (current_tiling_ == tilings_.end()) {
// No more tilings. This and subsequent iterations will return null
// tilings until we've iterated over the remaining geometry rects.
return;
}
}
gfx::Rect last_rect = *region_iter_;
++region_iter_;
if (current_tiling_ == tilings_.end()) {
return;
}
tiling_iter_ = (**current_tiling_)->Cover(last_rect, coverage_scale_);
}
}
// RAW_PTR_EXCLUSION: Renderer performance: visible in sampling profiler
// stacks.
RAW_PTR_EXCLUSION const Container& tilings_;
const float coverage_scale_;
const TilingIterator ideal_tiling_;
std::optional<TilingIterator> current_tiling_;
Region missing_region_;
Region current_region_;
Region::Iterator region_iter_;
TilingCoverageIterator<T> tiling_iter_;
};
} // namespace cc
#endif // CC_TILES_TILING_SET_COVERAGE_ITERATOR_H_