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 #pragma once
18 
19 #include "Caches.h"
20 #include "DeviceInfo.h"
21 #include "Outline.h"
22 #include "Rect.h"
23 #include "RevealClip.h"
24 #include "utils/MathUtils.h"
25 #include "utils/PaintUtils.h"
26 
27 #include <SkBlendMode.h>
28 #include <SkCamera.h>
29 #include <SkColor.h>
30 #include <SkMatrix.h>
31 #include <SkRegion.h>
32 
33 #include <androidfw/ResourceTypes.h>
34 #include <cutils/compiler.h>
35 #include <stddef.h>
36 #include <utils/Log.h>
37 #include <algorithm>
38 #include <ostream>
39 #include <vector>
40 
41 class SkBitmap;
42 class SkColorFilter;
43 class SkPaint;
44 
45 namespace android {
46 namespace uirenderer {
47 
48 class Matrix4;
49 class RenderNode;
50 class RenderProperties;
51 
52 // The __VA_ARGS__ will be executed if a & b are not equal
53 #define RP_SET(a, b, ...) ((a) != (b) ? ((a) = (b), ##__VA_ARGS__, true) : false)
54 #define RP_SET_AND_DIRTY(a, b) RP_SET(a, b, mPrimitiveFields.mMatrixOrPivotDirty = true)
55 
56 // Keep in sync with View.java:LAYER_TYPE_*
57 enum class LayerType {
58     None = 0,
59     // We cannot build the software layer directly (must be done at record time) and all management
60     // of software layers is handled in Java.
61     Software = 1,
62     RenderLayer = 2,
63 };
64 
65 enum ClippingFlags {
66     CLIP_TO_BOUNDS = 0x1 << 0,
67     CLIP_TO_CLIP_BOUNDS = 0x1 << 1,
68 };
69 
70 class ANDROID_API LayerProperties {
71 public:
setType(LayerType type)72     bool setType(LayerType type) {
73         if (RP_SET(mType, type)) {
74             reset();
75             return true;
76         }
77         return false;
78     }
79 
setOpaque(bool opaque)80     bool setOpaque(bool opaque) { return RP_SET(mOpaque, opaque); }
81 
opaque()82     bool opaque() const { return mOpaque; }
83 
setAlpha(uint8_t alpha)84     bool setAlpha(uint8_t alpha) { return RP_SET(mAlpha, alpha); }
85 
alpha()86     uint8_t alpha() const { return mAlpha; }
87 
setXferMode(SkBlendMode mode)88     bool setXferMode(SkBlendMode mode) { return RP_SET(mMode, mode); }
89 
xferMode()90     SkBlendMode xferMode() const { return mMode; }
91 
92     bool setColorFilter(SkColorFilter* filter);
93 
colorFilter()94     SkColorFilter* colorFilter() const { return mColorFilter; }
95 
96     // Sets alpha, xfermode, and colorfilter from an SkPaint
97     // paint may be NULL, in which case defaults will be set
98     bool setFromPaint(const SkPaint* paint);
99 
needsBlending()100     bool needsBlending() const { return !opaque() || alpha() < 255; }
101 
102     LayerProperties& operator=(const LayerProperties& other);
103 
104 private:
105     LayerProperties();
106     ~LayerProperties();
107     void reset();
108 
109     // Private since external users should go through properties().effectiveLayerType()
type()110     LayerType type() const { return mType; }
111 
112     friend class RenderProperties;
113 
114     LayerType mType = LayerType::None;
115     // Whether or not that Layer's content is opaque, doesn't include alpha
116     bool mOpaque;
117     uint8_t mAlpha;
118     SkBlendMode mMode;
119     SkColorFilter* mColorFilter = nullptr;
120 };
121 
122 /*
123  * Data structure that holds the properties for a RenderNode
124  */
125 class ANDROID_API RenderProperties {
126 public:
127     RenderProperties();
128     virtual ~RenderProperties();
129 
setFlag(int flag,bool newValue,int * outFlags)130     static bool setFlag(int flag, bool newValue, int* outFlags) {
131         if (newValue) {
132             if (!(flag & *outFlags)) {
133                 *outFlags |= flag;
134                 return true;
135             }
136             return false;
137         } else {
138             if (flag & *outFlags) {
139                 *outFlags &= ~flag;
140                 return true;
141             }
142             return false;
143         }
144     }
145 
146     /**
147      * Set internal layer state based on whether this layer
148      *
149      * Additionally, returns true if child RenderNodes with functors will need to use a layer
150      * to support clipping.
151      */
prepareForFunctorPresence(bool willHaveFunctor,bool ancestorDictatesFunctorsNeedLayer)152     bool prepareForFunctorPresence(bool willHaveFunctor, bool ancestorDictatesFunctorsNeedLayer) {
153         // parent may have already dictated that a descendant layer is needed
154         bool functorsNeedLayer =
155                 ancestorDictatesFunctorsNeedLayer
156 
157                 // Round rect clipping forces layer for functors
158                 || CC_UNLIKELY(getOutline().willRoundRectClip()) ||
159                 CC_UNLIKELY(getRevealClip().willClip())
160 
161                 // Complex matrices forces layer, due to stencil clipping
162                 || CC_UNLIKELY(getTransformMatrix() && !getTransformMatrix()->isScaleTranslate()) ||
163                 CC_UNLIKELY(getAnimationMatrix() && !getAnimationMatrix()->isScaleTranslate()) ||
164                 CC_UNLIKELY(getStaticMatrix() && !getStaticMatrix()->isScaleTranslate());
165 
166         mComputedFields.mNeedLayerForFunctors = (willHaveFunctor && functorsNeedLayer);
167 
168         // If on a layer, will have consumed the need for isolating functors from stencil.
169         // Thus, it's safe to reset the flag until some descendent sets it.
170         return CC_LIKELY(effectiveLayerType() == LayerType::None) && functorsNeedLayer;
171     }
172 
173     RenderProperties& operator=(const RenderProperties& other);
174 
setClipToBounds(bool clipToBounds)175     bool setClipToBounds(bool clipToBounds) {
176         return setFlag(CLIP_TO_BOUNDS, clipToBounds, &mPrimitiveFields.mClippingFlags);
177     }
178 
setClipBounds(const Rect & clipBounds)179     bool setClipBounds(const Rect& clipBounds) {
180         bool ret = setFlag(CLIP_TO_CLIP_BOUNDS, true, &mPrimitiveFields.mClippingFlags);
181         return RP_SET(mPrimitiveFields.mClipBounds, clipBounds) || ret;
182     }
183 
setClipBoundsEmpty()184     bool setClipBoundsEmpty() {
185         return setFlag(CLIP_TO_CLIP_BOUNDS, false, &mPrimitiveFields.mClippingFlags);
186     }
187 
setProjectBackwards(bool shouldProject)188     bool setProjectBackwards(bool shouldProject) {
189         return RP_SET(mPrimitiveFields.mProjectBackwards, shouldProject);
190     }
191 
setProjectionReceiver(bool shouldReceive)192     bool setProjectionReceiver(bool shouldReceive) {
193         return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldReceive);
194     }
195 
isProjectionReceiver()196     bool isProjectionReceiver() const { return mPrimitiveFields.mProjectionReceiver; }
197 
setStaticMatrix(const SkMatrix * matrix)198     bool setStaticMatrix(const SkMatrix* matrix) {
199         delete mStaticMatrix;
200         if (matrix) {
201             mStaticMatrix = new SkMatrix(*matrix);
202         } else {
203             mStaticMatrix = nullptr;
204         }
205         return true;
206     }
207 
208     // Can return NULL
getStaticMatrix()209     const SkMatrix* getStaticMatrix() const { return mStaticMatrix; }
210 
setAnimationMatrix(const SkMatrix * matrix)211     bool setAnimationMatrix(const SkMatrix* matrix) {
212         delete mAnimationMatrix;
213         if (matrix) {
214             mAnimationMatrix = new SkMatrix(*matrix);
215         } else {
216             mAnimationMatrix = nullptr;
217         }
218         return true;
219     }
220 
setAlpha(float alpha)221     bool setAlpha(float alpha) {
222         alpha = MathUtils::clampAlpha(alpha);
223         return RP_SET(mPrimitiveFields.mAlpha, alpha);
224     }
225 
getAlpha()226     float getAlpha() const { return mPrimitiveFields.mAlpha; }
227 
setHasOverlappingRendering(bool hasOverlappingRendering)228     bool setHasOverlappingRendering(bool hasOverlappingRendering) {
229         return RP_SET(mPrimitiveFields.mHasOverlappingRendering, hasOverlappingRendering);
230     }
231 
hasOverlappingRendering()232     bool hasOverlappingRendering() const { return mPrimitiveFields.mHasOverlappingRendering; }
233 
setElevation(float elevation)234     bool setElevation(float elevation) {
235         return RP_SET(mPrimitiveFields.mElevation, elevation);
236         // Don't dirty matrix/pivot, since they don't respect Z
237     }
238 
getElevation()239     float getElevation() const { return mPrimitiveFields.mElevation; }
240 
setTranslationX(float translationX)241     bool setTranslationX(float translationX) {
242         return RP_SET_AND_DIRTY(mPrimitiveFields.mTranslationX, translationX);
243     }
244 
getTranslationX()245     float getTranslationX() const { return mPrimitiveFields.mTranslationX; }
246 
setTranslationY(float translationY)247     bool setTranslationY(float translationY) {
248         return RP_SET_AND_DIRTY(mPrimitiveFields.mTranslationY, translationY);
249     }
250 
getTranslationY()251     float getTranslationY() const { return mPrimitiveFields.mTranslationY; }
252 
setTranslationZ(float translationZ)253     bool setTranslationZ(float translationZ) {
254         return RP_SET(mPrimitiveFields.mTranslationZ, translationZ);
255         // mMatrixOrPivotDirty not set, since matrix doesn't respect Z
256     }
257 
getTranslationZ()258     float getTranslationZ() const { return mPrimitiveFields.mTranslationZ; }
259 
260     // Animation helper
setX(float value)261     bool setX(float value) { return setTranslationX(value - getLeft()); }
262 
263     // Animation helper
getX()264     float getX() const { return getLeft() + getTranslationX(); }
265 
266     // Animation helper
setY(float value)267     bool setY(float value) { return setTranslationY(value - getTop()); }
268 
269     // Animation helper
getY()270     float getY() const { return getTop() + getTranslationY(); }
271 
272     // Animation helper
setZ(float value)273     bool setZ(float value) { return setTranslationZ(value - getElevation()); }
274 
getZ()275     float getZ() const { return getElevation() + getTranslationZ(); }
276 
setRotation(float rotation)277     bool setRotation(float rotation) {
278         return RP_SET_AND_DIRTY(mPrimitiveFields.mRotation, rotation);
279     }
280 
getRotation()281     float getRotation() const { return mPrimitiveFields.mRotation; }
282 
setRotationX(float rotationX)283     bool setRotationX(float rotationX) {
284         return RP_SET_AND_DIRTY(mPrimitiveFields.mRotationX, rotationX);
285     }
286 
getRotationX()287     float getRotationX() const { return mPrimitiveFields.mRotationX; }
288 
setRotationY(float rotationY)289     bool setRotationY(float rotationY) {
290         return RP_SET_AND_DIRTY(mPrimitiveFields.mRotationY, rotationY);
291     }
292 
getRotationY()293     float getRotationY() const { return mPrimitiveFields.mRotationY; }
294 
setScaleX(float scaleX)295     bool setScaleX(float scaleX) { return RP_SET_AND_DIRTY(mPrimitiveFields.mScaleX, scaleX); }
296 
getScaleX()297     float getScaleX() const { return mPrimitiveFields.mScaleX; }
298 
setScaleY(float scaleY)299     bool setScaleY(float scaleY) { return RP_SET_AND_DIRTY(mPrimitiveFields.mScaleY, scaleY); }
300 
getScaleY()301     float getScaleY() const { return mPrimitiveFields.mScaleY; }
302 
setPivotX(float pivotX)303     bool setPivotX(float pivotX) {
304         if (RP_SET(mPrimitiveFields.mPivotX, pivotX) || !mPrimitiveFields.mPivotExplicitlySet) {
305             mPrimitiveFields.mMatrixOrPivotDirty = true;
306             mPrimitiveFields.mPivotExplicitlySet = true;
307             return true;
308         }
309         return false;
310     }
311 
312     /* Note that getPivotX and getPivotY are adjusted by updateMatrix(),
313      * so the value returned may be stale if the RenderProperties has been
314      * modified since the last call to updateMatrix()
315      */
getPivotX()316     float getPivotX() const { return mPrimitiveFields.mPivotX; }
317 
setPivotY(float pivotY)318     bool setPivotY(float pivotY) {
319         if (RP_SET(mPrimitiveFields.mPivotY, pivotY) || !mPrimitiveFields.mPivotExplicitlySet) {
320             mPrimitiveFields.mMatrixOrPivotDirty = true;
321             mPrimitiveFields.mPivotExplicitlySet = true;
322             return true;
323         }
324         return false;
325     }
326 
getPivotY()327     float getPivotY() const { return mPrimitiveFields.mPivotY; }
328 
isPivotExplicitlySet()329     bool isPivotExplicitlySet() const { return mPrimitiveFields.mPivotExplicitlySet; }
330 
resetPivot()331     bool resetPivot() {
332         return RP_SET_AND_DIRTY(mPrimitiveFields.mPivotExplicitlySet, false);
333     }
334 
setCameraDistance(float distance)335     bool setCameraDistance(float distance) {
336         if (distance != getCameraDistance()) {
337             mPrimitiveFields.mMatrixOrPivotDirty = true;
338             mComputedFields.mTransformCamera.setCameraLocation(0, 0, distance);
339             return true;
340         }
341         return false;
342     }
343 
getCameraDistance()344     float getCameraDistance() const {
345         // TODO: update getCameraLocationZ() to be const
346         return const_cast<Sk3DView*>(&mComputedFields.mTransformCamera)->getCameraLocationZ();
347     }
348 
setLeft(int left)349     bool setLeft(int left) {
350         if (RP_SET(mPrimitiveFields.mLeft, left)) {
351             mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft;
352             if (!mPrimitiveFields.mPivotExplicitlySet) {
353                 mPrimitiveFields.mMatrixOrPivotDirty = true;
354             }
355             return true;
356         }
357         return false;
358     }
359 
getLeft()360     int getLeft() const { return mPrimitiveFields.mLeft; }
361 
setTop(int top)362     bool setTop(int top) {
363         if (RP_SET(mPrimitiveFields.mTop, top)) {
364             mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop;
365             if (!mPrimitiveFields.mPivotExplicitlySet) {
366                 mPrimitiveFields.mMatrixOrPivotDirty = true;
367             }
368             return true;
369         }
370         return false;
371     }
372 
getTop()373     int getTop() const { return mPrimitiveFields.mTop; }
374 
setRight(int right)375     bool setRight(int right) {
376         if (RP_SET(mPrimitiveFields.mRight, right)) {
377             mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft;
378             if (!mPrimitiveFields.mPivotExplicitlySet) {
379                 mPrimitiveFields.mMatrixOrPivotDirty = true;
380             }
381             return true;
382         }
383         return false;
384     }
385 
getRight()386     int getRight() const { return mPrimitiveFields.mRight; }
387 
setBottom(int bottom)388     bool setBottom(int bottom) {
389         if (RP_SET(mPrimitiveFields.mBottom, bottom)) {
390             mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop;
391             if (!mPrimitiveFields.mPivotExplicitlySet) {
392                 mPrimitiveFields.mMatrixOrPivotDirty = true;
393             }
394             return true;
395         }
396         return false;
397     }
398 
getBottom()399     int getBottom() const { return mPrimitiveFields.mBottom; }
400 
setLeftTop(int left,int top)401     bool setLeftTop(int left, int top) {
402         bool leftResult = setLeft(left);
403         bool topResult = setTop(top);
404         return leftResult || topResult;
405     }
406 
setLeftTopRightBottom(int left,int top,int right,int bottom)407     bool setLeftTopRightBottom(int left, int top, int right, int bottom) {
408         if (left != mPrimitiveFields.mLeft || top != mPrimitiveFields.mTop ||
409             right != mPrimitiveFields.mRight || bottom != mPrimitiveFields.mBottom) {
410             mPrimitiveFields.mLeft = left;
411             mPrimitiveFields.mTop = top;
412             mPrimitiveFields.mRight = right;
413             mPrimitiveFields.mBottom = bottom;
414             mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft;
415             mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop;
416             if (!mPrimitiveFields.mPivotExplicitlySet) {
417                 mPrimitiveFields.mMatrixOrPivotDirty = true;
418             }
419             return true;
420         }
421         return false;
422     }
423 
offsetLeftRight(int offset)424     bool offsetLeftRight(int offset) {
425         if (offset != 0) {
426             mPrimitiveFields.mLeft += offset;
427             mPrimitiveFields.mRight += offset;
428             return true;
429         }
430         return false;
431     }
432 
offsetTopBottom(int offset)433     bool offsetTopBottom(int offset) {
434         if (offset != 0) {
435             mPrimitiveFields.mTop += offset;
436             mPrimitiveFields.mBottom += offset;
437             return true;
438         }
439         return false;
440     }
441 
getWidth()442     int getWidth() const { return mPrimitiveFields.mWidth; }
443 
getHeight()444     int getHeight() const { return mPrimitiveFields.mHeight; }
445 
getAnimationMatrix()446     const SkMatrix* getAnimationMatrix() const { return mAnimationMatrix; }
447 
hasTransformMatrix()448     bool hasTransformMatrix() const {
449         return getTransformMatrix() && !getTransformMatrix()->isIdentity();
450     }
451 
452     // May only call this if hasTransformMatrix() is true
isTransformTranslateOnly()453     bool isTransformTranslateOnly() const {
454         return getTransformMatrix()->getType() == SkMatrix::kTranslate_Mask;
455     }
456 
getTransformMatrix()457     const SkMatrix* getTransformMatrix() const {
458         LOG_ALWAYS_FATAL_IF(mPrimitiveFields.mMatrixOrPivotDirty, "Cannot get a dirty matrix!");
459         return mComputedFields.mTransformMatrix;
460     }
461 
getClippingFlags()462     int getClippingFlags() const { return mPrimitiveFields.mClippingFlags; }
463 
getClipToBounds()464     bool getClipToBounds() const { return mPrimitiveFields.mClippingFlags & CLIP_TO_BOUNDS; }
465 
getClipBounds()466     const Rect& getClipBounds() const { return mPrimitiveFields.mClipBounds; }
467 
getClippingRectForFlags(uint32_t flags,Rect * outRect)468     void getClippingRectForFlags(uint32_t flags, Rect* outRect) const {
469         if (flags & CLIP_TO_BOUNDS) {
470             outRect->set(0, 0, getWidth(), getHeight());
471             if (flags & CLIP_TO_CLIP_BOUNDS) {
472                 outRect->doIntersect(mPrimitiveFields.mClipBounds);
473             }
474         } else {
475             outRect->set(mPrimitiveFields.mClipBounds);
476         }
477     }
478 
getHasOverlappingRendering()479     bool getHasOverlappingRendering() const { return mPrimitiveFields.mHasOverlappingRendering; }
480 
getOutline()481     const Outline& getOutline() const { return mPrimitiveFields.mOutline; }
482 
getRevealClip()483     const RevealClip& getRevealClip() const { return mPrimitiveFields.mRevealClip; }
484 
getProjectBackwards()485     bool getProjectBackwards() const { return mPrimitiveFields.mProjectBackwards; }
486 
487     void debugOutputProperties(std::ostream& output, const int level) const;
488 
489     void updateMatrix();
490 
mutableOutline()491     Outline& mutableOutline() { return mPrimitiveFields.mOutline; }
492 
mutableRevealClip()493     RevealClip& mutableRevealClip() { return mPrimitiveFields.mRevealClip; }
494 
layerProperties()495     const LayerProperties& layerProperties() const { return mLayerProperties; }
496 
mutateLayerProperties()497     LayerProperties& mutateLayerProperties() { return mLayerProperties; }
498 
499     // Returns true if damage calculations should be clipped to bounds
500     // TODO: Figure out something better for getZ(), as children should still be
501     // clipped to this RP's bounds. But as we will damage -INT_MAX to INT_MAX
502     // for this RP's getZ() anyway, this can be optimized when we have a
503     // Z damage estimate instead of INT_MAX
getClipDamageToBounds()504     bool getClipDamageToBounds() const {
505         return getClipToBounds() && (getZ() <= 0 || getOutline().isEmpty());
506     }
507 
hasShadow()508     bool hasShadow() const {
509         return getZ() > 0.0f && getOutline().getPath() != nullptr &&
510                getOutline().getAlpha() != 0.0f;
511     }
512 
getSpotShadowColor()513     SkColor getSpotShadowColor() const {
514         return mPrimitiveFields.mSpotShadowColor;
515     }
516 
setSpotShadowColor(SkColor shadowColor)517     bool setSpotShadowColor(SkColor shadowColor) {
518         return RP_SET(mPrimitiveFields.mSpotShadowColor, shadowColor);
519     }
520 
getAmbientShadowColor()521     SkColor getAmbientShadowColor() const {
522         return mPrimitiveFields.mAmbientShadowColor;
523     }
524 
setAmbientShadowColor(SkColor shadowColor)525     bool setAmbientShadowColor(SkColor shadowColor) {
526         return RP_SET(mPrimitiveFields.mAmbientShadowColor, shadowColor);
527     }
528 
fitsOnLayer()529     bool fitsOnLayer() const {
530         const DeviceInfo* deviceInfo = DeviceInfo::get();
531         return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize() &&
532                mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize();
533     }
534 
promotedToLayer()535     bool promotedToLayer() const {
536         return mLayerProperties.mType == LayerType::None && fitsOnLayer() &&
537                (mComputedFields.mNeedLayerForFunctors ||
538                 (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 &&
539                  mPrimitiveFields.mHasOverlappingRendering));
540     }
541 
effectiveLayerType()542     LayerType effectiveLayerType() const {
543         return CC_UNLIKELY(promotedToLayer()) ? LayerType::RenderLayer : mLayerProperties.mType;
544     }
545 
546 private:
547     // Rendering properties
548     struct PrimitiveFields {
549         int mLeft = 0, mTop = 0, mRight = 0, mBottom = 0;
550         int mWidth = 0, mHeight = 0;
551         int mClippingFlags = CLIP_TO_BOUNDS;
552         SkColor mSpotShadowColor = SK_ColorBLACK;
553         SkColor mAmbientShadowColor = SK_ColorBLACK;
554         float mAlpha = 1;
555         float mTranslationX = 0, mTranslationY = 0, mTranslationZ = 0;
556         float mElevation = 0;
557         float mRotation = 0, mRotationX = 0, mRotationY = 0;
558         float mScaleX = 1, mScaleY = 1;
559         float mPivotX = 0, mPivotY = 0;
560         bool mHasOverlappingRendering = false;
561         bool mPivotExplicitlySet = false;
562         bool mMatrixOrPivotDirty = false;
563         bool mProjectBackwards = false;
564         bool mProjectionReceiver = false;
565         Rect mClipBounds;
566         Outline mOutline;
567         RevealClip mRevealClip;
568     } mPrimitiveFields;
569 
570     SkMatrix* mStaticMatrix;
571     SkMatrix* mAnimationMatrix;
572     LayerProperties mLayerProperties;
573 
574     /**
575      * These fields are all generated from other properties and are not set directly.
576      */
577     struct ComputedFields {
578         ComputedFields();
579         ~ComputedFields();
580 
581         /**
582          * Stores the total transformation of the DisplayList based upon its scalar
583          * translate/rotate/scale properties.
584          *
585          * In the common translation-only case, the matrix isn't necessarily allocated,
586          * and the mTranslation properties are used directly.
587          */
588         SkMatrix* mTransformMatrix;
589 
590         Sk3DView mTransformCamera;
591 
592         // Force layer on for functors to enable render features they don't yet support (clipping)
593         bool mNeedLayerForFunctors = false;
594     } mComputedFields;
595 };
596 
597 } /* namespace uirenderer */
598 } /* namespace android */
599