| /* |
| * 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. |
| */ |
| |
| #pragma once |
| |
| #ifdef __ANDROID__ // Layoutlib does not support device info |
| #include "DeviceInfo.h" |
| #endif // __ANDROID__ |
| |
| #include "Outline.h" |
| #include "Rect.h" |
| #include "RevealClip.h" |
| #include "effects/StretchEffect.h" |
| #include "utils/MathUtils.h" |
| #include "utils/PaintUtils.h" |
| |
| #include <SkBlendMode.h> |
| #include <SkImageFilter.h> |
| #include <SkCamera.h> |
| #include <SkColor.h> |
| #include <SkMatrix.h> |
| #include <SkRegion.h> |
| |
| #include <androidfw/ResourceTypes.h> |
| #include <cutils/compiler.h> |
| #include <stddef.h> |
| #include <utils/Log.h> |
| #include <algorithm> |
| #include <ostream> |
| #include <vector> |
| |
| 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, |
| // We cannot build the software layer directly (must be done at record time) and all management |
| // of software layers is handled in Java. |
| Software = 1, |
| RenderLayer = 2, |
| }; |
| |
| enum ClippingFlags { |
| CLIP_TO_BOUNDS = 0x1 << 0, |
| CLIP_TO_CLIP_BOUNDS = 0x1 << 1, |
| }; |
| |
| class 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(SkBlendMode mode) { return RP_SET(mMode, mode); } |
| |
| SkBlendMode xferMode() const { return mMode; } |
| |
| SkColorFilter* getColorFilter() const { return mColorFilter.get(); } |
| |
| bool setImageFilter(SkImageFilter* imageFilter); |
| |
| SkImageFilter* getImageFilter() const { return mImageFilter.get(); } |
| |
| const StretchEffect& getStretchEffect() const { return mStretchEffect; } |
| |
| StretchEffect& mutableStretchEffect() { return mStretchEffect; } |
| |
| // 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); |
| |
| // Strongly recommend using effectiveLayerType instead |
| LayerType type() const { return mType; } |
| |
| private: |
| LayerProperties(); |
| ~LayerProperties(); |
| void reset(); |
| bool setColorFilter(SkColorFilter* filter); |
| |
| 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; |
| SkBlendMode mMode; |
| sk_sp<SkColorFilter> mColorFilter; |
| sk_sp<SkImageFilter> mImageFilter; |
| StretchEffect mStretchEffect; |
| }; |
| |
| /* |
| * Data structure that holds the properties for a RenderNode |
| */ |
| class 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 || |
| CC_UNLIKELY(isClipMayBeComplex()) |
| |
| // Round rect clipping forces layer for functors |
| || CC_UNLIKELY(getOutline().willComplexClip()) || |
| 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 shouldReceive) { |
| return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldReceive); |
| } |
| |
| bool isProjectionReceiver() const { return mPrimitiveFields.mProjectionReceiver; } |
| |
| bool setClipMayBeComplex(bool isClipMayBeComplex) { |
| return RP_SET(mPrimitiveFields.mClipMayBeComplex, isClipMayBeComplex); |
| } |
| |
| bool isClipMayBeComplex() const { return mPrimitiveFields.mClipMayBeComplex; } |
| |
| 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 resetPivot() { return RP_SET_AND_DIRTY(mPrimitiveFields.mPivotExplicitlySet, false); } |
| |
| 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; |
| } |
| |
| int 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; |
| } |
| |
| int 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; |
| } |
| |
| int 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; |
| } |
| |
| int 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; } |
| |
| const Rect& getClipBounds() const { return mPrimitiveFields.mClipBounds; } |
| |
| 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->doIntersect(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(std::ostream& output, 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; |
| } |
| |
| SkColor getSpotShadowColor() const { return mPrimitiveFields.mSpotShadowColor; } |
| |
| bool setSpotShadowColor(SkColor shadowColor) { |
| return RP_SET(mPrimitiveFields.mSpotShadowColor, shadowColor); |
| } |
| |
| SkColor getAmbientShadowColor() const { return mPrimitiveFields.mAmbientShadowColor; } |
| |
| bool setAmbientShadowColor(SkColor shadowColor) { |
| return RP_SET(mPrimitiveFields.mAmbientShadowColor, shadowColor); |
| } |
| |
| bool fitsOnLayer() const { |
| #ifdef __ANDROID__ // Layoutlib does not support device info |
| const DeviceInfo* deviceInfo = DeviceInfo::get(); |
| return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize() && |
| mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize(); |
| #else |
| return mPrimitiveFields.mWidth <= 4096 && mPrimitiveFields.mHeight <= 4096; |
| #endif |
| } |
| |
| bool promotedToLayer() const { |
| return mLayerProperties.mType == LayerType::None && fitsOnLayer() && |
| (mComputedFields.mNeedLayerForFunctors || mLayerProperties.mImageFilter != nullptr || |
| mLayerProperties.getStretchEffect().requiresLayer() || |
| (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 && |
| mPrimitiveFields.mHasOverlappingRendering)); |
| } |
| |
| LayerType effectiveLayerType() const { |
| return CC_UNLIKELY(promotedToLayer()) ? LayerType::RenderLayer : mLayerProperties.mType; |
| } |
| |
| bool setAllowForceDark(bool allow) { |
| return RP_SET(mPrimitiveFields.mAllowForceDark, allow); |
| } |
| |
| bool getAllowForceDark() const { |
| return mPrimitiveFields.mAllowForceDark; |
| } |
| |
| private: |
| // Rendering properties |
| struct PrimitiveFields { |
| int mLeft = 0, mTop = 0, mRight = 0, mBottom = 0; |
| int mWidth = 0, mHeight = 0; |
| int mClippingFlags = CLIP_TO_BOUNDS; |
| SkColor mSpotShadowColor = SK_ColorBLACK; |
| SkColor mAmbientShadowColor = SK_ColorBLACK; |
| float mAlpha = 1; |
| float mTranslationX = 0, mTranslationY = 0, mTranslationZ = 0; |
| float mElevation = 0; |
| float mRotation = 0, mRotationX = 0, mRotationY = 0; |
| float mScaleX = 1, mScaleY = 1; |
| float mPivotX = 0, mPivotY = 0; |
| bool mHasOverlappingRendering = false; |
| bool mPivotExplicitlySet = false; |
| bool mMatrixOrPivotDirty = false; |
| bool mProjectBackwards = false; |
| bool mProjectionReceiver = false; |
| bool mAllowForceDark = true; |
| bool mClipMayBeComplex = false; |
| Rect mClipBounds; |
| Outline mOutline; |
| RevealClip mRevealClip; |
| } 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 */ |