ClipArea.cpp revision b4f4f3e16d8fbb2905dfc168383213161e988f83
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        ClipBase* serialization;
365        switch (mMode) {
366        case ClipMode::Rectangle:
367            serialization = allocator.create<ClipRect>(mClipRect);
368            break;
369        case ClipMode::RectangleList:
370            serialization = allocator.create<ClipRectList>(mRectangleList);
371            serialization->rect = mRectangleList.calculateBounds();
372            break;
373        case ClipMode::Region:
374            serialization = allocator.create<ClipRegion>(mClipRegion);
375            serialization->rect.set(mClipRegion.getBounds());
376            break;
377        }
378        mLastSerialization = serialization;
379    }
380    return mLastSerialization;
381}
382
383inline static const Rect& getRect(const ClipBase* scb) {
384    return reinterpret_cast<const ClipRect*>(scb)->rect;
385}
386
387inline static const RectangleList& getRectList(const ClipBase* scb) {
388    return reinterpret_cast<const ClipRectList*>(scb)->rectList;
389}
390
391inline static const SkRegion& getRegion(const ClipBase* scb) {
392    return reinterpret_cast<const ClipRegion*>(scb)->region;
393}
394
395// Conservative check for too many rectangles to fit in rectangle list.
396// For simplicity, doesn't account for rect merging
397static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) {
398    int currentRectCount = clipArea.isRectangleList()
399            ? clipArea.getRectangleList().getTransformedRectanglesCount()
400            : 1;
401    int recordedRectCount = (scb->mode == ClipMode::RectangleList)
402            ? getRectList(scb).getTransformedRectanglesCount()
403            : 1;
404    return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles;
405}
406
407const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator,
408        const ClipBase* recordedClip, const Matrix4& recordedClipTransform) {
409    // if no recordedClip passed, just serialize current state
410    if (!recordedClip) return serializeClip(allocator);
411
412    if (!mLastResolutionResult
413            || recordedClip != mLastResolutionClip
414            || recordedClipTransform != mLastResolutionTransform) {
415        mLastResolutionClip = recordedClip;
416        mLastResolutionTransform = recordedClipTransform;
417
418        if (CC_LIKELY(mMode == ClipMode::Rectangle
419                && recordedClip->mode == ClipMode::Rectangle
420                && recordedClipTransform.rectToRect())) {
421            // common case - result is a single rectangle
422            auto rectClip = allocator.create<ClipRect>(getRect(recordedClip));
423            recordedClipTransform.mapRect(rectClip->rect);
424            rectClip->rect.doIntersect(mClipRect);
425            mLastResolutionResult = rectClip;
426        } else if (CC_UNLIKELY(mMode == ClipMode::Region
427                || recordedClip->mode == ClipMode::Region
428                || cannotFitInRectangleList(*this, recordedClip))) {
429            // region case
430            SkRegion other;
431            switch (recordedClip->mode) {
432            case ClipMode::Rectangle:
433                if (CC_LIKELY(recordedClipTransform.rectToRect())) {
434                    // simple transform, skip creating SkPath
435                    Rect resultClip(getRect(recordedClip));
436                    recordedClipTransform.mapRect(resultClip);
437                    other.setRect(resultClip.toSkIRect());
438                } else {
439                    SkPath transformedRect = pathFromTransformedRectangle(getRect(recordedClip),
440                            recordedClipTransform);
441                    other.setPath(transformedRect, createViewportRegion());
442                }
443                break;
444            case ClipMode::RectangleList: {
445                RectangleList transformedList(getRectList(recordedClip));
446                transformedList.transform(recordedClipTransform);
447                other = transformedList.convertToRegion(createViewportRegion());
448                break;
449            }
450            case ClipMode::Region:
451                other = getRegion(recordedClip);
452
453                // TODO: handle non-translate transforms properly!
454                other.translate(recordedClipTransform.getTranslateX(),
455                        recordedClipTransform.getTranslateY());
456            }
457
458            ClipRegion* regionClip = allocator.create<ClipRegion>();
459            switch (mMode) {
460            case ClipMode::Rectangle:
461                regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op);
462                break;
463            case ClipMode::RectangleList:
464                regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()),
465                        other, SkRegion::kIntersect_Op);
466                break;
467            case ClipMode::Region:
468                regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op);
469                break;
470            }
471            regionClip->rect.set(regionClip->region.getBounds());
472            mLastResolutionResult = regionClip;
473        } else {
474            auto rectListClip = allocator.create<ClipRectList>(mRectangleList);
475            auto&& rectList = rectListClip->rectList;
476            if (mMode == ClipMode::Rectangle) {
477                rectList.set(mClipRect, Matrix4::identity());
478            }
479
480            if (recordedClip->mode == ClipMode::Rectangle) {
481                rectList.intersectWith(getRect(recordedClip), recordedClipTransform);
482            } else {
483                const RectangleList& other = getRectList(recordedClip);
484                for (int i = 0; i < other.getTransformedRectanglesCount(); i++) {
485                    auto&& tr = other.getTransformedRectangle(i);
486                    Matrix4 totalTransform(recordedClipTransform);
487                    totalTransform.multiply(tr.getTransform());
488                    rectList.intersectWith(tr.getBounds(), totalTransform);
489                }
490            }
491            rectListClip->rect = rectList.calculateBounds();
492            mLastResolutionResult = rectListClip;
493        }
494    }
495    return mLastResolutionResult;
496}
497
498void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) {
499    if (!clip) return; // nothing to do
500
501    if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) {
502        clipRectWithTransform(getRect(clip), &transform, SkRegion::kIntersect_Op);
503    } else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) {
504        auto&& rectList = getRectList(clip);
505        for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) {
506            auto&& tr = rectList.getTransformedRectangle(i);
507            Matrix4 totalTransform(transform);
508            totalTransform.multiply(tr.getTransform());
509            clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op);
510        }
511    } else {
512        SkRegion region(getRegion(clip));
513        // TODO: handle non-translate transforms properly!
514        region.translate(transform.getTranslateX(), transform.getTranslateY());
515        clipRegion(region, SkRegion::kIntersect_Op);
516    }
517}
518
519} /* namespace uirenderer */
520} /* namespace android */
521