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 <log/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