1/*
2 * Copyright (C) 2014 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
17#include "CanvasState.h"
18#include "hwui/Canvas.h"
19#include "utils/MathUtils.h"
20
21namespace android {
22namespace uirenderer {
23
24
25CanvasState::CanvasState(CanvasStateClient& renderer)
26        : mDirtyClip(false)
27        , mWidth(-1)
28        , mHeight(-1)
29        , mSaveCount(1)
30        , mCanvas(renderer)
31        , mSnapshot(&mFirstSnapshot) {
32}
33
34CanvasState::~CanvasState() {
35    // First call freeSnapshot on all but mFirstSnapshot
36    // to invoke all the dtors
37    freeAllSnapshots();
38
39    // Now actually release the memory
40    while (mSnapshotPool) {
41        void* temp = mSnapshotPool;
42        mSnapshotPool = mSnapshotPool->previous;
43        free(temp);
44    }
45}
46
47void CanvasState::initializeRecordingSaveStack(int viewportWidth, int viewportHeight) {
48    if (mWidth != viewportWidth || mHeight != viewportHeight) {
49        mWidth = viewportWidth;
50        mHeight = viewportHeight;
51        mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight);
52        mCanvas.onViewportInitialized();
53    }
54
55    freeAllSnapshots();
56    mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip);
57    mSnapshot->setRelativeLightCenter(Vector3());
58    mSaveCount = 1;
59}
60
61void CanvasState::initializeSaveStack(
62        int viewportWidth, int viewportHeight,
63        float clipLeft, float clipTop,
64        float clipRight, float clipBottom, const Vector3& lightCenter) {
65    if (mWidth != viewportWidth || mHeight != viewportHeight) {
66        mWidth = viewportWidth;
67        mHeight = viewportHeight;
68        mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight);
69        mCanvas.onViewportInitialized();
70    }
71
72    freeAllSnapshots();
73    mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip);
74    mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom);
75    mSnapshot->fbo = mCanvas.getTargetFbo();
76    mSnapshot->setRelativeLightCenter(lightCenter);
77    mSaveCount = 1;
78}
79
80Snapshot* CanvasState::allocSnapshot(Snapshot* previous, int savecount) {
81    void* memory;
82    if (mSnapshotPool) {
83        memory = mSnapshotPool;
84        mSnapshotPool = mSnapshotPool->previous;
85        mSnapshotPoolCount--;
86    } else {
87        memory = malloc(sizeof(Snapshot));
88    }
89    return new (memory) Snapshot(previous, savecount);
90}
91
92void CanvasState::freeSnapshot(Snapshot* snapshot) {
93    snapshot->~Snapshot();
94    // Arbitrary number, just don't let this grown unbounded
95    if (mSnapshotPoolCount > 10) {
96        free((void*) snapshot);
97    } else {
98        snapshot->previous = mSnapshotPool;
99        mSnapshotPool = snapshot;
100        mSnapshotPoolCount++;
101    }
102}
103
104void CanvasState::freeAllSnapshots() {
105    while (mSnapshot != &mFirstSnapshot) {
106        Snapshot* temp = mSnapshot;
107        mSnapshot = mSnapshot->previous;
108        freeSnapshot(temp);
109    }
110}
111
112///////////////////////////////////////////////////////////////////////////////
113// Save (layer)
114///////////////////////////////////////////////////////////////////////////////
115
116/**
117 * Guaranteed to save without side-effects
118 *
119 * This approach, here and in restoreSnapshot(), allows subclasses to directly manipulate the save
120 * stack, and ensures restoreToCount() doesn't call back into subclass overrides.
121 */
122int CanvasState::saveSnapshot(int flags) {
123    mSnapshot = allocSnapshot(mSnapshot, flags);
124    return mSaveCount++;
125}
126
127int CanvasState::save(int flags) {
128    return saveSnapshot(flags);
129}
130
131/**
132 * Guaranteed to restore without side-effects.
133 */
134void CanvasState::restoreSnapshot() {
135    Snapshot* toRemove = mSnapshot;
136    Snapshot* toRestore = mSnapshot->previous;
137
138    mSaveCount--;
139    mSnapshot = toRestore;
140
141    // subclass handles restore implementation
142    mCanvas.onSnapshotRestored(*toRemove, *toRestore);
143
144    freeSnapshot(toRemove);
145}
146
147void CanvasState::restore() {
148    if (mSaveCount > 1) {
149        restoreSnapshot();
150    }
151}
152
153void CanvasState::restoreToCount(int saveCount) {
154    if (saveCount < 1) saveCount = 1;
155
156    while (mSaveCount > saveCount) {
157        restoreSnapshot();
158    }
159}
160
161///////////////////////////////////////////////////////////////////////////////
162// Matrix
163///////////////////////////////////////////////////////////////////////////////
164
165void CanvasState::getMatrix(SkMatrix* matrix) const {
166    mSnapshot->transform->copyTo(*matrix);
167}
168
169void CanvasState::translate(float dx, float dy, float dz) {
170    mSnapshot->transform->translate(dx, dy, dz);
171}
172
173void CanvasState::rotate(float degrees) {
174    mSnapshot->transform->rotate(degrees, 0.0f, 0.0f, 1.0f);
175}
176
177void CanvasState::scale(float sx, float sy) {
178    mSnapshot->transform->scale(sx, sy, 1.0f);
179}
180
181void CanvasState::skew(float sx, float sy) {
182    mSnapshot->transform->skew(sx, sy);
183}
184
185void CanvasState::setMatrix(const SkMatrix& matrix) {
186    mSnapshot->transform->load(matrix);
187}
188
189void CanvasState::setMatrix(const Matrix4& matrix) {
190    *(mSnapshot->transform) = matrix;
191}
192
193void CanvasState::concatMatrix(const SkMatrix& matrix) {
194    mat4 transform(matrix);
195    mSnapshot->transform->multiply(transform);
196}
197
198void CanvasState::concatMatrix(const Matrix4& matrix) {
199    mSnapshot->transform->multiply(matrix);
200}
201
202///////////////////////////////////////////////////////////////////////////////
203// Clip
204///////////////////////////////////////////////////////////////////////////////
205
206bool CanvasState::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
207    mSnapshot->clip(Rect(left, top, right, bottom), op);
208    mDirtyClip = true;
209    return !mSnapshot->clipIsEmpty();
210}
211
212bool CanvasState::clipPath(const SkPath* path, SkRegion::Op op) {
213    mSnapshot->clipPath(*path, op);
214    mDirtyClip = true;
215    return !mSnapshot->clipIsEmpty();
216}
217
218bool CanvasState::clipRegion(const SkRegion* region, SkRegion::Op op) {
219    mSnapshot->clipRegionTransformed(*region, op);
220    mDirtyClip = true;
221    return !mSnapshot->clipIsEmpty();
222}
223
224void CanvasState::setClippingOutline(LinearAllocator& allocator, const Outline* outline) {
225    Rect bounds;
226    float radius;
227    if (!outline->getAsRoundRect(&bounds, &radius)) return; // only RR supported
228
229    bool outlineIsRounded = MathUtils::isPositive(radius);
230    if (!outlineIsRounded || currentTransform()->isSimple()) {
231        // TODO: consider storing this rect separately, so that this can't be replaced with clip ops
232        clipRect(bounds.left, bounds.top, bounds.right, bounds.bottom, SkRegion::kIntersect_Op);
233    }
234    if (outlineIsRounded) {
235        setClippingRoundRect(allocator, bounds, radius, false);
236    }
237}
238
239void CanvasState::setClippingRoundRect(LinearAllocator& allocator,
240        const Rect& rect, float radius, bool highPriority) {
241    mSnapshot->setClippingRoundRect(allocator, rect, radius, highPriority);
242}
243
244void CanvasState::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) {
245    mSnapshot->setProjectionPathMask(allocator, path);
246}
247
248///////////////////////////////////////////////////////////////////////////////
249// Quick Rejection
250///////////////////////////////////////////////////////////////////////////////
251
252/**
253 * Calculates whether content drawn within the passed bounds would be outside of, or intersect with
254 * the clipRect. Does not modify the scissor.
255 *
256 * @param clipRequired if not null, will be set to true if element intersects clip
257 *         (and wasn't rejected)
258 *
259 * @param snapOut if set, the geometry will be treated as having an AA ramp.
260 *         See Rect::snapGeometryToPixelBoundaries()
261 */
262bool CanvasState::calculateQuickRejectForScissor(float left, float top,
263        float right, float bottom,
264        bool* clipRequired, bool* roundRectClipRequired,
265        bool snapOut) const {
266    if (mSnapshot->isIgnored() || bottom <= top || right <= left) {
267        return true;
268    }
269
270    Rect r(left, top, right, bottom);
271    currentTransform()->mapRect(r);
272    r.snapGeometryToPixelBoundaries(snapOut);
273
274    Rect clipRect(currentRenderTargetClip());
275    clipRect.snapToPixelBoundaries();
276
277    if (!clipRect.intersects(r)) return true;
278
279    // clip is required if geometry intersects clip rect
280    if (clipRequired) {
281        *clipRequired = !clipRect.contains(r);
282    }
283
284    // round rect clip is required if RR clip exists, and geometry intersects its corners
285    if (roundRectClipRequired) {
286        *roundRectClipRequired = mSnapshot->roundRectClipState != nullptr
287                && mSnapshot->roundRectClipState->areaRequiresRoundRectClip(r);
288    }
289    return false;
290}
291
292bool CanvasState::quickRejectConservative(float left, float top,
293        float right, float bottom) const {
294    if (mSnapshot->isIgnored() || bottom <= top || right <= left) {
295        return true;
296    }
297
298    Rect r(left, top, right, bottom);
299    currentTransform()->mapRect(r);
300    r.roundOut(); // rounded out to be conservative
301
302    Rect clipRect(currentRenderTargetClip());
303    clipRect.snapToPixelBoundaries();
304
305    if (!clipRect.intersects(r)) return true;
306
307    return false;
308}
309
310} // namespace uirenderer
311} // namespace android
312