Home | History | Annotate | Download | only in hwui
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 #include "ClipArea.h"
     17 
     18 #include "utils/LinearAllocator.h"
     19 
     20 #include <SkPath.h>
     21 #include <limits>
     22 #include <type_traits>
     23 
     24 namespace android {
     25 namespace uirenderer {
     26 
     27 static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) {
     28     Vertex v = {x, y};
     29     transform.mapPoint(v.x, v.y);
     30     transformedBounds.expandToCover(v.x, v.y);
     31 }
     32 
     33 Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) {
     34     const float kMinFloat = std::numeric_limits<float>::lowest();
     35     const float kMaxFloat = std::numeric_limits<float>::max();
     36     Rect transformedBounds = {kMaxFloat, kMaxFloat, kMinFloat, kMinFloat};
     37     handlePoint(transformedBounds, transform, r.left, r.top);
     38     handlePoint(transformedBounds, transform, r.right, r.top);
     39     handlePoint(transformedBounds, transform, r.left, r.bottom);
     40     handlePoint(transformedBounds, transform, r.right, r.bottom);
     41     return transformedBounds;
     42 }
     43 
     44 void ClipBase::dump() const {
     45     ALOGD("mode %d" RECT_STRING, mode, RECT_ARGS(rect));
     46 }
     47 
     48 /*
     49  * TransformedRectangle
     50  */
     51 
     52 TransformedRectangle::TransformedRectangle() {}
     53 
     54 TransformedRectangle::TransformedRectangle(const Rect& bounds, const Matrix4& transform)
     55         : mBounds(bounds), mTransform(transform) {}
     56 
     57 bool TransformedRectangle::canSimplyIntersectWith(const TransformedRectangle& other) const {
     58     return mTransform == other.mTransform;
     59 }
     60 
     61 void TransformedRectangle::intersectWith(const TransformedRectangle& other) {
     62     mBounds.doIntersect(other.mBounds);
     63 }
     64 
     65 bool TransformedRectangle::isEmpty() const {
     66     return mBounds.isEmpty();
     67 }
     68 
     69 /*
     70  * RectangleList
     71  */
     72 
     73 RectangleList::RectangleList() : mTransformedRectanglesCount(0) {}
     74 
     75 bool RectangleList::isEmpty() const {
     76     if (mTransformedRectanglesCount < 1) {
     77         return true;
     78     }
     79 
     80     for (int i = 0; i < mTransformedRectanglesCount; i++) {
     81         if (mTransformedRectangles[i].isEmpty()) {
     82             return true;
     83         }
     84     }
     85     return false;
     86 }
     87 
     88 int RectangleList::getTransformedRectanglesCount() const {
     89     return mTransformedRectanglesCount;
     90 }
     91 
     92 const TransformedRectangle& RectangleList::getTransformedRectangle(int i) const {
     93     return mTransformedRectangles[i];
     94 }
     95 
     96 void RectangleList::setEmpty() {
     97     mTransformedRectanglesCount = 0;
     98 }
     99 
    100 void RectangleList::set(const Rect& bounds, const Matrix4& transform) {
    101     mTransformedRectanglesCount = 1;
    102     mTransformedRectangles[0] = TransformedRectangle(bounds, transform);
    103 }
    104 
    105 bool RectangleList::intersectWith(const Rect& bounds, const Matrix4& transform) {
    106     TransformedRectangle newRectangle(bounds, transform);
    107 
    108     // Try to find a rectangle with a compatible transformation
    109     int index = 0;
    110     for (; index < mTransformedRectanglesCount; index++) {
    111         TransformedRectangle& tr(mTransformedRectangles[index]);
    112         if (tr.canSimplyIntersectWith(newRectangle)) {
    113             tr.intersectWith(newRectangle);
    114             return true;
    115         }
    116     }
    117 
    118     // Add it to the list if there is room
    119     if (index < kMaxTransformedRectangles) {
    120         mTransformedRectangles[index] = newRectangle;
    121         mTransformedRectanglesCount += 1;
    122         return true;
    123     }
    124 
    125     // This rectangle list is full
    126     return false;
    127 }
    128 
    129 Rect RectangleList::calculateBounds() const {
    130     Rect bounds;
    131     for (int index = 0; index < mTransformedRectanglesCount; index++) {
    132         const TransformedRectangle& tr(mTransformedRectangles[index]);
    133         if (index == 0) {
    134             bounds = tr.transformedBounds();
    135         } else {
    136             bounds.doIntersect(tr.transformedBounds());
    137         }
    138     }
    139     return bounds;
    140 }
    141 
    142 static SkPath pathFromTransformedRectangle(const Rect& bounds, const Matrix4& transform) {
    143     SkPath rectPath;
    144     SkPath rectPathTransformed;
    145     rectPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom);
    146     SkMatrix skTransform;
    147     transform.copyTo(skTransform);
    148     rectPath.transform(skTransform, &rectPathTransformed);
    149     return rectPathTransformed;
    150 }
    151 
    152 SkRegion RectangleList::convertToRegion(const SkRegion& clip) const {
    153     SkRegion rectangleListAsRegion;
    154     for (int index = 0; index < mTransformedRectanglesCount; index++) {
    155         const TransformedRectangle& tr(mTransformedRectangles[index]);
    156         SkPath rectPathTransformed =
    157                 pathFromTransformedRectangle(tr.getBounds(), tr.getTransform());
    158         if (index == 0) {
    159             rectangleListAsRegion.setPath(rectPathTransformed, clip);
    160         } else {
    161             SkRegion rectRegion;
    162             rectRegion.setPath(rectPathTransformed, clip);
    163             rectangleListAsRegion.op(rectRegion, SkRegion::kIntersect_Op);
    164         }
    165     }
    166     return rectangleListAsRegion;
    167 }
    168 
    169 void RectangleList::transform(const Matrix4& transform) {
    170     for (int index = 0; index < mTransformedRectanglesCount; index++) {
    171         mTransformedRectangles[index].transform(transform);
    172     }
    173 }
    174 
    175 /*
    176  * ClipArea
    177  */
    178 
    179 ClipArea::ClipArea() : mMode(ClipMode::Rectangle) {}
    180 
    181 /*
    182  * Interface
    183  */
    184 
    185 void ClipArea::setViewportDimensions(int width, int height) {
    186     mPostViewportClipObserved = false;
    187     mViewportBounds.set(0, 0, width, height);
    188     mClipRect = mViewportBounds;
    189 }
    190 
    191 void ClipArea::setEmpty() {
    192     onClipUpdated();
    193     mMode = ClipMode::Rectangle;
    194     mClipRect.setEmpty();
    195     mClipRegion.setEmpty();
    196     mRectangleList.setEmpty();
    197 }
    198 
    199 void ClipArea::setClip(float left, float top, float right, float bottom) {
    200     onClipUpdated();
    201     mMode = ClipMode::Rectangle;
    202     mClipRect.set(left, top, right, bottom);
    203     mClipRegion.setEmpty();
    204 }
    205 
    206 void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op) {
    207     if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true;
    208     if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
    209     onClipUpdated();
    210     switch (mMode) {
    211         case ClipMode::Rectangle:
    212             rectangleModeClipRectWithTransform(r, transform, op);
    213             break;
    214         case ClipMode::RectangleList:
    215             rectangleListModeClipRectWithTransform(r, transform, op);
    216             break;
    217         case ClipMode::Region:
    218             regionModeClipRectWithTransform(r, transform, op);
    219             break;
    220     }
    221 }
    222 
    223 void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
    224     if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true;
    225     if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
    226     onClipUpdated();
    227     enterRegionMode();
    228     mClipRegion.op(region, op);
    229     onClipRegionUpdated();
    230 }
    231 
    232 void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform, SkRegion::Op op) {
    233     if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true;
    234     if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op;
    235     onClipUpdated();
    236     SkMatrix skTransform;
    237     transform->copyTo(skTransform);
    238     SkPath transformed;
    239     path.transform(skTransform, &transformed);
    240     SkRegion region;
    241     regionFromPath(transformed, region);
    242     enterRegionMode();
    243     mClipRegion.op(region, op);
    244     onClipRegionUpdated();
    245 }
    246 
    247 /*
    248  * Rectangle mode
    249  */
    250 
    251 void ClipArea::enterRectangleMode() {
    252     // Entering rectangle mode discards any
    253     // existing clipping information from the other modes.
    254     // The only way this occurs is by a clip setting operation.
    255     mMode = ClipMode::Rectangle;
    256 }
    257 
    258 void ClipArea::rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform,
    259                                                   SkRegion::Op op) {
    260     if (op == SkRegion::kReplace_Op && transform->rectToRect()) {
    261         mClipRect = r;
    262         transform->mapRect(mClipRect);
    263         return;
    264     } else if (op != SkRegion::kIntersect_Op) {
    265         enterRegionMode();
    266         regionModeClipRectWithTransform(r, transform, op);
    267         return;
    268     }
    269 
    270     if (transform->rectToRect()) {
    271         Rect transformed(r);
    272         transform->mapRect(transformed);
    273         mClipRect.doIntersect(transformed);
    274         return;
    275     }
    276 
    277     enterRectangleListMode();
    278     rectangleListModeClipRectWithTransform(r, transform, op);
    279 }
    280 
    281 /*
    282  * RectangleList mode implementation
    283  */
    284 
    285 void ClipArea::enterRectangleListMode() {
    286     // Is is only legal to enter rectangle list mode from
    287     // rectangle mode, since rectangle list mode cannot represent
    288     // all clip areas that can be represented by a region.
    289     ALOG_ASSERT(mMode == ClipMode::Rectangle);
    290     mMode = ClipMode::RectangleList;
    291     mRectangleList.set(mClipRect, Matrix4::identity());
    292 }
    293 
    294 void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r, const mat4* transform,
    295                                                       SkRegion::Op op) {
    296     if (op != SkRegion::kIntersect_Op || !mRectangleList.intersectWith(r, *transform)) {
    297         enterRegionMode();
    298         regionModeClipRectWithTransform(r, transform, op);
    299     }
    300 }
    301 
    302 /*
    303  * Region mode implementation
    304  */
    305 
    306 void ClipArea::enterRegionMode() {
    307     ClipMode oldMode = mMode;
    308     mMode = ClipMode::Region;
    309     if (oldMode != ClipMode::Region) {
    310         if (oldMode == ClipMode::Rectangle) {
    311             mClipRegion.setRect(mClipRect.toSkIRect());
    312         } else {
    313             mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
    314             onClipRegionUpdated();
    315         }
    316     }
    317 }
    318 
    319 void ClipArea::regionModeClipRectWithTransform(const Rect& r, const mat4* transform,
    320                                                SkRegion::Op op) {
    321     SkPath transformedRect = pathFromTransformedRectangle(r, *transform);
    322     SkRegion transformedRectRegion;
    323     regionFromPath(transformedRect, transformedRectRegion);
    324     mClipRegion.op(transformedRectRegion, op);
    325     onClipRegionUpdated();
    326 }
    327 
    328 void ClipArea::onClipRegionUpdated() {
    329     if (!mClipRegion.isEmpty()) {
    330         mClipRect.set(mClipRegion.getBounds());
    331 
    332         if (mClipRegion.isRect()) {
    333             mClipRegion.setEmpty();
    334             enterRectangleMode();
    335         }
    336     } else {
    337         mClipRect.setEmpty();
    338     }
    339 }
    340 
    341 /**
    342  * Clip serialization
    343  */
    344 
    345 const ClipBase* ClipArea::serializeClip(LinearAllocator& allocator) {
    346     if (!mPostViewportClipObserved) {
    347         // Only initial clip-to-viewport observed, so no serialization of clip necessary
    348         return nullptr;
    349     }
    350 
    351     static_assert(std::is_trivially_destructible<Rect>::value,
    352                   "expect Rect to be trivially destructible");
    353     static_assert(std::is_trivially_destructible<RectangleList>::value,
    354                   "expect RectangleList to be trivially destructible");
    355 
    356     if (mLastSerialization == nullptr) {
    357         ClipBase* serialization = nullptr;
    358         switch (mMode) {
    359             case ClipMode::Rectangle:
    360                 serialization = allocator.create<ClipRect>(mClipRect);
    361                 break;
    362             case ClipMode::RectangleList:
    363                 serialization = allocator.create<ClipRectList>(mRectangleList);
    364                 serialization->rect = mRectangleList.calculateBounds();
    365                 break;
    366             case ClipMode::Region:
    367                 serialization = allocator.create<ClipRegion>(mClipRegion);
    368                 serialization->rect.set(mClipRegion.getBounds());
    369                 break;
    370         }
    371         serialization->intersectWithRoot = mReplaceOpObserved;
    372         // TODO: this is only done for draw time, should eventually avoid for record time
    373         serialization->rect.snapToPixelBoundaries();
    374         mLastSerialization = serialization;
    375     }
    376     return mLastSerialization;
    377 }
    378 
    379 inline static const RectangleList& getRectList(const ClipBase* scb) {
    380     return reinterpret_cast<const ClipRectList*>(scb)->rectList;
    381 }
    382 
    383 inline static const SkRegion& getRegion(const ClipBase* scb) {
    384     return reinterpret_cast<const ClipRegion*>(scb)->region;
    385 }
    386 
    387 // Conservative check for too many rectangles to fit in rectangle list.
    388 // For simplicity, doesn't account for rect merging
    389 static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) {
    390     int currentRectCount = clipArea.isRectangleList()
    391                                    ? clipArea.getRectangleList().getTransformedRectanglesCount()
    392                                    : 1;
    393     int recordedRectCount = (scb->mode == ClipMode::RectangleList)
    394                                     ? getRectList(scb).getTransformedRectanglesCount()
    395                                     : 1;
    396     return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles;
    397 }
    398 
    399 static const ClipRect sEmptyClipRect(Rect(0, 0));
    400 
    401 const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator,
    402                                                    const ClipBase* recordedClip,
    403                                                    const Matrix4& recordedClipTransform) {
    404     // if no recordedClip passed, just serialize current state
    405     if (!recordedClip) return serializeClip(allocator);
    406 
    407     // if either is empty, clip is empty
    408     if (CC_UNLIKELY(recordedClip->rect.isEmpty()) || mClipRect.isEmpty()) return &sEmptyClipRect;
    409 
    410     if (!mLastResolutionResult || recordedClip != mLastResolutionClip ||
    411         recordedClipTransform != mLastResolutionTransform) {
    412         mLastResolutionClip = recordedClip;
    413         mLastResolutionTransform = recordedClipTransform;
    414 
    415         if (CC_LIKELY(mMode == ClipMode::Rectangle && recordedClip->mode == ClipMode::Rectangle &&
    416                       recordedClipTransform.rectToRect())) {
    417             // common case - result is a single rectangle
    418             auto rectClip = allocator.create<ClipRect>(recordedClip->rect);
    419             recordedClipTransform.mapRect(rectClip->rect);
    420             rectClip->rect.doIntersect(mClipRect);
    421             rectClip->rect.snapToPixelBoundaries();
    422             mLastResolutionResult = rectClip;
    423         } else if (CC_UNLIKELY(mMode == ClipMode::Region ||
    424                                recordedClip->mode == ClipMode::Region ||
    425                                cannotFitInRectangleList(*this, recordedClip))) {
    426             // region case
    427             SkRegion other;
    428             switch (recordedClip->mode) {
    429                 case ClipMode::Rectangle:
    430                     if (CC_LIKELY(recordedClipTransform.rectToRect())) {
    431                         // simple transform, skip creating SkPath
    432                         Rect resultClip(recordedClip->rect);
    433                         recordedClipTransform.mapRect(resultClip);
    434                         other.setRect(resultClip.toSkIRect());
    435                     } else {
    436                         SkPath transformedRect = pathFromTransformedRectangle(
    437                                 recordedClip->rect, recordedClipTransform);
    438                         other.setPath(transformedRect, createViewportRegion());
    439                     }
    440                     break;
    441                 case ClipMode::RectangleList: {
    442                     RectangleList transformedList(getRectList(recordedClip));
    443                     transformedList.transform(recordedClipTransform);
    444                     other = transformedList.convertToRegion(createViewportRegion());
    445                     break;
    446                 }
    447                 case ClipMode::Region:
    448                     other = getRegion(recordedClip);
    449                     applyTransformToRegion(recordedClipTransform, &other);
    450             }
    451 
    452             ClipRegion* regionClip = allocator.create<ClipRegion>();
    453             switch (mMode) {
    454                 case ClipMode::Rectangle:
    455                     regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op);
    456                     break;
    457                 case ClipMode::RectangleList:
    458                     regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()),
    459                                           other, SkRegion::kIntersect_Op);
    460                     break;
    461                 case ClipMode::Region:
    462                     regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op);
    463                     break;
    464             }
    465             // Don't need to snap, since region's in int bounds
    466             regionClip->rect.set(regionClip->region.getBounds());
    467             mLastResolutionResult = regionClip;
    468         } else {
    469             auto rectListClip = allocator.create<ClipRectList>(mRectangleList);
    470             auto&& rectList = rectListClip->rectList;
    471             if (mMode == ClipMode::Rectangle) {
    472                 rectList.set(mClipRect, Matrix4::identity());
    473             }
    474 
    475             if (recordedClip->mode == ClipMode::Rectangle) {
    476                 rectList.intersectWith(recordedClip->rect, recordedClipTransform);
    477             } else {
    478                 const RectangleList& other = getRectList(recordedClip);
    479                 for (int i = 0; i < other.getTransformedRectanglesCount(); i++) {
    480                     auto&& tr = other.getTransformedRectangle(i);
    481                     Matrix4 totalTransform(recordedClipTransform);
    482                     totalTransform.multiply(tr.getTransform());
    483                     rectList.intersectWith(tr.getBounds(), totalTransform);
    484                 }
    485             }
    486             rectListClip->rect = rectList.calculateBounds();
    487             rectListClip->rect.snapToPixelBoundaries();
    488             mLastResolutionResult = rectListClip;
    489         }
    490     }
    491     return mLastResolutionResult;
    492 }
    493 
    494 void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) {
    495     if (!clip) return;  // nothing to do
    496 
    497     if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) {
    498         clipRectWithTransform(clip->rect, &transform, SkRegion::kIntersect_Op);
    499     } else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) {
    500         auto&& rectList = getRectList(clip);
    501         for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) {
    502             auto&& tr = rectList.getTransformedRectangle(i);
    503             Matrix4 totalTransform(transform);
    504             totalTransform.multiply(tr.getTransform());
    505             clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op);
    506         }
    507     } else {
    508         SkRegion region(getRegion(clip));
    509         applyTransformToRegion(transform, &region);
    510         clipRegion(region, SkRegion::kIntersect_Op);
    511     }
    512 }
    513 
    514 void ClipArea::applyTransformToRegion(const Matrix4& transform, SkRegion* region) {
    515     if (transform.rectToRect() && !transform.isPureTranslate()) {
    516         // handle matrices with scale manually by mapping each rect
    517         SkRegion other;
    518         SkRegion::Iterator it(*region);
    519         while (!it.done()) {
    520             Rect rect(it.rect());
    521             transform.mapRect(rect);
    522             rect.snapGeometryToPixelBoundaries(true);
    523             other.op(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kUnion_Op);
    524             it.next();
    525         }
    526         region->swap(other);
    527     } else {
    528         // TODO: handle non-translate transforms properly!
    529         region->translate(transform.getTranslateX(), transform.getTranslateY());
    530     }
    531 }
    532 
    533 } /* namespace uirenderer */
    534 } /* namespace android */
    535