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 
24 namespace android {
25 namespace uirenderer {
26 
27 enum TransformType {
28     TransformInvalid = 0,
29     TransformRenderNode,
30     TransformMatrix4,
31     TransformNone,
32 };
33 
34 struct 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 
DamageAccumulator()47 DamageAccumulator::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 
computeTransformImpl(const DirtyStack * currentFrame,Matrix4 * outMatrix)55 static 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 
computeCurrentTransform(Matrix4 * outMatrix) const74 void DamageAccumulator::computeCurrentTransform(Matrix4* outMatrix) const {
75     outMatrix->loadIdentity();
76     computeTransformImpl(mHead, outMatrix);
77 }
78 
pushCommon()79 void 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 
pushTransform(const RenderNode * transform)90 void DamageAccumulator::pushTransform(const RenderNode* transform) {
91     pushCommon();
92     mHead->type = TransformRenderNode;
93     mHead->renderNode = transform;
94 }
95 
pushTransform(const Matrix4 * transform)96 void DamageAccumulator::pushTransform(const Matrix4* transform) {
97     pushCommon();
98     mHead->type = TransformMatrix4;
99     mHead->matrix4 = transform;
100 }
101 
popTransform()102 void 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 
mapRect(const Matrix4 * matrix,const SkRect & in,SkRect * out)121 static 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 
applyMatrix4Transform(DirtyStack * frame)135 void DamageAccumulator::applyMatrix4Transform(DirtyStack* frame) {
136     mapRect(frame->matrix4, frame->pendingDirty, &mHead->pendingDirty);
137 }
138 
mapRect(const RenderProperties & props,const SkRect & in,SkRect * out)139 static 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 
findParentRenderNode(DirtyStack * frame)157 static 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 
findProjectionReceiver(DirtyStack * frame)167 static 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 
applyTransforms(DirtyStack * frame,DirtyStack * end)180 static 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 
applyRenderNodeTransform(DirtyStack * frame)192 void 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 
dirty(float left,float top,float right,float bottom)227 void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
228     mHead->pendingDirty.join(left, top, right, bottom);
229 }
230 
peekAtDirty(SkRect * dest) const231 void DamageAccumulator::peekAtDirty(SkRect* dest) const {
232     *dest = mHead->pendingDirty;
233 }
234 
finish(SkRect * totalDirty)235 void 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