Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9355b66d authored by Alan Viverette's avatar Alan Viverette Committed by Android Git Automerger
Browse files

am fca5f35e: Merge "Reduce number of saveLayer calls in RippleDrawable" into...

am fca5f35e: Merge "Reduce number of saveLayer calls in RippleDrawable" into lmp-mr1-dev automerge: b7243145 automerge: 0bc1eee0

* commit 'fca5f35e':
  Reduce number of saveLayer calls in RippleDrawable
parents 5c176c73 fca5f35e
Loading
Loading
Loading
Loading
+16 −10
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Xfermode;
import android.util.MathUtils;
import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator;
@@ -58,8 +59,10 @@ class Ripple {
    /** Bounds used for computing max radius. */
    private final Rect mBounds;

    /** Full-opacity color for drawing this ripple. */
    private int mColorOpaque;
    /** ARGB color for drawing this ripple. */
    private int mColor;

    private Xfermode mXfermode;

    /** Maximum ripple radius. */
    private float mOuterRadius;
@@ -120,9 +123,7 @@ class Ripple {
        mStartingY = startingY;
    }

    public void setup(int maxRadius, int color, float density) {
        mColorOpaque = color | 0xFF000000;

    public void setup(int maxRadius, float density) {
        if (maxRadius != RippleDrawable.RADIUS_AUTO) {
            mHasMaxRadius = true;
            mOuterRadius = maxRadius;
@@ -216,6 +217,10 @@ class Ripple {
     * Draws the ripple centered at (0,0) using the specified paint.
     */
    public boolean draw(Canvas c, Paint p) {
        // Store the color and xfermode, we might need them later.
        mColor = p.getColor();
        mXfermode = p.getXfermode();

        final boolean canUseHardware = c.isHardwareAccelerated();
        if (mCanUseHardware != canUseHardware && mCanUseHardware) {
            // We've switched from hardware to non-hardware mode. Panic.
@@ -261,8 +266,8 @@ class Ripple {
    private boolean drawSoftware(Canvas c, Paint p) {
        boolean hasContent = false;

        p.setColor(mColorOpaque);
        final int alpha = (int) (255 * mOpacity + 0.5f);
        final int paintAlpha = p.getAlpha();
        final int alpha = (int) (paintAlpha * mOpacity + 0.5f);
        final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
        if (alpha > 0 && radius > 0) {
            final float x = MathUtils.lerp(
@@ -270,8 +275,8 @@ class Ripple {
            final float y = MathUtils.lerp(
                    mClampedStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
            p.setAlpha(alpha);
            p.setStyle(Style.FILL);
            c.drawCircle(x, y, radius, p);
            p.setAlpha(paintAlpha);
            hasContent = true;
        }

@@ -374,8 +379,9 @@ class Ripple {
        final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
        final Paint paint = getTempPaint();
        paint.setAntiAlias(true);
        paint.setColor(mColorOpaque);
        paint.setAlpha((int) (255 * mOpacity + 0.5f));
        paint.setColor(mColor);
        paint.setXfermode(mXfermode);
        paint.setAlpha((int) (Color.alpha(mColor) * mOpacity + 0.5f));
        paint.setStyle(Style.FILL);
        mPropPaint = CanvasProperty.createPaint(paint);
        mPropRadius = CanvasProperty.createFloat(startRadius);
+21 −23
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Xfermode;
import android.util.MathUtils;
import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator;
@@ -60,11 +61,10 @@ class RippleBackground {
    /** Bounds used for computing max radius. */
    private final Rect mBounds;

    /** Full-opacity color for drawing this ripple. */
    private int mColorOpaque;
    /** ARGB color for drawing this ripple. */
    private int mColor;

    /** Maximum alpha value for drawing this ripple. */
    private int mColorAlpha;
    private Xfermode mXfermode;

    /** Maximum ripple radius. */
    private float mOuterRadius;
@@ -106,10 +106,7 @@ class RippleBackground {
        mBounds = bounds;
    }

    public void setup(int maxRadius, int color, float density) {
        mColorOpaque = color | 0xFF000000;
        mColorAlpha = Color.alpha(color) / 2;

    public void setup(int maxRadius, float density) {
        if (maxRadius != RippleDrawable.RADIUS_AUTO) {
            mHasMaxRadius = true;
            mOuterRadius = maxRadius;
@@ -124,10 +121,6 @@ class RippleBackground {
        mDensity = density;
    }

    public boolean isHardwareAnimating() {
        return mHardwareAnimating;
    }

    public void onHotspotBoundsChanged() {
        if (!mHasMaxRadius) {
            final float halfWidth = mBounds.width() / 2.0f;
@@ -151,6 +144,10 @@ class RippleBackground {
     * Draws the ripple centered at (0,0) using the specified paint.
     */
    public boolean draw(Canvas c, Paint p) {
        // Store the color and xfermode, we might need them later.
        mColor = p.getColor();
        mXfermode = p.getXfermode();

        final boolean canUseHardware = c.isHardwareAccelerated();
        if (mCanUseHardware != canUseHardware && mCanUseHardware) {
            // We've switched from hardware to non-hardware mode. Panic.
@@ -169,8 +166,7 @@ class RippleBackground {
    }

    public boolean shouldDraw() {
        final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
        return mCanUseHardware && mHardwareAnimating || outerAlpha > 0 && mOuterRadius > 0;
        return (mCanUseHardware && mHardwareAnimating) || (mOuterOpacity > 0 && mOuterRadius > 0);
    }

    private boolean drawHardware(HardwareCanvas c) {
@@ -201,12 +197,13 @@ class RippleBackground {
    private boolean drawSoftware(Canvas c, Paint p) {
        boolean hasContent = false;

        p.setColor(mColorOpaque);
        final int outerAlpha = (int) (mColorAlpha * mOuterOpacity + 0.5f);
        if (outerAlpha > 0 && mOuterRadius > 0) {
            p.setAlpha(outerAlpha);
            p.setStyle(Style.FILL);
            c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
        final int paintAlpha = p.getAlpha();
        final int alpha = (int) (paintAlpha * mOuterOpacity + 0.5f);
        final float radius = mOuterRadius;
        if (alpha > 0 && radius > 0) {
            p.setAlpha(alpha);
            c.drawCircle(mOuterX, mOuterY, radius, p);
            p.setAlpha(paintAlpha);
            hasContent = true;
        }

@@ -262,7 +259,7 @@ class RippleBackground {
        // outer(t) = mOuterOpacity + t * WAVE_OUTER_OPACITY_VELOCITY / 1000
        final int inflectionDuration = Math.max(0, (int) (1000 * (1 - mOuterOpacity)
                / (WAVE_OPACITY_DECAY_VELOCITY + outerOpacityVelocity) + 0.5f));
        final int inflectionOpacity = (int) (mColorAlpha * (mOuterOpacity
        final int inflectionOpacity = (int) (Color.alpha(mColor) * (mOuterOpacity
                + inflectionDuration * outerOpacityVelocity * outerSizeInfluence / 1000) + 0.5f);

        if (mCanUseHardware) {
@@ -277,8 +274,9 @@ class RippleBackground {

        final Paint outerPaint = getTempPaint();
        outerPaint.setAntiAlias(true);
        outerPaint.setColor(mColorOpaque);
        outerPaint.setAlpha((int) (mColorAlpha * mOuterOpacity + 0.5f));
        outerPaint.setXfermode(mXfermode);
        outerPaint.setColor(mColor);
        outerPaint.setAlpha((int) (Color.alpha(mColor) * mOuterOpacity + 0.5f));
        outerPaint.setStyle(Style.FILL);
        mPropOuterPaint = CanvasProperty.createPaint(outerPaint);
        mPropOuterRadius = CanvasProperty.createFloat(mOuterRadius);
+76 −142
Original line number Diff line number Diff line
@@ -16,6 +16,11 @@

package android.graphics.drawable;

import com.android.internal.R;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
@@ -34,11 +39,6 @@ import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;

import com.android.internal.R;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.Arrays;

@@ -156,13 +156,6 @@ public class RippleDrawable extends LayerDrawable {
    /** Whether bounds are being overridden. */
    private boolean mOverrideBounds;

    /**
     * Whether the next draw MUST draw something to canvas. Used to work around
     * a bug in hardware invalidation following a render thread-accelerated
     * animation.
     */
    private boolean mNeedsDraw;

    /**
     * Constructor used for drawable inflation.
     */
@@ -203,21 +196,15 @@ public class RippleDrawable extends LayerDrawable {
    public void jumpToCurrentState() {
        super.jumpToCurrentState();

        boolean needsDraw = false;

        if (mRipple != null) {
            needsDraw |= mRipple.isHardwareAnimating();
            mRipple.jump();
        }

        if (mBackground != null) {
            needsDraw |= mBackground.isHardwareAnimating();
            mBackground.jump();
        }

        needsDraw |= cancelExitingRipples();

        mNeedsDraw = needsDraw;
        cancelExitingRipples();
        invalidateSelf();
    }

@@ -497,8 +484,7 @@ public class RippleDrawable extends LayerDrawable {
            mBackground = new RippleBackground(this, mHotspotBounds);
        }

        final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
        mBackground.setup(mState.mMaxRadius, color, mDensity);
        mBackground.setup(mState.mMaxRadius, mDensity);
        mBackground.enter(focused);
    }

@@ -534,8 +520,7 @@ public class RippleDrawable extends LayerDrawable {
            mRipple = new Ripple(this, mHotspotBounds, x, y);
        }

        final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
        mRipple.setup(mState.mMaxRadius, color, mDensity);
        mRipple.setup(mState.mMaxRadius, mDensity);
        mRipple.enter();
    }

@@ -559,23 +544,17 @@ public class RippleDrawable extends LayerDrawable {
     * background. Nothing will be drawn after this method is called.
     */
    private void clearHotspots() {
        boolean needsDraw = false;

        if (mRipple != null) {
            needsDraw |= mRipple.isHardwareAnimating();
            mRipple.cancel();
            mRipple = null;
        }

        if (mBackground != null) {
            needsDraw |= mBackground.isHardwareAnimating();
            mBackground.cancel();
            mBackground = null;
        }

        needsDraw |= cancelExitingRipples();

        mNeedsDraw = needsDraw;
        cancelExitingRipples();
        invalidateSelf();
    }

@@ -631,56 +610,41 @@ public class RippleDrawable extends LayerDrawable {
        }
    }

    /**
     * Optimized for drawing ripples with a mask layer and optional content.
     */
    @Override
    public void draw(@NonNull Canvas canvas) {
        final boolean hasMask = mMask != null;
        final boolean drawNonMaskContent = mLayerState.mNum > (hasMask ? 1 : 0);
        final boolean drawMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
        final boolean hasRipples = mRipple != null || mExitingRipplesCount > 0
                || (mBackground != null && mBackground.shouldDraw());

        // Clip to the dirty bounds, which will be the drawable bounds if we
        // have a mask or content and the ripple bounds if we're projecting.
        final Rect bounds = getDirtyBounds();
        final int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
        canvas.clipRect(bounds);

        // If we have content, draw it into a layer first.
        final int contentLayer;
        if (drawNonMaskContent) {
            contentLayer = drawContentLayer(canvas, bounds, SRC_OVER);
        } else {
            contentLayer = -1;
        }

        // Next, try to draw the ripples (into a layer if necessary). If we need
        // to mask against the underlying content, set the xfermode to SRC_ATOP.
        final PorterDuffXfermode xfermode = (hasMask || !drawNonMaskContent) ? SRC_OVER : SRC_ATOP;

        // If we have a background and a non-opaque mask, draw the masking layer.
        final int backgroundLayer = drawBackgroundLayer(canvas, bounds, xfermode, drawMask);
        if (backgroundLayer >= 0) {
            if (drawMask) {
        // If we have content, draw it first. If we have ripples and no mask,
        // we'll draw it into a SRC_OVER layer so that we can mask ripples
        // against it using SRC_IN.
        final boolean hasContentLayer = drawContent(canvas, bounds, hasRipples, hasMask);

        // Next, try to draw the ripples. If we have a non-opaque mask, we'll
        // draw the ripples into a SRC_OVER layer, draw the mask into a DST_IN
        // layer, and blend.
        if (hasRipples) {
            final boolean hasNonOpaqueMask = hasMask && mMask.getOpacity() != PixelFormat.OPAQUE;
            final boolean hasRippleLayer = drawBackgroundAndRipples(canvas, bounds,
                    hasNonOpaqueMask, hasContentLayer);

            // If drawing ripples created a layer, we have a non-opaque mask
            // that needs to be blended on top of the ripples with DST_IN.
            if (hasRippleLayer) {
                drawMaskingLayer(canvas, bounds, DST_IN);
            }
            canvas.restoreToCount(backgroundLayer);
        }

        // If we have ripples and a non-opaque mask, draw the masking layer.
        final int rippleLayer = drawRippleLayer(canvas, bounds, xfermode);
        if (rippleLayer >= 0) {
            if (drawMask) {
                drawMaskingLayer(canvas, bounds, DST_IN);
            }
            canvas.restoreToCount(rippleLayer);
        }

        // If we failed to draw anything and we just canceled animations, at
        // least draw a color so that hardware invalidation works correctly.
        if (contentLayer < 0 && backgroundLayer < 0 && rippleLayer < 0 && mNeedsDraw) {
            canvas.drawColor(Color.TRANSPARENT);

            // Request another draw so we can avoid adding a transparent layer
            // during the next display list refresh.
            invalidateSelf();
        }
        mNeedsDraw = false;

        canvas.restoreToCount(saveCount);
    }

@@ -714,28 +678,27 @@ public class RippleDrawable extends LayerDrawable {
        return -1;
    }

    private int drawContentLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
    private boolean drawContent(Canvas canvas, Rect bounds, boolean hasRipples, boolean hasMask) {
        final ChildDrawable[] array = mLayerState.mChildren;
        final int count = mLayerState.mNum;

        // We don't need a layer if we don't expect to draw any ripples or
        // a background, we have an explicit mask, or if the non-mask content
        // is all opaque.
        boolean needsLayer = false;
        if ((mExitingRipplesCount > 0 || (mBackground != null && mBackground.shouldDraw()))
                && mMask == null) {

        if (hasRipples && !hasMask) {
            // If we only have opaque content, we don't really need a layer
            // because the ripples will be clipped to the drawable bounds.
            for (int i = 0; i < count; i++) {
                if (array[i].mId != R.id.mask
                        && array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
                if (array[i].mDrawable.getOpacity() != PixelFormat.OPAQUE) {
                    needsLayer = true;
                    break;
                }
            }
        }

        final Paint maskingPaint = getMaskingPaint(mode);
        final int restoreToCount = needsLayer ? canvas.saveLayer(bounds.left, bounds.top,
                bounds.right, bounds.bottom, maskingPaint) : -1;
        if (needsLayer) {
            canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom,
                    getMaskingPaint(SRC_OVER));
        }

        // Draw everything except the mask.
        for (int i = 0; i < count; i++) {
@@ -744,82 +707,52 @@ public class RippleDrawable extends LayerDrawable {
            }
        }

        return restoreToCount;
        return needsLayer;
    }

    private int drawBackgroundLayer(
            Canvas canvas, Rect bounds, PorterDuffXfermode mode, boolean drawMask) {
        int saveCount = -1;

        if (mBackground != null && mBackground.shouldDraw()) {
            // TODO: We can avoid saveLayer here if we push the xfermode into
            // the background's render thread animator at exit() time.
            if (drawMask || mode != SRC_OVER) {
                saveCount = canvas.saveLayer(bounds.left, bounds.top, bounds.right,
                        bounds.bottom, getMaskingPaint(mode));
    private boolean drawBackgroundAndRipples(
            Canvas canvas, Rect bounds, boolean hasNonOpaqueMask, boolean hasContentLayer) {
        if (hasNonOpaqueMask) {
            final Paint p = getMaskingPaint(SRC_OVER);
            canvas.saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, p);
        }

        final PorterDuffXfermode mode = hasContentLayer ? SRC_ATOP : SRC_OVER;
        final float x = mHotspotBounds.exactCenterX();
        final float y = mHotspotBounds.exactCenterY();
        canvas.translate(x, y);
            mBackground.draw(canvas, getRipplePaint());
            canvas.translate(-x, -y);
        }

        return saveCount;
    }
        final Paint p = getRipplePaint();
        p.setXfermode(mode);

    private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
        boolean drewRipples = false;
        int restoreToCount = -1;
        int restoreTranslate = -1;
        // Grab the color for the current state and cut the alpha channel in
        // half so that the ripple and background together yield full alpha.
        final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
        final int alpha = (Color.alpha(color) / 2) << 24;
        p.setColor(color & 0xFFFFFF | alpha);

        // Draw ripples and update the animating ripples array.
        final int count = mExitingRipplesCount;
        final Ripple[] ripples = mExitingRipples;
        for (int i = 0; i <= count; i++) {
            final Ripple ripple;
            if (i < count) {
                ripple = ripples[i];
            } else if (mRipple != null) {
                ripple = mRipple;
            } else {
                continue;
        final RippleBackground background = mBackground;
        if (background != null && background.shouldDraw()) {
            background.draw(canvas, p);
        }

            // If we're masking the ripple layer, make sure we have a layer
            // first. This will merge SRC_OVER (directly) onto the canvas.
            if (restoreToCount < 0) {
                final Paint maskingPaint = getMaskingPaint(mode);
                final int color = mState.mColor.getColorForState(getState(), Color.TRANSPARENT);
                final int alpha = Color.alpha(color);
                maskingPaint.setAlpha(alpha / 2);

                // TODO: We can avoid saveLayer here if we're only drawing one
                // ripple and we don't have content or a translucent mask.
                restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
                        bounds.right, bounds.bottom, maskingPaint);

                // Translate the canvas to the current hotspot bounds.
                restoreTranslate = canvas.save();
                canvas.translate(mHotspotBounds.exactCenterX(), mHotspotBounds.exactCenterY());
        final int count = mExitingRipplesCount;
        if (count > 0) {
            final Ripple[] ripples = mExitingRipples;
            for (int i = 0; i < count; i++) {
                ripples[i].draw(canvas, p);
            }

            drewRipples |= ripple.draw(canvas, getRipplePaint());
        }

        // Always restore the translation.
        if (restoreTranslate >= 0) {
            canvas.restoreToCount(restoreTranslate);
        final Ripple active = mRipple;
        if (active != null) {
            active.draw(canvas, p);
        }

        // If we created a layer with no content, merge it immediately.
        if (restoreToCount >= 0 && !drewRipples) {
            canvas.restoreToCount(restoreToCount);
            restoreToCount = -1;
        }
        canvas.translate(-x, -y);

        return restoreToCount;
        // Returns true if a layer was created.
        return hasNonOpaqueMask;
    }

    private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
@@ -838,6 +771,7 @@ public class RippleDrawable extends LayerDrawable {
        if (mRipplePaint == null) {
            mRipplePaint = new Paint();
            mRipplePaint.setAntiAlias(true);
            mRipplePaint.setStyle(Paint.Style.FILL);
        }
        return mRipplePaint;
    }