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