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 "DamageAccumulator.h"
18
19#include <cutils/log.h>
20
21#include "RenderNode.h"
22#include "utils/MathUtils.h"
23
24namespace android {
25namespace uirenderer {
26
27enum TransformType {
28    TransformInvalid = 0,
29    TransformRenderNode,
30    TransformMatrix4,
31    TransformNone,
32};
33
34struct DirtyStack {
35    TransformType type;
36    union {
37        const RenderNode* renderNode;
38        const Matrix4* matrix4;
39    };
40    // When this frame is pop'd, this rect is mapped through the above transform
41    // and applied to the previous (aka parent) frame
42    SkRect pendingDirty;
43    DirtyStack* prev;
44    DirtyStack* next;
45};
46
47DamageAccumulator::DamageAccumulator() {
48    mHead = mAllocator.create_trivial<DirtyStack>();
49    memset(mHead, 0, sizeof(DirtyStack));
50    // Create a root that we will not pop off
51    mHead->prev = mHead;
52    mHead->type = TransformNone;
53}
54
55static void computeTransformImpl(const DirtyStack* currentFrame, Matrix4* outMatrix) {
56    if (currentFrame->prev != currentFrame) {
57        computeTransformImpl(currentFrame->prev, outMatrix);
58    }
59    switch (currentFrame->type) {
60    case TransformRenderNode:
61        currentFrame->renderNode->applyViewPropertyTransforms(*outMatrix);
62        break;
63    case TransformMatrix4:
64        outMatrix->multiply(*currentFrame->matrix4);
65        break;
66    case TransformNone:
67        // nothing to be done
68        break;
69    default:
70        LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d", currentFrame->type);
71    }
72}
73
74void DamageAccumulator::computeCurrentTransform(Matrix4* outMatrix) const {
75    outMatrix->loadIdentity();
76    computeTransformImpl(mHead, outMatrix);
77}
78
79void DamageAccumulator::pushCommon() {
80    if (!mHead->next) {
81        DirtyStack* nextFrame = mAllocator.create_trivial<DirtyStack>();
82        nextFrame->next = nullptr;
83        nextFrame->prev = mHead;
84        mHead->next = nextFrame;
85    }
86    mHead = mHead->next;
87    mHead->pendingDirty.setEmpty();
88}
89
90void DamageAccumulator::pushTransform(const RenderNode* transform) {
91    pushCommon();
92    mHead->type = TransformRenderNode;
93    mHead->renderNode = transform;
94}
95
96void DamageAccumulator::pushTransform(const Matrix4* transform) {
97    pushCommon();
98    mHead->type = TransformMatrix4;
99    mHead->matrix4 = transform;
100}
101
102void DamageAccumulator::popTransform() {
103    LOG_ALWAYS_FATAL_IF(mHead->prev == mHead, "Cannot pop the root frame!");
104    DirtyStack* dirtyFrame = mHead;
105    mHead = mHead->prev;
106    switch (dirtyFrame->type) {
107    case TransformRenderNode:
108        applyRenderNodeTransform(dirtyFrame);
109        break;
110    case TransformMatrix4:
111        applyMatrix4Transform(dirtyFrame);
112        break;
113    case TransformNone:
114        mHead->pendingDirty.join(dirtyFrame->pendingDirty);
115        break;
116    default:
117        LOG_ALWAYS_FATAL("Tried to pop an invalid type: %d", dirtyFrame->type);
118    }
119}
120
121static inline void mapRect(const Matrix4* matrix, const SkRect& in, SkRect* out) {
122    if (in.isEmpty()) return;
123    Rect temp(in);
124    if (CC_LIKELY(!matrix->isPerspective())) {
125        matrix->mapRect(temp);
126    } else {
127        // Don't attempt to calculate damage for a perspective transform
128        // as the numbers this works with can break the perspective
129        // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX
130        temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
131    }
132    out->join(RECT_ARGS(temp));
133}
134
135void DamageAccumulator::applyMatrix4Transform(DirtyStack* frame) {
136    mapRect(frame->matrix4, frame->pendingDirty, &mHead->pendingDirty);
137}
138
139static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRect* out) {
140    if (in.isEmpty()) return;
141    const SkMatrix* transform = props.getTransformMatrix();
142    SkRect temp(in);
143    if (transform && !transform->isIdentity()) {
144        if (CC_LIKELY(!transform->hasPerspective())) {
145            transform->mapRect(&temp);
146        } else {
147            // Don't attempt to calculate damage for a perspective transform
148            // as the numbers this works with can break the perspective
149            // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX
150            temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
151        }
152    }
153    temp.offset(props.getLeft(), props.getTop());
154    out->join(temp);
155}
156
157static DirtyStack* findParentRenderNode(DirtyStack* frame) {
158    while (frame->prev != frame) {
159        frame = frame->prev;
160        if (frame->type == TransformRenderNode) {
161            return frame;
162        }
163    }
164    return nullptr;
165}
166
167static DirtyStack* findProjectionReceiver(DirtyStack* frame) {
168    if (frame) {
169        while (frame->prev != frame) {
170            frame = frame->prev;
171            if (frame->type == TransformRenderNode
172                    && frame->renderNode->hasProjectionReceiver()) {
173                return frame;
174            }
175        }
176    }
177    return nullptr;
178}
179
180static void applyTransforms(DirtyStack* frame, DirtyStack* end) {
181    SkRect* rect = &frame->pendingDirty;
182    while (frame != end) {
183        if (frame->type == TransformRenderNode) {
184            mapRect(frame->renderNode->properties(), *rect, rect);
185        } else {
186            mapRect(frame->matrix4, *rect, rect);
187        }
188        frame = frame->prev;
189    }
190}
191
192void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) {
193    if (frame->pendingDirty.isEmpty()) {
194        return;
195    }
196
197    const RenderProperties& props = frame->renderNode->properties();
198    if (props.getAlpha() <= 0) {
199        return;
200    }
201
202    // Perform clipping
203    if (props.getClipDamageToBounds() && !frame->pendingDirty.isEmpty()) {
204        if (!frame->pendingDirty.intersect(0, 0, props.getWidth(), props.getHeight())) {
205            frame->pendingDirty.setEmpty();
206        }
207    }
208
209    // apply all transforms
210    mapRect(props, frame->pendingDirty, &mHead->pendingDirty);
211
212    // project backwards if necessary
213    if (props.getProjectBackwards() && !frame->pendingDirty.isEmpty()) {
214        // First, find our parent RenderNode:
215        DirtyStack* parentNode = findParentRenderNode(frame);
216        // Find our parent's projection receiver, which is what we project onto
217        DirtyStack* projectionReceiver = findProjectionReceiver(parentNode);
218        if (projectionReceiver) {
219            applyTransforms(frame, projectionReceiver);
220            projectionReceiver->pendingDirty.join(frame->pendingDirty);
221        }
222
223        frame->pendingDirty.setEmpty();
224    }
225}
226
227void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
228    mHead->pendingDirty.join(left, top, right, bottom);
229}
230
231void DamageAccumulator::peekAtDirty(SkRect* dest) const {
232    *dest = mHead->pendingDirty;
233}
234
235void DamageAccumulator::finish(SkRect* totalDirty) {
236    LOG_ALWAYS_FATAL_IF(mHead->prev != mHead, "Cannot finish, mismatched push/pop calls! %p vs. %p", mHead->prev, mHead);
237    // Root node never has a transform, so this is the fully mapped dirty rect
238    *totalDirty = mHead->pendingDirty;
239    totalDirty->roundOut(totalDirty);
240    mHead->pendingDirty.setEmpty();
241}
242
243} /* namespace uirenderer */
244} /* namespace android */
245