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",
71 currentFrame->type);
72 }
73 }
74
computeCurrentTransform(Matrix4 * outMatrix) const75 void DamageAccumulator::computeCurrentTransform(Matrix4* outMatrix) const {
76 outMatrix->loadIdentity();
77 computeTransformImpl(mHead, outMatrix);
78 }
79
pushCommon()80 void DamageAccumulator::pushCommon() {
81 if (!mHead->next) {
82 DirtyStack* nextFrame = mAllocator.create_trivial<DirtyStack>();
83 nextFrame->next = nullptr;
84 nextFrame->prev = mHead;
85 mHead->next = nextFrame;
86 }
87 mHead = mHead->next;
88 mHead->pendingDirty.setEmpty();
89 }
90
pushTransform(const RenderNode * transform)91 void DamageAccumulator::pushTransform(const RenderNode* transform) {
92 pushCommon();
93 mHead->type = TransformRenderNode;
94 mHead->renderNode = transform;
95 }
96
pushTransform(const Matrix4 * transform)97 void DamageAccumulator::pushTransform(const Matrix4* transform) {
98 pushCommon();
99 mHead->type = TransformMatrix4;
100 mHead->matrix4 = transform;
101 }
102
popTransform()103 void DamageAccumulator::popTransform() {
104 LOG_ALWAYS_FATAL_IF(mHead->prev == mHead, "Cannot pop the root frame!");
105 DirtyStack* dirtyFrame = mHead;
106 mHead = mHead->prev;
107 switch (dirtyFrame->type) {
108 case TransformRenderNode:
109 applyRenderNodeTransform(dirtyFrame);
110 break;
111 case TransformMatrix4:
112 applyMatrix4Transform(dirtyFrame);
113 break;
114 case TransformNone:
115 mHead->pendingDirty.join(dirtyFrame->pendingDirty);
116 break;
117 default:
118 LOG_ALWAYS_FATAL("Tried to pop an invalid type: %d", dirtyFrame->type);
119 }
120 }
121
mapRect(const Matrix4 * matrix,const SkRect & in,SkRect * out)122 static inline void mapRect(const Matrix4* matrix, const SkRect& in, SkRect* out) {
123 if (in.isEmpty()) return;
124 Rect temp(in);
125 if (CC_LIKELY(!matrix->isPerspective())) {
126 matrix->mapRect(temp);
127 } else {
128 // Don't attempt to calculate damage for a perspective transform
129 // as the numbers this works with can break the perspective
130 // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX
131 temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
132 }
133 out->join({RECT_ARGS(temp)});
134 }
135
applyMatrix4Transform(DirtyStack * frame)136 void DamageAccumulator::applyMatrix4Transform(DirtyStack* frame) {
137 mapRect(frame->matrix4, frame->pendingDirty, &mHead->pendingDirty);
138 }
139
applyMatrix(const SkMatrix * transform,SkRect * rect)140 static inline void applyMatrix(const SkMatrix* transform, SkRect* rect) {
141 if (transform && !transform->isIdentity()) {
142 if (CC_LIKELY(!transform->hasPerspective())) {
143 transform->mapRect(rect);
144 } else {
145 // Don't attempt to calculate damage for a perspective transform
146 // as the numbers this works with can break the perspective
147 // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX
148 rect->setLTRB(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
149 }
150 }
151 }
152
applyMatrix(const SkMatrix & transform,SkRect * rect)153 static inline void applyMatrix(const SkMatrix& transform, SkRect* rect) {
154 return applyMatrix(&transform, rect);
155 }
156
mapRect(const RenderProperties & props,const SkRect & in,SkRect * out)157 static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRect* out) {
158 if (in.isEmpty()) return;
159 SkRect temp(in);
160 if (Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale) {
161 const StretchEffect& stretch = props.layerProperties().getStretchEffect();
162 if (!stretch.isEmpty()) {
163 applyMatrix(stretch.makeLinearStretch(props.getWidth(), props.getHeight()), &temp);
164 }
165 }
166 applyMatrix(props.getTransformMatrix(), &temp);
167 if (props.getStaticMatrix()) {
168 applyMatrix(props.getStaticMatrix(), &temp);
169 } else if (props.getAnimationMatrix()) {
170 applyMatrix(props.getAnimationMatrix(), &temp);
171 }
172 temp.offset(props.getLeft(), props.getTop());
173 out->join(temp);
174 }
175
findParentRenderNode(DirtyStack * frame)176 static DirtyStack* findParentRenderNode(DirtyStack* frame) {
177 while (frame->prev != frame) {
178 frame = frame->prev;
179 if (frame->type == TransformRenderNode) {
180 return frame;
181 }
182 }
183 return nullptr;
184 }
185
findProjectionReceiver(DirtyStack * frame)186 static DirtyStack* findProjectionReceiver(DirtyStack* frame) {
187 if (frame) {
188 while (frame->prev != frame) {
189 frame = frame->prev;
190 if (frame->type == TransformRenderNode && frame->renderNode->hasProjectionReceiver()) {
191 return frame;
192 }
193 }
194 }
195 return nullptr;
196 }
197
applyTransforms(DirtyStack * frame,DirtyStack * end)198 static void applyTransforms(DirtyStack* frame, DirtyStack* end) {
199 SkRect* rect = &frame->pendingDirty;
200 while (frame != end) {
201 if (frame->type == TransformRenderNode) {
202 mapRect(frame->renderNode->properties(), *rect, rect);
203 } else {
204 mapRect(frame->matrix4, *rect, rect);
205 }
206 frame = frame->prev;
207 }
208 }
209
applyRenderNodeTransform(DirtyStack * frame)210 void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) {
211 if (frame->pendingDirty.isEmpty()) {
212 return;
213 }
214
215 const RenderProperties& props = frame->renderNode->properties();
216 if (props.getAlpha() <= 0) {
217 return;
218 }
219
220 // Perform clipping
221 if (props.getClipDamageToBounds()) {
222 if (!frame->pendingDirty.intersect(SkRect::MakeIWH(props.getWidth(), props.getHeight()))) {
223 frame->pendingDirty.setEmpty();
224 }
225 }
226
227 // apply all transforms
228 mapRect(props, frame->pendingDirty, &mHead->pendingDirty);
229
230 // project backwards if necessary
231 if (props.getProjectBackwards() && !frame->pendingDirty.isEmpty()) {
232 // First, find our parent RenderNode:
233 DirtyStack* parentNode = findParentRenderNode(frame);
234 // Find our parent's projection receiver, which is what we project onto
235 DirtyStack* projectionReceiver = findProjectionReceiver(parentNode);
236 if (projectionReceiver) {
237 applyTransforms(frame, projectionReceiver);
238 projectionReceiver->pendingDirty.join(frame->pendingDirty);
239 }
240
241 frame->pendingDirty.setEmpty();
242 }
243 }
244
computeClipAndTransform(const SkRect & bounds,Matrix4 * outMatrix) const245 SkRect DamageAccumulator::computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const {
246 const DirtyStack* frame = mHead;
247 Matrix4 transform;
248 SkRect pretransformResult = bounds;
249 while (true) {
250 SkRect currentBounds = pretransformResult;
251 pretransformResult.setEmpty();
252 switch (frame->type) {
253 case TransformRenderNode: {
254 const RenderProperties& props = frame->renderNode->properties();
255 // Perform clipping
256 if (props.getClipDamageToBounds() && !currentBounds.isEmpty()) {
257 if (!currentBounds.intersect(
258 SkRect::MakeIWH(props.getWidth(), props.getHeight()))) {
259 currentBounds.setEmpty();
260 }
261 }
262
263 // apply all transforms
264 mapRect(props, currentBounds, &pretransformResult);
265 frame->renderNode->applyViewPropertyTransforms(transform);
266 } break;
267 case TransformMatrix4:
268 mapRect(frame->matrix4, currentBounds, &pretransformResult);
269 transform.multiply(*frame->matrix4);
270 break;
271 default:
272 pretransformResult = currentBounds;
273 break;
274 }
275 if (frame->prev == frame) break;
276 frame = frame->prev;
277 }
278 SkRect result;
279 Matrix4 globalToLocal;
280 globalToLocal.loadInverse(transform);
281 mapRect(&globalToLocal, pretransformResult, &result);
282 *outMatrix = transform;
283 return result;
284 }
285
dirty(float left,float top,float right,float bottom)286 void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
287 mHead->pendingDirty.join({left, top, right, bottom});
288 }
289
peekAtDirty(SkRect * dest) const290 void DamageAccumulator::peekAtDirty(SkRect* dest) const {
291 *dest = mHead->pendingDirty;
292 }
293
finish(SkRect * totalDirty)294 void DamageAccumulator::finish(SkRect* totalDirty) {
295 LOG_ALWAYS_FATAL_IF(mHead->prev != mHead, "Cannot finish, mismatched push/pop calls! %p vs. %p",
296 mHead->prev, mHead);
297 // Root node never has a transform, so this is the fully mapped dirty rect
298 *totalDirty = mHead->pendingDirty;
299 totalDirty->roundOut(totalDirty);
300 mHead->pendingDirty.setEmpty();
301 }
302
findNearestStretchEffect() const303 DamageAccumulator::StretchResult DamageAccumulator::findNearestStretchEffect() const {
304 DirtyStack* frame = mHead;
305 while (frame->prev != frame) {
306 if (frame->type == TransformRenderNode) {
307 const auto& renderNode = frame->renderNode;
308 const auto& frameRenderNodeProperties = renderNode->properties();
309 const auto& effect =
310 frameRenderNodeProperties.layerProperties().getStretchEffect();
311 const float width = (float) frameRenderNodeProperties.getWidth();
312 const float height = (float) frameRenderNodeProperties.getHeight();
313 if (!effect.isEmpty()) {
314 Matrix4 stretchMatrix;
315 computeTransformImpl(frame, &stretchMatrix);
316 Rect stretchRect = Rect(0.f, 0.f, width, height);
317 stretchMatrix.mapRect(stretchRect);
318
319 return StretchResult{
320 .stretchEffect = &effect,
321 .parentBounds = SkRect::MakeLTRB(stretchRect.left, stretchRect.top,
322 stretchRect.right, stretchRect.bottom),
323 .width = width,
324 .height = height};
325 }
326 }
327 frame = frame->prev;
328 }
329 return StretchResult{};
330 }
331
332 } /* namespace uirenderer */
333 } /* namespace android */
334