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