Test: No code changes, just ran through clang-format Change-Id: Id23aa4ec7eebc0446fe3a30260f33e7fd455bb8c
535 lines
19 KiB
C++
535 lines
19 KiB
C++
/*
|
|
* Copyright (C) 2015 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
#include "ClipArea.h"
|
|
|
|
#include "utils/LinearAllocator.h"
|
|
|
|
#include <SkPath.h>
|
|
#include <limits>
|
|
#include <type_traits>
|
|
|
|
namespace android {
|
|
namespace uirenderer {
|
|
|
|
static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) {
|
|
Vertex v = {x, y};
|
|
transform.mapPoint(v.x, v.y);
|
|
transformedBounds.expandToCover(v.x, v.y);
|
|
}
|
|
|
|
Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) {
|
|
const float kMinFloat = std::numeric_limits<float>::lowest();
|
|
const float kMaxFloat = std::numeric_limits<float>::max();
|
|
Rect transformedBounds = {kMaxFloat, kMaxFloat, kMinFloat, kMinFloat};
|
|
handlePoint(transformedBounds, transform, r.left, r.top);
|
|
handlePoint(transformedBounds, transform, r.right, r.top);
|
|
handlePoint(transformedBounds, transform, r.left, r.bottom);
|
|
handlePoint(transformedBounds, transform, r.right, r.bottom);
|
|
return transformedBounds;
|
|
}
|
|
|
|
void ClipBase::dump() const {
|
|
ALOGD("mode %d" RECT_STRING, mode, RECT_ARGS(rect));
|
|
}
|
|
|
|
/*
|
|
* TransformedRectangle
|
|
*/
|
|
|
|
TransformedRectangle::TransformedRectangle() {}
|
|
|
|
TransformedRectangle::TransformedRectangle(const Rect& bounds, const Matrix4& transform)
|
|
: mBounds(bounds), mTransform(transform) {}
|
|
|
|
bool TransformedRectangle::canSimplyIntersectWith(const TransformedRectangle& other) const {
|
|
return mTransform == other.mTransform;
|
|
}
|
|
|
|
void TransformedRectangle::intersectWith(const TransformedRectangle& other) {
|
|
mBounds.doIntersect(other.mBounds);
|
|
}
|
|
|
|
bool TransformedRectangle::isEmpty() const {
|
|
return mBounds.isEmpty();
|
|
}
|
|
|
|
/*
|
|
* RectangleList
|
|
*/
|
|
|
|
RectangleList::RectangleList() : mTransformedRectanglesCount(0) {}
|
|
|
|
bool RectangleList::isEmpty() const {
|
|
if (mTransformedRectanglesCount < 1) {
|
|
return true;
|
|
}
|
|
|
|
for (int i = 0; i < mTransformedRectanglesCount; i++) {
|
|
if (mTransformedRectangles[i].isEmpty()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int RectangleList::getTransformedRectanglesCount() const {
|
|
return mTransformedRectanglesCount;
|
|
}
|
|
|
|
const TransformedRectangle& RectangleList::getTransformedRectangle(int i) const {
|
|
return mTransformedRectangles[i];
|
|
}
|
|
|
|
void RectangleList::setEmpty() {
|
|
mTransformedRectanglesCount = 0;
|
|
}
|
|
|
|
void RectangleList::set(const Rect& bounds, const Matrix4& transform) {
|
|
mTransformedRectanglesCount = 1;
|
|
mTransformedRectangles[0] = TransformedRectangle(bounds, transform);
|
|
}
|
|
|
|
bool RectangleList::intersectWith(const Rect& bounds, const Matrix4& transform) {
|
|
TransformedRectangle newRectangle(bounds, transform);
|
|
|
|
// Try to find a rectangle with a compatible transformation
|
|
int index = 0;
|
|
for (; index < mTransformedRectanglesCount; index++) {
|
|
TransformedRectangle& tr(mTransformedRectangles[index]);
|
|
if (tr.canSimplyIntersectWith(newRectangle)) {
|
|
tr.intersectWith(newRectangle);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Add it to the list if there is room
|
|
if (index < kMaxTransformedRectangles) {
|
|
mTransformedRectangles[index] = newRectangle;
|
|
mTransformedRectanglesCount += 1;
|
|
return true;
|
|
}
|
|
|
|
// This rectangle list is full
|
|
return false;
|
|
}
|
|
|
|
Rect RectangleList::calculateBounds() const {
|
|
Rect bounds;
|
|
for (int index = 0; index < mTransformedRectanglesCount; index++) {
|
|
const TransformedRectangle& tr(mTransformedRectangles[index]);
|
|
if (index == 0) {
|
|
bounds = tr.transformedBounds();
|
|
} else {
|
|
bounds.doIntersect(tr.transformedBounds());
|
|
}
|
|
}
|
|
return bounds;
|
|
}
|
|
|
|
static SkPath pathFromTransformedRectangle(const Rect& bounds, const Matrix4& transform) {
|
|
SkPath rectPath;
|
|
SkPath rectPathTransformed;
|
|
rectPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom);
|
|
SkMatrix skTransform;
|
|
transform.copyTo(skTransform);
|
|
rectPath.transform(skTransform, &rectPathTransformed);
|
|
return rectPathTransformed;
|
|
}
|
|
|
|
SkRegion RectangleList::convertToRegion(const SkRegion& clip) const {
|
|
SkRegion rectangleListAsRegion;
|
|
for (int index = 0; index < mTransformedRectanglesCount; index++) {
|
|
const TransformedRectangle& tr(mTransformedRectangles[index]);
|
|
SkPath rectPathTransformed =
|
|
pathFromTransformedRectangle(tr.getBounds(), tr.getTransform());
|
|
if (index == 0) {
|
|
rectangleListAsRegion.setPath(rectPathTransformed, clip);
|
|
} else {
|
|
SkRegion rectRegion;
|
|
rectRegion.setPath(rectPathTransformed, clip);
|
|
rectangleListAsRegion.op(rectRegion, SkRegion::kIntersect_Op);
|
|
}
|
|
}
|
|
return rectangleListAsRegion;
|
|
}
|
|
|
|
void RectangleList::transform(const Matrix4& transform) {
|
|
for (int index = 0; index < mTransformedRectanglesCount; index++) {
|
|
mTransformedRectangles[index].transform(transform);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ClipArea
|
|
*/
|
|
|
|
ClipArea::ClipArea() : mMode(ClipMode::Rectangle) {}
|
|
|
|
/*
|
|
* Interface
|
|
*/
|
|
|
|
void ClipArea::setViewportDimensions(int width, int height) {
|
|
mPostViewportClipObserved = false;
|
|
mViewportBounds.set(0, 0, width, height);
|
|
mClipRect = mViewportBounds;
|
|
}
|
|
|
|
void ClipArea::setEmpty() {
|
|
onClipUpdated();
|
|
mMode = ClipMode::Rectangle;
|
|
mClipRect.setEmpty();
|
|
mClipRegion.setEmpty();
|
|
mRectangleList.setEmpty();
|
|
}
|
|
|
|
void ClipArea::setClip(float left, float top, float right, float bottom) {
|
|
onClipUpdated();
|
|
mMode = ClipMode::Rectangle;
|
|
mClipRect.set(left, top, right, bottom);
|
|
mClipRegion.setEmpty();
|
|
}
|
|
|
|
void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op) {
|
|
if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true;
|
|
if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
|
|
onClipUpdated();
|
|
switch (mMode) {
|
|
case ClipMode::Rectangle:
|
|
rectangleModeClipRectWithTransform(r, transform, op);
|
|
break;
|
|
case ClipMode::RectangleList:
|
|
rectangleListModeClipRectWithTransform(r, transform, op);
|
|
break;
|
|
case ClipMode::Region:
|
|
regionModeClipRectWithTransform(r, transform, op);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
|
|
if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true;
|
|
if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
|
|
onClipUpdated();
|
|
enterRegionMode();
|
|
mClipRegion.op(region, op);
|
|
onClipRegionUpdated();
|
|
}
|
|
|
|
void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform, SkRegion::Op op) {
|
|
if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true;
|
|
if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
|
|
onClipUpdated();
|
|
SkMatrix skTransform;
|
|
transform->copyTo(skTransform);
|
|
SkPath transformed;
|
|
path.transform(skTransform, &transformed);
|
|
SkRegion region;
|
|
regionFromPath(transformed, region);
|
|
enterRegionMode();
|
|
mClipRegion.op(region, op);
|
|
onClipRegionUpdated();
|
|
}
|
|
|
|
/*
|
|
* Rectangle mode
|
|
*/
|
|
|
|
void ClipArea::enterRectangleMode() {
|
|
// Entering rectangle mode discards any
|
|
// existing clipping information from the other modes.
|
|
// The only way this occurs is by a clip setting operation.
|
|
mMode = ClipMode::Rectangle;
|
|
}
|
|
|
|
void ClipArea::rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform,
|
|
SkRegion::Op op) {
|
|
if (op == SkRegion::kReplace_Op && transform->rectToRect()) {
|
|
mClipRect = r;
|
|
transform->mapRect(mClipRect);
|
|
return;
|
|
} else if (op != SkRegion::kIntersect_Op) {
|
|
enterRegionMode();
|
|
regionModeClipRectWithTransform(r, transform, op);
|
|
return;
|
|
}
|
|
|
|
if (transform->rectToRect()) {
|
|
Rect transformed(r);
|
|
transform->mapRect(transformed);
|
|
mClipRect.doIntersect(transformed);
|
|
return;
|
|
}
|
|
|
|
enterRectangleListMode();
|
|
rectangleListModeClipRectWithTransform(r, transform, op);
|
|
}
|
|
|
|
/*
|
|
* RectangleList mode implementation
|
|
*/
|
|
|
|
void ClipArea::enterRectangleListMode() {
|
|
// Is is only legal to enter rectangle list mode from
|
|
// rectangle mode, since rectangle list mode cannot represent
|
|
// all clip areas that can be represented by a region.
|
|
ALOG_ASSERT(mMode == ClipMode::Rectangle);
|
|
mMode = ClipMode::RectangleList;
|
|
mRectangleList.set(mClipRect, Matrix4::identity());
|
|
}
|
|
|
|
void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r, const mat4* transform,
|
|
SkRegion::Op op) {
|
|
if (op != SkRegion::kIntersect_Op || !mRectangleList.intersectWith(r, *transform)) {
|
|
enterRegionMode();
|
|
regionModeClipRectWithTransform(r, transform, op);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Region mode implementation
|
|
*/
|
|
|
|
void ClipArea::enterRegionMode() {
|
|
ClipMode oldMode = mMode;
|
|
mMode = ClipMode::Region;
|
|
if (oldMode != ClipMode::Region) {
|
|
if (oldMode == ClipMode::Rectangle) {
|
|
mClipRegion.setRect(mClipRect.toSkIRect());
|
|
} else {
|
|
mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
|
|
onClipRegionUpdated();
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClipArea::regionModeClipRectWithTransform(const Rect& r, const mat4* transform,
|
|
SkRegion::Op op) {
|
|
SkPath transformedRect = pathFromTransformedRectangle(r, *transform);
|
|
SkRegion transformedRectRegion;
|
|
regionFromPath(transformedRect, transformedRectRegion);
|
|
mClipRegion.op(transformedRectRegion, op);
|
|
onClipRegionUpdated();
|
|
}
|
|
|
|
void ClipArea::onClipRegionUpdated() {
|
|
if (!mClipRegion.isEmpty()) {
|
|
mClipRect.set(mClipRegion.getBounds());
|
|
|
|
if (mClipRegion.isRect()) {
|
|
mClipRegion.setEmpty();
|
|
enterRectangleMode();
|
|
}
|
|
} else {
|
|
mClipRect.setEmpty();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clip serialization
|
|
*/
|
|
|
|
const ClipBase* ClipArea::serializeClip(LinearAllocator& allocator) {
|
|
if (!mPostViewportClipObserved) {
|
|
// Only initial clip-to-viewport observed, so no serialization of clip necessary
|
|
return nullptr;
|
|
}
|
|
|
|
static_assert(std::is_trivially_destructible<Rect>::value,
|
|
"expect Rect to be trivially destructible");
|
|
static_assert(std::is_trivially_destructible<RectangleList>::value,
|
|
"expect RectangleList to be trivially destructible");
|
|
|
|
if (mLastSerialization == nullptr) {
|
|
ClipBase* serialization = nullptr;
|
|
switch (mMode) {
|
|
case ClipMode::Rectangle:
|
|
serialization = allocator.create<ClipRect>(mClipRect);
|
|
break;
|
|
case ClipMode::RectangleList:
|
|
serialization = allocator.create<ClipRectList>(mRectangleList);
|
|
serialization->rect = mRectangleList.calculateBounds();
|
|
break;
|
|
case ClipMode::Region:
|
|
serialization = allocator.create<ClipRegion>(mClipRegion);
|
|
serialization->rect.set(mClipRegion.getBounds());
|
|
break;
|
|
}
|
|
serialization->intersectWithRoot = mReplaceOpObserved;
|
|
// TODO: this is only done for draw time, should eventually avoid for record time
|
|
serialization->rect.snapToPixelBoundaries();
|
|
mLastSerialization = serialization;
|
|
}
|
|
return mLastSerialization;
|
|
}
|
|
|
|
inline static const RectangleList& getRectList(const ClipBase* scb) {
|
|
return reinterpret_cast<const ClipRectList*>(scb)->rectList;
|
|
}
|
|
|
|
inline static const SkRegion& getRegion(const ClipBase* scb) {
|
|
return reinterpret_cast<const ClipRegion*>(scb)->region;
|
|
}
|
|
|
|
// Conservative check for too many rectangles to fit in rectangle list.
|
|
// For simplicity, doesn't account for rect merging
|
|
static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) {
|
|
int currentRectCount = clipArea.isRectangleList()
|
|
? clipArea.getRectangleList().getTransformedRectanglesCount()
|
|
: 1;
|
|
int recordedRectCount = (scb->mode == ClipMode::RectangleList)
|
|
? getRectList(scb).getTransformedRectanglesCount()
|
|
: 1;
|
|
return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles;
|
|
}
|
|
|
|
static const ClipRect sEmptyClipRect(Rect(0, 0));
|
|
|
|
const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator,
|
|
const ClipBase* recordedClip,
|
|
const Matrix4& recordedClipTransform) {
|
|
// if no recordedClip passed, just serialize current state
|
|
if (!recordedClip) return serializeClip(allocator);
|
|
|
|
// if either is empty, clip is empty
|
|
if (CC_UNLIKELY(recordedClip->rect.isEmpty()) || mClipRect.isEmpty()) return &sEmptyClipRect;
|
|
|
|
if (!mLastResolutionResult || recordedClip != mLastResolutionClip ||
|
|
recordedClipTransform != mLastResolutionTransform) {
|
|
mLastResolutionClip = recordedClip;
|
|
mLastResolutionTransform = recordedClipTransform;
|
|
|
|
if (CC_LIKELY(mMode == ClipMode::Rectangle && recordedClip->mode == ClipMode::Rectangle &&
|
|
recordedClipTransform.rectToRect())) {
|
|
// common case - result is a single rectangle
|
|
auto rectClip = allocator.create<ClipRect>(recordedClip->rect);
|
|
recordedClipTransform.mapRect(rectClip->rect);
|
|
rectClip->rect.doIntersect(mClipRect);
|
|
rectClip->rect.snapToPixelBoundaries();
|
|
mLastResolutionResult = rectClip;
|
|
} else if (CC_UNLIKELY(mMode == ClipMode::Region ||
|
|
recordedClip->mode == ClipMode::Region ||
|
|
cannotFitInRectangleList(*this, recordedClip))) {
|
|
// region case
|
|
SkRegion other;
|
|
switch (recordedClip->mode) {
|
|
case ClipMode::Rectangle:
|
|
if (CC_LIKELY(recordedClipTransform.rectToRect())) {
|
|
// simple transform, skip creating SkPath
|
|
Rect resultClip(recordedClip->rect);
|
|
recordedClipTransform.mapRect(resultClip);
|
|
other.setRect(resultClip.toSkIRect());
|
|
} else {
|
|
SkPath transformedRect = pathFromTransformedRectangle(
|
|
recordedClip->rect, recordedClipTransform);
|
|
other.setPath(transformedRect, createViewportRegion());
|
|
}
|
|
break;
|
|
case ClipMode::RectangleList: {
|
|
RectangleList transformedList(getRectList(recordedClip));
|
|
transformedList.transform(recordedClipTransform);
|
|
other = transformedList.convertToRegion(createViewportRegion());
|
|
break;
|
|
}
|
|
case ClipMode::Region:
|
|
other = getRegion(recordedClip);
|
|
applyTransformToRegion(recordedClipTransform, &other);
|
|
}
|
|
|
|
ClipRegion* regionClip = allocator.create<ClipRegion>();
|
|
switch (mMode) {
|
|
case ClipMode::Rectangle:
|
|
regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op);
|
|
break;
|
|
case ClipMode::RectangleList:
|
|
regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()),
|
|
other, SkRegion::kIntersect_Op);
|
|
break;
|
|
case ClipMode::Region:
|
|
regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op);
|
|
break;
|
|
}
|
|
// Don't need to snap, since region's in int bounds
|
|
regionClip->rect.set(regionClip->region.getBounds());
|
|
mLastResolutionResult = regionClip;
|
|
} else {
|
|
auto rectListClip = allocator.create<ClipRectList>(mRectangleList);
|
|
auto&& rectList = rectListClip->rectList;
|
|
if (mMode == ClipMode::Rectangle) {
|
|
rectList.set(mClipRect, Matrix4::identity());
|
|
}
|
|
|
|
if (recordedClip->mode == ClipMode::Rectangle) {
|
|
rectList.intersectWith(recordedClip->rect, recordedClipTransform);
|
|
} else {
|
|
const RectangleList& other = getRectList(recordedClip);
|
|
for (int i = 0; i < other.getTransformedRectanglesCount(); i++) {
|
|
auto&& tr = other.getTransformedRectangle(i);
|
|
Matrix4 totalTransform(recordedClipTransform);
|
|
totalTransform.multiply(tr.getTransform());
|
|
rectList.intersectWith(tr.getBounds(), totalTransform);
|
|
}
|
|
}
|
|
rectListClip->rect = rectList.calculateBounds();
|
|
rectListClip->rect.snapToPixelBoundaries();
|
|
mLastResolutionResult = rectListClip;
|
|
}
|
|
}
|
|
return mLastResolutionResult;
|
|
}
|
|
|
|
void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) {
|
|
if (!clip) return; // nothing to do
|
|
|
|
if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) {
|
|
clipRectWithTransform(clip->rect, &transform, SkRegion::kIntersect_Op);
|
|
} else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) {
|
|
auto&& rectList = getRectList(clip);
|
|
for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) {
|
|
auto&& tr = rectList.getTransformedRectangle(i);
|
|
Matrix4 totalTransform(transform);
|
|
totalTransform.multiply(tr.getTransform());
|
|
clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op);
|
|
}
|
|
} else {
|
|
SkRegion region(getRegion(clip));
|
|
applyTransformToRegion(transform, ®ion);
|
|
clipRegion(region, SkRegion::kIntersect_Op);
|
|
}
|
|
}
|
|
|
|
void ClipArea::applyTransformToRegion(const Matrix4& transform, SkRegion* region) {
|
|
if (transform.rectToRect() && !transform.isPureTranslate()) {
|
|
// handle matrices with scale manually by mapping each rect
|
|
SkRegion other;
|
|
SkRegion::Iterator it(*region);
|
|
while (!it.done()) {
|
|
Rect rect(it.rect());
|
|
transform.mapRect(rect);
|
|
rect.snapGeometryToPixelBoundaries(true);
|
|
other.op(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kUnion_Op);
|
|
it.next();
|
|
}
|
|
region->swap(other);
|
|
} else {
|
|
// TODO: handle non-translate transforms properly!
|
|
region->translate(transform.getTranslateX(), transform.getTranslateY());
|
|
}
|
|
}
|
|
|
|
} /* namespace uirenderer */
|
|
} /* namespace android */
|