• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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