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