/* * 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); // TODO: handle non-translate transforms properly! other.translate(recordedClipTransform.getTranslateX(), recordedClipTransform.getTranslateY()); } 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)); // TODO: handle non-translate transforms properly! region.translate(transform.getTranslateX(), transform.getTranslateY()); clipRegion(region, SkRegion::kIntersect_Op); } } } /* namespace uirenderer */ } /* namespace android */