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