blob: 3e900972fd71e2e6a473cde44208fe772c8bec9e [file] [log] [blame]
// Copyright 2013 The Flutter 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 "flutter/display_list/geometry/dl_region.h"
#include "flutter/fml/logging.h"
namespace flutter {
// Threshold for switching from linear search through span lines to binary
// search.
const int kBinarySearchThreshold = 10;
DlRegion::SpanBuffer::SpanBuffer(DlRegion::SpanBuffer&& m)
: capacity_(m.capacity_), size_(m.size_), spans_(m.spans_) {
m.size_ = 0;
m.capacity_ = 0;
m.spans_ = nullptr;
};
DlRegion::SpanBuffer::SpanBuffer(const DlRegion::SpanBuffer& m)
: capacity_(m.capacity_), size_(m.size_) {
if (m.spans_ == nullptr) {
spans_ = nullptr;
} else {
spans_ = static_cast<Span*>(std::malloc(capacity_ * sizeof(Span)));
memcpy(spans_, m.spans_, size_ * sizeof(Span));
}
};
DlRegion::SpanBuffer& DlRegion::SpanBuffer::operator=(
const DlRegion::SpanBuffer& buffer) {
SpanBuffer copy(buffer);
std::swap(*this, copy);
return *this;
}
DlRegion::SpanBuffer& DlRegion::SpanBuffer::operator=(
DlRegion::SpanBuffer&& buffer) {
std::swap(capacity_, buffer.capacity_);
std::swap(size_, buffer.size_);
std::swap(spans_, buffer.spans_);
return *this;
}
DlRegion::SpanBuffer::~SpanBuffer() {
free(spans_);
}
void DlRegion::SpanBuffer::reserve(size_t capacity) {
if (capacity_ < capacity) {
spans_ = static_cast<Span*>(std::realloc(spans_, capacity * sizeof(Span)));
capacity_ = capacity;
}
}
DlRegion::SpanChunkHandle DlRegion::SpanBuffer::storeChunk(const Span* begin,
const Span* end) {
size_t chunk_size = end - begin;
size_t min_capacity = size_ + chunk_size + 1;
if (capacity_ < min_capacity) {
size_t new_capacity = std::max(min_capacity, capacity_ * 2);
new_capacity = std::max(new_capacity, size_t(512));
reserve(new_capacity);
}
SpanChunkHandle res = size_;
size_ += chunk_size + 1;
setChunkSize(res, chunk_size);
auto* dst = spans_ + res + 1;
memmove(dst, begin, chunk_size * sizeof(Span));
return res;
}
size_t DlRegion::SpanBuffer::getChunkSize(SpanChunkHandle handle) const {
FML_DCHECK(handle < size_);
return spans_[handle].left;
}
void DlRegion::SpanBuffer::setChunkSize(SpanChunkHandle handle, size_t size) {
FML_DCHECK(handle < size_);
FML_DCHECK(spans_ != nullptr);
// NOLINTNEXTLINE(clang-analyzer-core.NullDereference)
spans_[handle].left = size;
}
void DlRegion::SpanBuffer::getSpans(SpanChunkHandle handle,
const DlRegion::Span*& begin,
const DlRegion::Span*& end) const {
FML_DCHECK(handle < size_);
begin = spans_ + handle + 1;
end = begin + getChunkSize(handle);
}
DlRegion::DlRegion(const std::vector<SkIRect>& rects) {
setRects(rects);
}
DlRegion::DlRegion(const SkIRect& rect) : bounds_(rect) {
Span span{rect.left(), rect.right()};
lines_.push_back(makeLine(rect.top(), rect.bottom(), &span, &span + 1));
}
bool DlRegion::spansEqual(SpanLine& line,
const Span* begin,
const Span* end) const {
const Span *our_begin, *our_end;
span_buffer_.getSpans(line.chunk_handle, our_begin, our_end);
size_t our_size = our_end - our_begin;
size_t their_size = end - begin;
if (our_size != their_size) {
return false;
}
return memcmp(our_begin, begin, our_size * sizeof(Span)) == 0;
}
DlRegion::SpanLine DlRegion::makeLine(int32_t top,
int32_t bottom,
const SpanVec& v) {
return makeLine(top, bottom, v.data(), v.data() + v.size());
}
DlRegion::SpanLine DlRegion::makeLine(int32_t top,
int32_t bottom,
const Span* begin,
const Span* end) {
auto handle = span_buffer_.storeChunk(begin, end);
return {top, bottom, handle};
}
// Returns number of valid spans in res. For performance reasons res is never
// downsized.
size_t DlRegion::unionLineSpans(std::vector<Span>& res,
const SpanBuffer& a_buffer,
SpanChunkHandle a_handle,
const SpanBuffer& b_buffer,
SpanChunkHandle b_handle) {
class OrderedSpanAccumulator {
public:
explicit OrderedSpanAccumulator(std::vector<Span>& res) : res(res) {}
void accumulate(const Span& span) {
if (span.left > last_ || len == 0) {
res[len++] = span;
last_ = span.right;
} else if (span.right > last_) {
FML_DCHECK(len > 0);
res[len - 1].right = span.right;
last_ = span.right;
}
}
size_t len = 0;
std::vector<Span>& res;
private:
int32_t last_ = std::numeric_limits<int32_t>::min();
};
const Span *begin1, *end1;
a_buffer.getSpans(a_handle, begin1, end1);
const Span *begin2, *end2;
b_buffer.getSpans(b_handle, begin2, end2);
size_t min_size = (end1 - begin1) + (end2 - begin2);
if (res.size() < min_size) {
res.resize(min_size);
}
OrderedSpanAccumulator accumulator(res);
while (true) {
if (begin1->left < begin2->left) {
accumulator.accumulate(*begin1++);
if (begin1 == end1) {
break;
}
} else {
// Either 2 is first, or they are equal, in which case add 2 now
// and we might combine 1 with it next time around
accumulator.accumulate(*begin2++);
if (begin2 == end2) {
break;
}
}
}
FML_DCHECK(begin1 == end1 || begin2 == end2);
while (begin1 < end1) {
accumulator.accumulate(*begin1++);
}
while (begin2 < end2) {
accumulator.accumulate(*begin2++);
}
FML_DCHECK(begin1 == end1 && begin2 == end2);
return accumulator.len;
}
size_t DlRegion::intersectLineSpans(std::vector<Span>& res,
const SpanBuffer& a_buffer,
SpanChunkHandle a_handle,
const SpanBuffer& b_buffer,
SpanChunkHandle b_handle) {
const Span *begin1, *end1;
a_buffer.getSpans(a_handle, begin1, end1);
const Span *begin2, *end2;
b_buffer.getSpans(b_handle, begin2, end2);
// Worst case scenario, interleaved overlapping spans
// AAAA BBBB CCCC
// XXX YYYY XXXX
size_t min_size = (end1 - begin1) + (end2 - begin2) - 1;
if (res.size() < min_size) {
res.resize(min_size);
}
// Pointer to the next span to be written.
Span* new_span = res.data();
while (begin1 != end1 && begin2 != end2) {
if (begin1->right <= begin2->left) {
++begin1;
} else if (begin2->right <= begin1->left) {
++begin2;
} else {
int32_t left = std::max(begin1->left, begin2->left);
int32_t right = std::min(begin1->right, begin2->right);
FML_DCHECK(left < right);
FML_DCHECK(new_span < res.data() + res.size());
*new_span++ = {left, right};
if (begin1->right == right) {
++begin1;
}
if (begin2->right == right) {
++begin2;
}
}
}
return new_span - res.data();
}
void DlRegion::setRects(const std::vector<SkIRect>& unsorted_rects) {
// setRects can only be called on empty regions.
FML_DCHECK(lines_.empty());
size_t count = unsorted_rects.size();
std::vector<const SkIRect*> rects(count);
for (size_t i = 0; i < count; i++) {
rects[i] = &unsorted_rects[i];
bounds_.join(unsorted_rects[i]);
}
std::sort(rects.begin(), rects.end(), [](const SkIRect* a, const SkIRect* b) {
if (a->top() < b->top()) {
return true;
}
if (a->top() > b->top()) {
return false;
}
return a->left() < b->left();
});
size_t active_end = 0;
size_t next_rect = 0;
int32_t cur_y = std::numeric_limits<int32_t>::min();
SpanVec working_spans;
#ifdef DlRegion_DO_STATS
size_t active_rect_count = 0;
size_t span_count = 0;
int pass_count = 0;
int line_count = 0;
#endif
while (next_rect < count || active_end > 0) {
// First prune passed rects out of the active list
size_t preserve_end = 0;
for (size_t i = 0; i < active_end; i++) {
const SkIRect* r = rects[i];
if (r->bottom() > cur_y) {
rects[preserve_end++] = r;
}
}
active_end = preserve_end;
// If we have no active rects any more, jump to the top of the
// next available input rect.
if (active_end == 0) {
if (next_rect >= count) {
// No active rects and no more rects to bring in. We are done.
break;
}
cur_y = rects[next_rect]->top();
}
// Next, insert any new rects we've reached into the active list
while (next_rect < count) {
const SkIRect* r = rects[next_rect];
if (r->isEmpty()) {
continue;
}
if (r->top() > cur_y) {
break;
}
// We now know that we will be inserting this rect into active list
next_rect++;
size_t insert_at = active_end++;
while (insert_at > 0) {
const SkIRect* ir = rects[insert_at - 1];
if (ir->left() <= r->left()) {
break;
}
rects[insert_at--] = ir;
}
rects[insert_at] = r;
}
// We either preserved some rects in the active list or added more from
// the remaining input rects, or we would have exited the loop above.
FML_DCHECK(active_end != 0);
working_spans.clear();
FML_DCHECK(working_spans.empty());
#ifdef DlRegion_DO_STATS
active_rect_count += active_end;
pass_count++;
#endif
// [start_x, end_x) always represents a valid span to be inserted
// [cur_y, end_y) is the intersecting range over which all spans are valid
int32_t start_x = rects[0]->left();
int32_t end_x = rects[0]->right();
int32_t end_y = rects[0]->bottom();
for (size_t i = 1; i < active_end; i++) {
const SkIRect* r = rects[i];
if (r->left() > end_x) {
working_spans.emplace_back(start_x, end_x);
start_x = r->left();
end_x = r->right();
} else if (end_x < r->right()) {
end_x = r->right();
}
if (end_y > r->bottom()) {
end_y = r->bottom();
}
}
working_spans.emplace_back(start_x, end_x);
// end_y must not pass by the top of the next input rect
if (next_rect < count && end_y > rects[next_rect]->top()) {
end_y = rects[next_rect]->top();
}
// If all of the rules above work out, we should never collapse the
// current range of Y coordinates to empty
FML_DCHECK(end_y > cur_y);
if (!lines_.empty() && lines_.back().bottom == cur_y &&
spansEqual(lines_.back(), working_spans.data(),
working_spans.data() + working_spans.size())) {
lines_.back().bottom = end_y;
} else {
#ifdef DlRegion_DO_STATS
span_count += working_spans.size();
line_count++;
#endif
lines_.push_back(makeLine(cur_y, end_y, working_spans));
}
cur_y = end_y;
}
#ifdef DlRegion_DO_STATS
double span_avg = ((double)span_count) / line_count;
double active_avg = ((double)active_rect_count) / pass_count;
FML_LOG(ERROR) << lines_.size() << " lines for " << count
<< " input rects, avg " << span_avg
<< " spans per line and avg " << active_avg
<< " active rects per loop";
#endif
}
void DlRegion::appendLine(int32_t top,
int32_t bottom,
const Span* begin,
const Span* end) {
if (lines_.empty()) {
lines_.push_back(makeLine(top, bottom, begin, end));
} else {
if (lines_.back().bottom == top && spansEqual(lines_.back(), begin, end)) {
lines_.back().bottom = bottom;
} else {
lines_.push_back(makeLine(top, bottom, begin, end));
}
}
}
DlRegion DlRegion::MakeUnion(const DlRegion& a, const DlRegion& b) {
if (a.isEmpty()) {
return b;
} else if (b.isEmpty()) {
return a;
} else if (a.isSimple() && a.bounds_.contains(b.bounds_)) {
return a;
} else if (b.isSimple() && b.bounds_.contains(a.bounds_)) {
return b;
}
DlRegion res;
res.bounds_ = a.bounds_;
res.bounds_.join(b.bounds_);
res.span_buffer_.reserve(a.span_buffer_.capacity() +
b.span_buffer_.capacity());
auto& lines = res.lines_;
lines.reserve(a.lines_.size() + b.lines_.size());
auto a_it = a.lines_.begin();
auto b_it = b.lines_.begin();
auto a_end = a.lines_.end();
auto b_end = b.lines_.end();
FML_DCHECK(a_it != a_end && b_it != b_end);
auto& a_buffer = a.span_buffer_;
auto& b_buffer = b.span_buffer_;
std::vector<Span> tmp;
int32_t cur_top = std::numeric_limits<int32_t>::min();
while (a_it != a_end && b_it != b_end) {
auto a_top = std::max(cur_top, a_it->top);
auto b_top = std::max(cur_top, b_it->top);
if (a_it->bottom <= b_top) {
res.appendLine(a_top, a_it->bottom, a_buffer, a_it->chunk_handle);
++a_it;
} else if (b_it->bottom <= a_top) {
res.appendLine(b_top, b_it->bottom, b_buffer, b_it->chunk_handle);
++b_it;
} else {
if (a_top < b_top) {
res.appendLine(a_top, b_top, a_buffer, a_it->chunk_handle);
cur_top = b_top;
if (cur_top == a_it->bottom) {
++a_it;
}
} else if (b_top < a_top) {
res.appendLine(b_top, a_top, b_buffer, b_it->chunk_handle);
cur_top = a_top;
if (cur_top == b_it->bottom) {
++b_it;
}
} else {
auto new_bottom = std::min(a_it->bottom, b_it->bottom);
FML_DCHECK(a_top == b_top);
FML_DCHECK(new_bottom > a_top);
FML_DCHECK(new_bottom > b_top);
auto size = unionLineSpans(tmp, a_buffer, a_it->chunk_handle, b_buffer,
b_it->chunk_handle);
res.appendLine(a_top, new_bottom, tmp.data(), tmp.data() + size);
cur_top = new_bottom;
if (cur_top == a_it->bottom) {
++a_it;
}
if (cur_top == b_it->bottom) {
++b_it;
}
}
}
}
FML_DCHECK(a_it == a_end || b_it == b_end);
while (a_it != a_end) {
auto a_top = std::max(cur_top, a_it->top);
res.appendLine(a_top, a_it->bottom, a_buffer, a_it->chunk_handle);
++a_it;
}
while (b_it != b_end) {
auto b_top = std::max(cur_top, b_it->top);
res.appendLine(b_top, b_it->bottom, b_buffer, b_it->chunk_handle);
++b_it;
}
return res;
}
DlRegion DlRegion::MakeIntersection(const DlRegion& a, const DlRegion& b) {
if (!SkIRect::Intersects(a.bounds_, b.bounds_)) {
return DlRegion();
} else if (a.isSimple() && b.isSimple()) {
SkIRect r(a.bounds_);
auto res = r.intersect(b.bounds_);
(void)res; // Suppress unused variable warning in release builds.
FML_DCHECK(res);
return DlRegion(r);
} else if (a.isSimple() && a.bounds_.contains(b.bounds_)) {
return b;
} else if (b.isSimple() && b.bounds_.contains(a.bounds_)) {
return a;
}
DlRegion res;
res.span_buffer_.reserve(
std::max(a.span_buffer_.capacity(), b.span_buffer_.capacity()));
auto& lines = res.lines_;
lines.reserve(std::min(a.lines_.size(), b.lines_.size()));
std::vector<SpanLine>::const_iterator a_it, b_it;
getIntersectionIterators(a.lines_, b.lines_, a_it, b_it);
auto a_end = a.lines_.end();
auto b_end = b.lines_.end();
auto& a_buffer = a.span_buffer_;
auto& b_buffer = b.span_buffer_;
std::vector<Span> tmp;
int32_t cur_top = std::numeric_limits<int32_t>::min();
while (a_it != a_end && b_it != b_end) {
auto a_top = std::max(cur_top, a_it->top);
auto b_top = std::max(cur_top, b_it->top);
if (a_it->bottom <= b_top) {
++a_it;
} else if (b_it->bottom <= a_top) {
++b_it;
} else {
auto top = std::max(a_top, b_top);
auto bottom = std::min(a_it->bottom, b_it->bottom);
FML_DCHECK(top < bottom);
auto size = intersectLineSpans(tmp, a_buffer, a_it->chunk_handle,
b_buffer, b_it->chunk_handle);
if (size > 0) {
res.appendLine(top, bottom, tmp.data(), tmp.data() + size);
res.bounds_.join(SkIRect::MakeLTRB(
tmp.data()->left, top, (tmp.data() + size - 1)->right, bottom));
}
cur_top = bottom;
if (cur_top == a_it->bottom) {
++a_it;
}
if (cur_top == b_it->bottom) {
++b_it;
}
}
}
FML_DCHECK(a_it == a_end || b_it == b_end);
return res;
}
std::vector<SkIRect> DlRegion::getRects(bool deband) const {
std::vector<SkIRect> rects;
if (isEmpty()) {
return rects;
} else if (isSimple()) {
rects.push_back(bounds_);
return rects;
}
size_t rect_count = 0;
size_t previous_span_end = 0;
for (const auto& line : lines_) {
rect_count += span_buffer_.getChunkSize(line.chunk_handle);
}
rects.reserve(rect_count);
for (const auto& line : lines_) {
const Span *span_begin, *span_end;
span_buffer_.getSpans(line.chunk_handle, span_begin, span_end);
for (const auto* span = span_begin; span < span_end; ++span) {
SkIRect rect{span->left, line.top, span->right, line.bottom};
if (deband) {
auto iter = rects.begin() + previous_span_end;
// If there is rectangle previously in rects on which this one is a
// vertical continuation, remove the previous rectangle and expand
// this one vertically to cover the area.
while (iter != rects.begin()) {
--iter;
if (iter->bottom() < rect.top()) {
// Went all the way to previous span line.
break;
} else if (iter->left() == rect.left() &&
iter->right() == rect.right()) {
FML_DCHECK(iter->bottom() == rect.top());
rect.fTop = iter->fTop;
rects.erase(iter);
--previous_span_end;
break;
}
}
}
rects.push_back(rect);
}
previous_span_end = rects.size();
}
return rects;
}
bool DlRegion::isComplex() const {
return lines_.size() > 1 ||
(lines_.size() == 1 &&
span_buffer_.getChunkSize(lines_.front().chunk_handle) > 1);
}
bool DlRegion::intersects(const SkIRect& rect) const {
if (isEmpty()) {
return false;
}
auto bounds_intersect = SkIRect::Intersects(bounds_, rect);
if (isSimple()) {
return bounds_intersect;
}
if (!bounds_intersect) {
return false;
}
auto it = lines_.begin();
auto end = lines_.end();
if (lines_.size() > kBinarySearchThreshold &&
it[kBinarySearchThreshold].bottom <= rect.fTop) {
it = std::lower_bound(
lines_.begin() + kBinarySearchThreshold + 1, lines_.end(), rect.fTop,
[](const SpanLine& line, int32_t top) { return line.bottom <= top; });
} else {
while (it != end && it->bottom <= rect.fTop) {
++it;
continue;
}
}
while (it != end && it->top < rect.fBottom) {
FML_DCHECK(rect.fTop < it->bottom && it->top < rect.fBottom);
const Span *begin, *end;
span_buffer_.getSpans(it->chunk_handle, begin, end);
while (begin != end && begin->left < rect.fRight) {
if (begin->right > rect.fLeft) {
return true;
}
++begin;
}
++it;
}
return false;
}
bool DlRegion::spansIntersect(const Span* begin1,
const Span* end1,
const Span* begin2,
const Span* end2) {
while (begin1 != end1 && begin2 != end2) {
if (begin1->right <= begin2->left) {
++begin1;
} else if (begin2->right <= begin1->left) {
++begin2;
} else {
return true;
}
}
return false;
}
void DlRegion::getIntersectionIterators(
const std::vector<SpanLine>& a_lines,
const std::vector<SpanLine>& b_lines,
std::vector<SpanLine>::const_iterator& a_it,
std::vector<SpanLine>::const_iterator& b_it) {
a_it = a_lines.begin();
auto a_end = a_lines.end();
b_it = b_lines.begin();
auto b_end = b_lines.end();
FML_DCHECK(a_it != a_end && b_it != b_end);
auto a_len = a_end - a_it;
auto b_len = b_end - b_it;
if (a_len > kBinarySearchThreshold &&
a_it[kBinarySearchThreshold].bottom <= b_it->top) {
a_it = std::lower_bound(
a_lines.begin() + kBinarySearchThreshold + 1, a_lines.end(), b_it->top,
[](const SpanLine& line, int32_t top) { return line.bottom <= top; });
} else if (b_len > kBinarySearchThreshold &&
b_it[kBinarySearchThreshold].bottom <= a_it->top) {
b_it = std::lower_bound(
b_lines.begin() + kBinarySearchThreshold + 1, b_lines.end(), a_it->top,
[](const SpanLine& line, int32_t top) { return line.bottom <= top; });
}
}
bool DlRegion::intersects(const DlRegion& region) const {
if (isEmpty() || region.isEmpty()) {
return false;
}
auto our_complex = isComplex();
auto their_complex = region.isComplex();
auto bounds_intersect = SkIRect::Intersects(bounds_, region.bounds_);
if (!our_complex && !their_complex) {
return bounds_intersect;
}
if (!bounds_intersect) {
return false;
}
if (!our_complex) {
return region.intersects(bounds_);
}
if (!their_complex) {
return intersects(region.bounds_);
}
std::vector<SpanLine>::const_iterator ours, theirs;
getIntersectionIterators(lines_, region.lines_, ours, theirs);
auto ours_end = lines_.end();
auto theirs_end = region.lines_.end();
while (ours != ours_end && theirs != theirs_end) {
if (ours->bottom <= theirs->top) {
++ours;
} else if (theirs->bottom <= ours->top) {
++theirs;
} else {
FML_DCHECK(ours->top < theirs->bottom && theirs->top < ours->bottom);
const Span *ours_begin, *ours_end;
span_buffer_.getSpans(ours->chunk_handle, ours_begin, ours_end);
const Span *theirs_begin, *theirs_end;
region.span_buffer_.getSpans(theirs->chunk_handle, theirs_begin,
theirs_end);
if (spansIntersect(ours_begin, ours_end, theirs_begin, theirs_end)) {
return true;
}
if (ours->bottom < theirs->bottom) {
++ours;
} else {
++theirs;
}
}
}
return false;
}
} // namespace flutter