| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #ifndef RENDERNODEPROPERTIES_H |
| #define RENDERNODEPROPERTIES_H |
| |
| #include <algorithm> |
| #include <stddef.h> |
| #include <vector> |
| #include <cutils/compiler.h> |
| #include <androidfw/ResourceTypes.h> |
| #include <utils/Log.h> |
| |
| #include <SkCamera.h> |
| #include <SkMatrix.h> |
| #include <SkRegion.h> |
| #include <SkXfermode.h> |
| |
| #include "Caches.h" |
| #include "Rect.h" |
| #include "RevealClip.h" |
| #include "Outline.h" |
| #include "utils/MathUtils.h" |
| |
| class SkBitmap; |
| class SkColorFilter; |
| class SkPaint; |
| |
| namespace android { |
| namespace uirenderer { |
| |
| class Matrix4; |
| class RenderNode; |
| class RenderProperties; |
| |
| // The __VA_ARGS__ will be executed if a & b are not equal |
| #define RP_SET(a, b, ...) (a != b ? (a = b, ##__VA_ARGS__, true) : false) |
| #define RP_SET_AND_DIRTY(a, b) RP_SET(a, b, mPrimitiveFields.mMatrixOrPivotDirty = true) |
| |
| // Keep in sync with View.java:LAYER_TYPE_* |
| enum class LayerType { |
| None = 0, |
| // Although we cannot build the software layer directly (must be done at |
| // record time), this information is used when applying alpha. |
| Software = 1, |
| RenderLayer = 2, |
| // TODO: LayerTypeSurfaceTexture? Maybe? |
| }; |
| |
| enum ClippingFlags { |
| CLIP_TO_BOUNDS = 0x1 << 0, |
| CLIP_TO_CLIP_BOUNDS = 0x1 << 1, |
| }; |
| |
| class ANDROID_API LayerProperties { |
| public: |
| bool setType(LayerType type) { |
| if (RP_SET(mType, type)) { |
| reset(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool setOpaque(bool opaque) { |
| return RP_SET(mOpaque, opaque); |
| } |
| |
| bool opaque() const { |
| return mOpaque; |
| } |
| |
| bool setAlpha(uint8_t alpha) { |
| return RP_SET(mAlpha, alpha); |
| } |
| |
| uint8_t alpha() const { |
| return mAlpha; |
| } |
| |
| bool setXferMode(SkXfermode::Mode mode) { |
| return RP_SET(mMode, mode); |
| } |
| |
| SkXfermode::Mode xferMode() const { |
| return mMode; |
| } |
| |
| bool setColorFilter(SkColorFilter* filter); |
| |
| SkColorFilter* colorFilter() const { |
| return mColorFilter; |
| } |
| |
| // Sets alpha, xfermode, and colorfilter from an SkPaint |
| // paint may be NULL, in which case defaults will be set |
| bool setFromPaint(const SkPaint* paint); |
| |
| bool needsBlending() const { |
| return !opaque() || alpha() < 255; |
| } |
| |
| LayerProperties& operator=(const LayerProperties& other); |
| |
| private: |
| LayerProperties(); |
| ~LayerProperties(); |
| void reset(); |
| |
| // Private since external users should go through properties().effectiveLayerType() |
| LayerType type() const { |
| return mType; |
| } |
| |
| friend class RenderProperties; |
| |
| LayerType mType = LayerType::None; |
| // Whether or not that Layer's content is opaque, doesn't include alpha |
| bool mOpaque; |
| uint8_t mAlpha; |
| SkXfermode::Mode mMode; |
| SkColorFilter* mColorFilter = nullptr; |
| }; |
| |
| /* |
| * Data structure that holds the properties for a RenderNode |
| */ |
| class ANDROID_API RenderProperties { |
| public: |
| RenderProperties(); |
| virtual ~RenderProperties(); |
| |
| static bool setFlag(int flag, bool newValue, int* outFlags) { |
| if (newValue) { |
| if (!(flag & *outFlags)) { |
| *outFlags |= flag; |
| return true; |
| } |
| return false; |
| } else { |
| if (flag & *outFlags) { |
| *outFlags &= ~flag; |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Set internal layer state based on whether this layer |
| * |
| * Additionally, returns true if child RenderNodes with functors will need to use a layer |
| * to support clipping. |
| */ |
| bool prepareForFunctorPresence(bool willHaveFunctor, bool ancestorDictatesFunctorsNeedLayer) { |
| // parent may have already dictated that a descendant layer is needed |
| bool functorsNeedLayer = ancestorDictatesFunctorsNeedLayer |
| |
| // Round rect clipping forces layer for functors |
| || CC_UNLIKELY(getOutline().willRoundRectClip()) |
| || CC_UNLIKELY(getRevealClip().willClip()) |
| |
| // Complex matrices forces layer, due to stencil clipping |
| || CC_UNLIKELY(getTransformMatrix() && !getTransformMatrix()->isScaleTranslate()) |
| || CC_UNLIKELY(getAnimationMatrix() && !getAnimationMatrix()->isScaleTranslate()) |
| || CC_UNLIKELY(getStaticMatrix() && !getStaticMatrix()->isScaleTranslate()); |
| |
| mComputedFields.mNeedLayerForFunctors = (willHaveFunctor && functorsNeedLayer); |
| |
| // If on a layer, will have consumed the need for isolating functors from stencil. |
| // Thus, it's safe to reset the flag until some descendent sets it. |
| return CC_LIKELY(effectiveLayerType() == LayerType::None) && functorsNeedLayer; |
| } |
| |
| RenderProperties& operator=(const RenderProperties& other); |
| |
| bool setClipToBounds(bool clipToBounds) { |
| return setFlag(CLIP_TO_BOUNDS, clipToBounds, &mPrimitiveFields.mClippingFlags); |
| } |
| |
| bool setClipBounds(const Rect& clipBounds) { |
| bool ret = setFlag(CLIP_TO_CLIP_BOUNDS, true, &mPrimitiveFields.mClippingFlags); |
| return RP_SET(mPrimitiveFields.mClipBounds, clipBounds) || ret; |
| } |
| |
| bool setClipBoundsEmpty() { |
| return setFlag(CLIP_TO_CLIP_BOUNDS, false, &mPrimitiveFields.mClippingFlags); |
| } |
| |
| bool setProjectBackwards(bool shouldProject) { |
| return RP_SET(mPrimitiveFields.mProjectBackwards, shouldProject); |
| } |
| |
| bool setProjectionReceiver(bool shouldRecieve) { |
| return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldRecieve); |
| } |
| |
| bool isProjectionReceiver() const { |
| return mPrimitiveFields.mProjectionReceiver; |
| } |
| |
| bool setStaticMatrix(const SkMatrix* matrix) { |
| delete mStaticMatrix; |
| if (matrix) { |
| mStaticMatrix = new SkMatrix(*matrix); |
| } else { |
| mStaticMatrix = nullptr; |
| } |
| return true; |
| } |
| |
| // Can return NULL |
| const SkMatrix* getStaticMatrix() const { |
| return mStaticMatrix; |
| } |
| |
| bool setAnimationMatrix(const SkMatrix* matrix) { |
| delete mAnimationMatrix; |
| if (matrix) { |
| mAnimationMatrix = new SkMatrix(*matrix); |
| } else { |
| mAnimationMatrix = nullptr; |
| } |
| return true; |
| } |
| |
| bool setAlpha(float alpha) { |
| alpha = MathUtils::clampAlpha(alpha); |
| return RP_SET(mPrimitiveFields.mAlpha, alpha); |
| } |
| |
| float getAlpha() const { |
| return mPrimitiveFields.mAlpha; |
| } |
| |
| bool setHasOverlappingRendering(bool hasOverlappingRendering) { |
| return RP_SET(mPrimitiveFields.mHasOverlappingRendering, hasOverlappingRendering); |
| } |
| |
| bool hasOverlappingRendering() const { |
| return mPrimitiveFields.mHasOverlappingRendering; |
| } |
| |
| bool setElevation(float elevation) { |
| return RP_SET(mPrimitiveFields.mElevation, elevation); |
| // Don't dirty matrix/pivot, since they don't respect Z |
| } |
| |
| float getElevation() const { |
| return mPrimitiveFields.mElevation; |
| } |
| |
| bool setTranslationX(float translationX) { |
| return RP_SET_AND_DIRTY(mPrimitiveFields.mTranslationX, translationX); |
| } |
| |
| float getTranslationX() const { |
| return mPrimitiveFields.mTranslationX; |
| } |
| |
| bool setTranslationY(float translationY) { |
| return RP_SET_AND_DIRTY(mPrimitiveFields.mTranslationY, translationY); |
| } |
| |
| float getTranslationY() const { |
| return mPrimitiveFields.mTranslationY; |
| } |
| |
| bool setTranslationZ(float translationZ) { |
| return RP_SET(mPrimitiveFields.mTranslationZ, translationZ); |
| // mMatrixOrPivotDirty not set, since matrix doesn't respect Z |
| } |
| |
| float getTranslationZ() const { |
| return mPrimitiveFields.mTranslationZ; |
| } |
| |
| // Animation helper |
| bool setX(float value) { |
| return setTranslationX(value - getLeft()); |
| } |
| |
| // Animation helper |
| float getX() const { |
| return getLeft() + getTranslationX(); |
| } |
| |
| // Animation helper |
| bool setY(float value) { |
| return setTranslationY(value - getTop()); |
| } |
| |
| // Animation helper |
| float getY() const { |
| return getTop() + getTranslationY(); |
| } |
| |
| // Animation helper |
| bool setZ(float value) { |
| return setTranslationZ(value - getElevation()); |
| } |
| |
| float getZ() const { |
| return getElevation() + getTranslationZ(); |
| } |
| |
| bool setRotation(float rotation) { |
| return RP_SET_AND_DIRTY(mPrimitiveFields.mRotation, rotation); |
| } |
| |
| float getRotation() const { |
| return mPrimitiveFields.mRotation; |
| } |
| |
| bool setRotationX(float rotationX) { |
| return RP_SET_AND_DIRTY(mPrimitiveFields.mRotationX, rotationX); |
| } |
| |
| float getRotationX() const { |
| return mPrimitiveFields.mRotationX; |
| } |
| |
| bool setRotationY(float rotationY) { |
| return RP_SET_AND_DIRTY(mPrimitiveFields.mRotationY, rotationY); |
| } |
| |
| float getRotationY() const { |
| return mPrimitiveFields.mRotationY; |
| } |
| |
| bool setScaleX(float scaleX) { |
| return RP_SET_AND_DIRTY(mPrimitiveFields.mScaleX, scaleX); |
| } |
| |
| float getScaleX() const { |
| return mPrimitiveFields.mScaleX; |
| } |
| |
| bool setScaleY(float scaleY) { |
| return RP_SET_AND_DIRTY(mPrimitiveFields.mScaleY, scaleY); |
| } |
| |
| float getScaleY() const { |
| return mPrimitiveFields.mScaleY; |
| } |
| |
| bool setPivotX(float pivotX) { |
| if (RP_SET(mPrimitiveFields.mPivotX, pivotX) |
| || !mPrimitiveFields.mPivotExplicitlySet) { |
| mPrimitiveFields.mMatrixOrPivotDirty = true; |
| mPrimitiveFields.mPivotExplicitlySet = true; |
| return true; |
| } |
| return false; |
| } |
| |
| /* Note that getPivotX and getPivotY are adjusted by updateMatrix(), |
| * so the value returned may be stale if the RenderProperties has been |
| * modified since the last call to updateMatrix() |
| */ |
| float getPivotX() const { |
| return mPrimitiveFields.mPivotX; |
| } |
| |
| bool setPivotY(float pivotY) { |
| if (RP_SET(mPrimitiveFields.mPivotY, pivotY) |
| || !mPrimitiveFields.mPivotExplicitlySet) { |
| mPrimitiveFields.mMatrixOrPivotDirty = true; |
| mPrimitiveFields.mPivotExplicitlySet = true; |
| return true; |
| } |
| return false; |
| } |
| |
| float getPivotY() const { |
| return mPrimitiveFields.mPivotY; |
| } |
| |
| bool isPivotExplicitlySet() const { |
| return mPrimitiveFields.mPivotExplicitlySet; |
| } |
| |
| bool setCameraDistance(float distance) { |
| if (distance != getCameraDistance()) { |
| mPrimitiveFields.mMatrixOrPivotDirty = true; |
| mComputedFields.mTransformCamera.setCameraLocation(0, 0, distance); |
| return true; |
| } |
| return false; |
| } |
| |
| float getCameraDistance() const { |
| // TODO: update getCameraLocationZ() to be const |
| return const_cast<Sk3DView*>(&mComputedFields.mTransformCamera)->getCameraLocationZ(); |
| } |
| |
| bool setLeft(int left) { |
| if (RP_SET(mPrimitiveFields.mLeft, left)) { |
| mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft; |
| if (!mPrimitiveFields.mPivotExplicitlySet) { |
| mPrimitiveFields.mMatrixOrPivotDirty = true; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| float getLeft() const { |
| return mPrimitiveFields.mLeft; |
| } |
| |
| bool setTop(int top) { |
| if (RP_SET(mPrimitiveFields.mTop, top)) { |
| mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop; |
| if (!mPrimitiveFields.mPivotExplicitlySet) { |
| mPrimitiveFields.mMatrixOrPivotDirty = true; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| float getTop() const { |
| return mPrimitiveFields.mTop; |
| } |
| |
| bool setRight(int right) { |
| if (RP_SET(mPrimitiveFields.mRight, right)) { |
| mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft; |
| if (!mPrimitiveFields.mPivotExplicitlySet) { |
| mPrimitiveFields.mMatrixOrPivotDirty = true; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| float getRight() const { |
| return mPrimitiveFields.mRight; |
| } |
| |
| bool setBottom(int bottom) { |
| if (RP_SET(mPrimitiveFields.mBottom, bottom)) { |
| mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop; |
| if (!mPrimitiveFields.mPivotExplicitlySet) { |
| mPrimitiveFields.mMatrixOrPivotDirty = true; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| float getBottom() const { |
| return mPrimitiveFields.mBottom; |
| } |
| |
| bool setLeftTop(int left, int top) { |
| bool leftResult = setLeft(left); |
| bool topResult = setTop(top); |
| return leftResult || topResult; |
| } |
| |
| bool setLeftTopRightBottom(int left, int top, int right, int bottom) { |
| if (left != mPrimitiveFields.mLeft || top != mPrimitiveFields.mTop |
| || right != mPrimitiveFields.mRight || bottom != mPrimitiveFields.mBottom) { |
| mPrimitiveFields.mLeft = left; |
| mPrimitiveFields.mTop = top; |
| mPrimitiveFields.mRight = right; |
| mPrimitiveFields.mBottom = bottom; |
| mPrimitiveFields.mWidth = mPrimitiveFields.mRight - mPrimitiveFields.mLeft; |
| mPrimitiveFields.mHeight = mPrimitiveFields.mBottom - mPrimitiveFields.mTop; |
| if (!mPrimitiveFields.mPivotExplicitlySet) { |
| mPrimitiveFields.mMatrixOrPivotDirty = true; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| bool offsetLeftRight(int offset) { |
| if (offset != 0) { |
| mPrimitiveFields.mLeft += offset; |
| mPrimitiveFields.mRight += offset; |
| return true; |
| } |
| return false; |
| } |
| |
| bool offsetTopBottom(int offset) { |
| if (offset != 0) { |
| mPrimitiveFields.mTop += offset; |
| mPrimitiveFields.mBottom += offset; |
| return true; |
| } |
| return false; |
| } |
| |
| int getWidth() const { |
| return mPrimitiveFields.mWidth; |
| } |
| |
| int getHeight() const { |
| return mPrimitiveFields.mHeight; |
| } |
| |
| const SkMatrix* getAnimationMatrix() const { |
| return mAnimationMatrix; |
| } |
| |
| bool hasTransformMatrix() const { |
| return getTransformMatrix() && !getTransformMatrix()->isIdentity(); |
| } |
| |
| // May only call this if hasTransformMatrix() is true |
| bool isTransformTranslateOnly() const { |
| return getTransformMatrix()->getType() == SkMatrix::kTranslate_Mask; |
| } |
| |
| const SkMatrix* getTransformMatrix() const { |
| LOG_ALWAYS_FATAL_IF(mPrimitiveFields.mMatrixOrPivotDirty, "Cannot get a dirty matrix!"); |
| return mComputedFields.mTransformMatrix; |
| } |
| |
| int getClippingFlags() const { |
| return mPrimitiveFields.mClippingFlags; |
| } |
| |
| bool getClipToBounds() const { |
| return mPrimitiveFields.mClippingFlags & CLIP_TO_BOUNDS; |
| } |
| |
| void getClippingRectForFlags(uint32_t flags, Rect* outRect) const { |
| if (flags & CLIP_TO_BOUNDS) { |
| outRect->set(0, 0, getWidth(), getHeight()); |
| if (flags & CLIP_TO_CLIP_BOUNDS) { |
| outRect->intersect(mPrimitiveFields.mClipBounds); |
| } |
| } else { |
| outRect->set(mPrimitiveFields.mClipBounds); |
| } |
| } |
| |
| bool getHasOverlappingRendering() const { |
| return mPrimitiveFields.mHasOverlappingRendering; |
| } |
| |
| const Outline& getOutline() const { |
| return mPrimitiveFields.mOutline; |
| } |
| |
| const RevealClip& getRevealClip() const { |
| return mPrimitiveFields.mRevealClip; |
| } |
| |
| bool getProjectBackwards() const { |
| return mPrimitiveFields.mProjectBackwards; |
| } |
| |
| void debugOutputProperties(const int level) const; |
| |
| void updateMatrix(); |
| |
| Outline& mutableOutline() { |
| return mPrimitiveFields.mOutline; |
| } |
| |
| RevealClip& mutableRevealClip() { |
| return mPrimitiveFields.mRevealClip; |
| } |
| |
| const LayerProperties& layerProperties() const { |
| return mLayerProperties; |
| } |
| |
| LayerProperties& mutateLayerProperties() { |
| return mLayerProperties; |
| } |
| |
| // Returns true if damage calculations should be clipped to bounds |
| // TODO: Figure out something better for getZ(), as children should still be |
| // clipped to this RP's bounds. But as we will damage -INT_MAX to INT_MAX |
| // for this RP's getZ() anyway, this can be optimized when we have a |
| // Z damage estimate instead of INT_MAX |
| bool getClipDamageToBounds() const { |
| return getClipToBounds() && (getZ() <= 0 || getOutline().isEmpty()); |
| } |
| |
| bool hasShadow() const { |
| return getZ() > 0.0f |
| && getOutline().getPath() != nullptr |
| && getOutline().getAlpha() != 0.0f; |
| } |
| |
| bool promotedToLayer() const { |
| const int maxTextureSize = Caches::getInstance().maxTextureSize; |
| return mLayerProperties.mType == LayerType::None |
| && mPrimitiveFields.mWidth <= maxTextureSize |
| && mPrimitiveFields.mHeight <= maxTextureSize |
| && (mComputedFields.mNeedLayerForFunctors |
| || (!MathUtils::isZero(mPrimitiveFields.mAlpha) |
| && mPrimitiveFields.mAlpha < 1 |
| && mPrimitiveFields.mHasOverlappingRendering)); |
| } |
| |
| LayerType effectiveLayerType() const { |
| return CC_UNLIKELY(promotedToLayer()) ? LayerType::RenderLayer : mLayerProperties.mType; |
| } |
| |
| private: |
| // Rendering properties |
| struct PrimitiveFields { |
| PrimitiveFields(); |
| |
| Outline mOutline; |
| RevealClip mRevealClip; |
| int mClippingFlags; |
| bool mProjectBackwards; |
| bool mProjectionReceiver; |
| float mAlpha; |
| bool mHasOverlappingRendering; |
| float mElevation; |
| float mTranslationX, mTranslationY, mTranslationZ; |
| float mRotation, mRotationX, mRotationY; |
| float mScaleX, mScaleY; |
| float mPivotX, mPivotY; |
| int mLeft, mTop, mRight, mBottom; |
| int mWidth, mHeight; |
| bool mPivotExplicitlySet; |
| bool mMatrixOrPivotDirty; |
| Rect mClipBounds; |
| } mPrimitiveFields; |
| |
| SkMatrix* mStaticMatrix; |
| SkMatrix* mAnimationMatrix; |
| LayerProperties mLayerProperties; |
| |
| /** |
| * These fields are all generated from other properties and are not set directly. |
| */ |
| struct ComputedFields { |
| ComputedFields(); |
| ~ComputedFields(); |
| |
| /** |
| * Stores the total transformation of the DisplayList based upon its scalar |
| * translate/rotate/scale properties. |
| * |
| * In the common translation-only case, the matrix isn't necessarily allocated, |
| * and the mTranslation properties are used directly. |
| */ |
| SkMatrix* mTransformMatrix; |
| |
| Sk3DView mTransformCamera; |
| |
| // Force layer on for functors to enable render features they don't yet support (clipping) |
| bool mNeedLayerForFunctors = false; |
| } mComputedFields; |
| }; |
| |
| } /* namespace uirenderer */ |
| } /* namespace android */ |
| |
| #endif /* RENDERNODEPROPERTIES_H */ |