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