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

Commit d129f1e0 authored by Galia Peycheva's avatar Galia Peycheva
Browse files

Fix synchronisation in BackgroundBlurDrawable

The PositionUpdateListener in BackgroundBlurDrawable is called from
a thread pool to update the position of the BlurRegion. After that,
in frame drawing callback (RenderThread), the Aggregator takes all
the settings in BackgroundBlurDrawable and dispatches them to SF.
However, while the RenderThread is working on frame N, the Ui thread
is working on frame N+1. So when the frame drawing callback is
called on RenderThread for frame N, BackgroundBlurDrawable
might already be holding settings for frame N+1.

In this CL, we fix that by making BackgroundBlurDrawable work only on Ui
thread, while BlurRegion lives only on RenderThread. Just before
drawing, we copy the state of BackgroundBlurDrawable into a BlurRegion.
So when render thread starts doing its thing for frame N, Ui thread can
start setting up BackgroundBlurDrawable for frame N+1. The settings for
frame N are already copied over to BlurRegion. In frame drawing
callback, we take the blur regions if there is a pending update and send
them to SF.

Bug: 167166562
Test: m && atest BlurAggregatorTest
Change-Id: Icec0fa8e97de30b7bad15b97f848ae642076652f
parent 09f52aa4
Loading
Loading
Loading
Loading
+0 −60
Original line number Diff line number Diff line
@@ -3504,64 +3504,4 @@ public final class SurfaceControl implements Parcelable {
    public static Transaction getGlobalTransaction() {
        return sGlobalTransaction;
    }

    /**
     * Wrapper for sending blur data to SurfaceFlinger.
     * @hide
     */
    public static final class BlurRegion {
        public int blurRadius;
        public float cornerRadiusTL;
        public float cornerRadiusTR;
        public float cornerRadiusBL;
        public float cornerRadiusBR;
        public float alpha = 1;
        public boolean visible = true;
        public final Rect rect = new Rect();

        private final float[] mFloatArray = new float[10];

        public BlurRegion() {
        }

        public BlurRegion(BlurRegion other) {
            rect.set(other.rect);
            blurRadius = other.blurRadius;
            alpha = other.alpha;
            cornerRadiusTL = other.cornerRadiusTL;
            cornerRadiusTR = other.cornerRadiusTR;
            cornerRadiusBL = other.cornerRadiusBL;
            cornerRadiusBR = other.cornerRadiusBR;
        }

        /**
         * Serializes this class into a float array that's more JNI friendly.
         */
        public float[] toFloatArray() {
            mFloatArray[0] = blurRadius;
            mFloatArray[1] = alpha;
            mFloatArray[2] = rect.left;
            mFloatArray[3] = rect.top;
            mFloatArray[4] = rect.right;
            mFloatArray[5] = rect.bottom;
            mFloatArray[6] = cornerRadiusTL;
            mFloatArray[7] = cornerRadiusTR;
            mFloatArray[8] = cornerRadiusBL;
            mFloatArray[9] = cornerRadiusBR;
            return mFloatArray;
        }

        @Override
        public String toString() {
            return "BlurRegion{"
                    + "blurRadius=" + blurRadius
                    + ", corners={" + cornerRadiusTL
                    + "," + cornerRadiusTR
                    + "," + cornerRadiusBL
                    + "," + cornerRadiusBR
                    + "}, alpha=" + alpha
                    + ", rect=" + rect
                    + "}";
        }
    }
}
+13 −8
Original line number Diff line number Diff line
@@ -3962,11 +3962,12 @@ public final class ViewRootImpl implements ViewParent,
    }

    private void addFrameCallbackIfNeeded() {
        boolean nextDrawUseBlastSync = mNextDrawUseBlastSync;
        boolean hasBlur = mBlurRegionAggregator.hasRegions();
        boolean reportNextDraw = mReportNextDraw;
        final boolean nextDrawUseBlastSync = mNextDrawUseBlastSync;
        final boolean reportNextDraw = mReportNextDraw;
        final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
        final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();

        if (!nextDrawUseBlastSync && !reportNextDraw && !hasBlur) {
        if (!nextDrawUseBlastSync && !reportNextDraw && !needsCallbackForBlur) {
            return;
        }

@@ -3974,18 +3975,22 @@ public final class ViewRootImpl implements ViewParent,
            Log.d(mTag, "Creating frameDrawingCallback"
                    + " nextDrawUseBlastSync=" + nextDrawUseBlastSync
                    + " reportNextDraw=" + reportNextDraw
                    + " hasBlur=" + hasBlur);
                    + " hasBlurUpdates=" + hasBlurUpdates);
        }

        // The callback will run on a worker thread pool from the render thread.
        final BackgroundBlurDrawable.BlurRegion[] blurRegionsForFrame =
                needsCallbackForBlur ?  mBlurRegionAggregator.getBlurRegionsCopyForRT() : null;

        // The callback will run on the render thread.
        HardwareRenderer.FrameDrawingCallback frameDrawingCallback = frame -> {
            if (DEBUG_BLAST) {
                Log.d(mTag, "Received frameDrawingCallback frameNum=" + frame + "."
                        + " Creating transactionCompleteCallback=" + nextDrawUseBlastSync);
            }

            if (hasBlur) {
                mBlurRegionAggregator.dispatchBlurTransactionIfNeeded(frame);
            if (needsCallbackForBlur) {
                mBlurRegionAggregator
                    .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates);
            }

            if (mBlastBufferQueue == null) {
+242 −69
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.internal.graphics.drawable;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UiThread;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -31,12 +32,14 @@ import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.view.SurfaceControl;
import android.util.LongSparseArray;
import android.view.ViewRootImpl;

import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

/**
 * A drawable that keeps track of a blur region, pokes a hole under it, and propagates its state
@@ -52,26 +55,40 @@ public final class BackgroundBlurDrawable extends Drawable {
    private final Paint mPaint = new Paint();
    private final Path mRectPath = new Path();
    private final float[] mTmpRadii = new float[8];
    private final SurfaceControl.BlurRegion mBlurRegion = new SurfaceControl.BlurRegion();

    // This will be called from a thread pool.
    private final RenderNode.PositionUpdateListener mPositionUpdateListener =
    private boolean mVisible = true;

    // Confined to UiThread. The values are copied into a BlurRegion, which lives on
    // RenderThread to avoid interference with UiThread updates.
    private int mBlurRadius;
    private float mCornerRadiusTL;
    private float mCornerRadiusTR;
    private float mCornerRadiusBL;
    private float mCornerRadiusBR;
    private float mAlpha = 1;

    // Do not update from UiThread. This holds the latest position for this drawable. It is used
    // by the Aggregator from RenderThread to get the final position of the blur region sent to SF
    private final Rect mRect = new Rect();
    // This is called from a thread pool. The callbacks might come out of order w.r.t. the frame
    // number, so we send a Runnable holding the actual update to the Aggregator. The Aggregator
    // can apply the update on RenderThread when processing that same frame.
    @VisibleForTesting
    public final RenderNode.PositionUpdateListener mPositionUpdateListener =
            new RenderNode.PositionUpdateListener() {
            @Override
            public void positionChanged(long frameNumber, int left, int top, int right,
                    int bottom) {
                synchronized (mAggregator) {
                    mBlurRegion.rect.set(left, top, right, bottom);
                    mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
                }
                mAggregator.onRenderNodePositionChanged(frameNumber, () -> {
                    mRect.set(left, top, right, bottom);
                });
            }

            @Override
            public void positionLost(long frameNumber) {
                synchronized (mAggregator) {
                    mBlurRegion.rect.setEmpty();
                    mAggregator.onBlurRegionUpdated(BackgroundBlurDrawable.this, mBlurRegion);
                }
                mAggregator.onRenderNodePositionChanged(frameNumber, () -> {
                    mRect.setEmpty();
                });
            }
        };

@@ -79,6 +96,7 @@ public final class BackgroundBlurDrawable extends Drawable {
        mAggregator = aggregator;
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
        mPaint.setColor(Color.TRANSPARENT);
        mPaint.setAntiAlias(true);
        mRenderNode = new RenderNode("BackgroundBlurDrawable");
        mRenderNode.addPositionUpdateListener(mPositionUpdateListener);
    }
@@ -104,23 +122,30 @@ public final class BackgroundBlurDrawable extends Drawable {
    public boolean setVisible(boolean visible, boolean restart) {
        boolean changed = super.setVisible(visible, restart);
        if (changed) {
            mBlurRegion.visible = visible;
            mVisible = visible;
            mAggregator.onBlurDrawableUpdated(this);
        }
        return changed;
    }

    @Override
    public void setAlpha(int alpha) {
        mBlurRegion.alpha = alpha / 255f;
        if (mAlpha != alpha / 255f) {
            mAlpha = alpha / 255f;
            invalidateSelf();
            mAggregator.onBlurDrawableUpdated(this);
        }
    }

    /**
     * Blur radius in pixels.
     */
    public void setBlurRadius(int blurRadius) {
        mBlurRegion.blurRadius = blurRadius;
        if (mBlurRadius != blurRadius) {
            mBlurRadius = blurRadius;
            invalidateSelf();
            mAggregator.onBlurDrawableUpdated(this);
        }
    }

    /**
@@ -139,14 +164,18 @@ public final class BackgroundBlurDrawable extends Drawable {
     */
    public void setCornerRadius(float cornerRadiusTL, float cornerRadiusTR, float cornerRadiusBL,
            float cornerRadiusBR) {
        synchronized (mAggregator) {
            mBlurRegion.cornerRadiusTL = cornerRadiusTL;
            mBlurRegion.cornerRadiusTR = cornerRadiusTR;
            mBlurRegion.cornerRadiusBL = cornerRadiusBL;
            mBlurRegion.cornerRadiusBR = cornerRadiusBR;
        }
        if (mCornerRadiusTL != cornerRadiusTL
                || mCornerRadiusTR != cornerRadiusTR
                || mCornerRadiusBL != cornerRadiusBL
                || mCornerRadiusBR != cornerRadiusBR) {
            mCornerRadiusTL = cornerRadiusTL;
            mCornerRadiusTR = cornerRadiusTR;
            mCornerRadiusBL = cornerRadiusBL;
            mCornerRadiusBR = cornerRadiusBR;
            updatePath();
            invalidateSelf();
            mAggregator.onBlurDrawableUpdated(this);
        }
    }

    @Override
@@ -157,12 +186,10 @@ public final class BackgroundBlurDrawable extends Drawable {
    }

    private void updatePath() {
        synchronized (mAggregator) {
            mTmpRadii[0] = mTmpRadii[1] = mBlurRegion.cornerRadiusTL;
            mTmpRadii[2] = mTmpRadii[3] = mBlurRegion.cornerRadiusTR;
            mTmpRadii[4] = mTmpRadii[5] = mBlurRegion.cornerRadiusBL;
            mTmpRadii[6] = mTmpRadii[7] = mBlurRegion.cornerRadiusBR;
        }
        mTmpRadii[0] = mTmpRadii[1] = mCornerRadiusTL;
        mTmpRadii[2] = mTmpRadii[3] = mCornerRadiusTR;
        mTmpRadii[4] = mTmpRadii[5] = mCornerRadiusBL;
        mTmpRadii[6] = mTmpRadii[7] = mCornerRadiusBR;
        mRectPath.reset();
        if (getAlpha() == 0 || !isVisible()) {
            return;
@@ -182,17 +209,32 @@ public final class BackgroundBlurDrawable extends Drawable {
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public String toString() {
        return "BackgroundBlurDrawable{"
            + "blurRadius=" + mBlurRadius
            + ", corners={" + mCornerRadiusTL
            + "," + mCornerRadiusTR
            + "," + mCornerRadiusBL
            + "," + mCornerRadiusBR
            + "}, alpha=" + mAlpha
            + ", visible=" + mVisible
            + "}";
    }

    /**
     * Responsible for keeping track of all blur regions of a {@link ViewRootImpl} and posting a
     * message when it's time to propagate them.
     */
    public static final class Aggregator {

        private final ArrayMap<BackgroundBlurDrawable, SurfaceControl.BlurRegion> mBlurRegions =
                new ArrayMap<>();
        private final Object mRtLock = new Object();
        // Maintains a list of all *visible* blur drawables. Confined to  UI thread
        private final ArraySet<BackgroundBlurDrawable> mDrawables = new ArraySet();
        @GuardedBy("mRtLock")
        private final LongSparseArray<ArraySet<Runnable>> mFrameRtUpdates = new LongSparseArray();
        private final ViewRootImpl mViewRoot;
        private float[][] mTmpBlurRegionsArray;
        private boolean mNeedsUpdate;
        private BlurRegion[] mTmpBlurRegionsForFrame = new BlurRegion[0];
        private boolean mHasUiUpdates;

        public Aggregator(ViewRootImpl viewRoot) {
            mViewRoot = viewRoot;
@@ -209,60 +251,191 @@ public final class BackgroundBlurDrawable extends Drawable {
        }

        /**
         * Called from RenderThread only, already locked.
         * @param drawable
         * @param blurRegion
         * Called when a BackgroundBlurDrawable has been updated
         */
        void onBlurRegionUpdated(BackgroundBlurDrawable drawable,
                SurfaceControl.BlurRegion blurRegion) {
            if (blurRegion.rect.isEmpty() || blurRegion.alpha == 0 || blurRegion.blurRadius == 0
                    || !blurRegion.visible) {
                mBlurRegions.remove(drawable);
                mNeedsUpdate = true;
        @UiThread
        void onBlurDrawableUpdated(BackgroundBlurDrawable drawable) {
            final boolean shouldBeDrawn =
                    drawable.mAlpha != 0 && drawable.mBlurRadius > 0 && drawable.mVisible;
            final boolean isDrawn = mDrawables.contains(drawable);
            if (shouldBeDrawn) {
                mHasUiUpdates = true;
                if (!isDrawn) {
                    mDrawables.add(drawable);
                    if (DEBUG) {
                    Log.d(TAG, "Remove " + blurRegion);
                        Log.d(TAG, "Add " + drawable);
                    }
                } else {
                mBlurRegions.put(drawable, blurRegion);
                mNeedsUpdate = true;
                    if (DEBUG) {
                    Log.d(TAG, "Update " + blurRegion);
                        Log.d(TAG, "Update " + drawable);
                    }
                }
            } else if (!shouldBeDrawn && isDrawn) {
                mHasUiUpdates = true;
                mDrawables.remove(drawable);
                if (DEBUG) {
                    Log.d(TAG, "Remove " + drawable);
                }
            }
        }

        // Called from a thread pool
        void onRenderNodePositionChanged(long frameNumber, Runnable update) {
            // One of the blur region's position has changed, so we have to send an updated list
            // of blur regions to SurfaceFlinger for this frame.
            synchronized (mRtLock) {
                ArraySet<Runnable> frameRtUpdates = mFrameRtUpdates.get(frameNumber);
                if (frameRtUpdates == null) {
                    frameRtUpdates = new ArraySet<>();
                    mFrameRtUpdates.put(frameNumber, frameRtUpdates);
                }
                frameRtUpdates.add(update);
            }
        }

        /**
         * @return true if there are any updates that need to be sent to SF
         */
        @UiThread
        public boolean hasUpdates() {
            return mHasUiUpdates;
        }

        /**
         * If there are any blur regions visible on the screen at the moment.
         * @return true if there are any visible blur regions
         */
        @UiThread
        public boolean hasRegions() {
            return mBlurRegions.size() > 0;
            return mDrawables.size() > 0;
        }

        /**
         * Dispatch blur updates, if there were any.
         * @param frameNumber Frame where the update should happen.
         * @return an array of BlurRegions, which are holding a copy of the information in
         *         all the currently visible BackgroundBlurDrawables
         */
        public void dispatchBlurTransactionIfNeeded(long frameNumber) {
            synchronized (this) {
                if (!mNeedsUpdate) {
                    return;
        @UiThread
        public BlurRegion[] getBlurRegionsCopyForRT() {
            if (mHasUiUpdates) {
                mTmpBlurRegionsForFrame = new BlurRegion[mDrawables.size()];
                for (int i = 0; i < mDrawables.size(); i++) {
                    mTmpBlurRegionsForFrame[i] = new BlurRegion(mDrawables.valueAt(i));
                }
                mHasUiUpdates = false;
            }

            return mTmpBlurRegionsForFrame;
        }
                mNeedsUpdate = false;

                if (mTmpBlurRegionsArray == null
                        || mTmpBlurRegionsArray.length != mBlurRegions.size()) {
                    mTmpBlurRegionsArray = new float[mBlurRegions.size()][];
        /**
         * Called on RenderThread.
         *
         * @return all blur regions if there are any ui or position updates for this frame,
         *         null otherwise
         */
        @VisibleForTesting
        public float[][] getBlurRegionsToDispatchToSf(long frameNumber,
                BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
            synchronized (mRtLock) {
                if (!hasUiUpdatesForFrame && (mFrameRtUpdates.size() == 0
                            || mFrameRtUpdates.keyAt(0) > frameNumber)) {
                    return null;
                }

                // mFrameRtUpdates holds position updates coming from a thread pool span from
                // RenderThread. At this point, all position updates for frame frameNumber should
                // have been added to mFrameRtUpdates.
                // Here, we apply all updates for frames <= frameNumber in case some previous update
                // has been missed. This also protects mFrameRtUpdates from memory leaks.
                while (mFrameRtUpdates.size() != 0 && mFrameRtUpdates.keyAt(0) <= frameNumber) {
                    final ArraySet<Runnable> frameUpdates = mFrameRtUpdates.valueAt(0);
                    mFrameRtUpdates.removeAt(0);
                    for (int i = 0; i < frameUpdates.size(); i++) {
                        frameUpdates.valueAt(i).run();
                    }
                }
            }

            if (DEBUG) {
                Log.d(TAG, "Dispatching " + blurRegionsForFrame.length + " blur regions:");
            }

            final float[][] blurRegionsArray = new float[blurRegionsForFrame.length][];
            for (int i = 0; i < blurRegionsArray.length; i++) {
                blurRegionsArray[i] = blurRegionsForFrame[i].toFloatArray();
                if (DEBUG) {
                    Log.d(TAG, "onBlurRegionUpdated will dispatch " + mTmpBlurRegionsArray.length
                            + " regions for frame " + frameNumber);
                    Log.d(TAG, blurRegionsForFrame[i].toString());
                }
            }
            return blurRegionsArray;
        }

        /**
         * Called on RenderThread in FrameDrawingCallback.
         * Dispatch all blur regions if there are any ui or position updates.
         */
        public void dispatchBlurTransactionIfNeeded(long frameNumber,
                BlurRegion[] blurRegionsForFrame, boolean hasUiUpdatesForFrame) {
            final float[][] blurRegionsArray = getBlurRegionsToDispatchToSf(frameNumber,
                    blurRegionsForFrame, hasUiUpdatesForFrame);
            if (blurRegionsArray != null) {
                mViewRoot.dispatchBlurRegions(blurRegionsArray, frameNumber);
            }
        }
                for (int i = 0; i < mTmpBlurRegionsArray.length; i++) {
                    mTmpBlurRegionsArray[i] = mBlurRegions.valueAt(i).toFloatArray();

    }

    /**
     * Wrapper for sending blur data to SurfaceFlinger
     * Confined to RenderThread.
     */
    public static final class BlurRegion {
        public final int blurRadius;
        public final float cornerRadiusTL;
        public final float cornerRadiusTR;
        public final float cornerRadiusBL;
        public final float cornerRadiusBR;
        public final float alpha;
        public final Rect rect;

        BlurRegion(BackgroundBlurDrawable drawable) {
            alpha = drawable.mAlpha;
            blurRadius = drawable.mBlurRadius;
            cornerRadiusTL = drawable.mCornerRadiusTL;
            cornerRadiusTR = drawable.mCornerRadiusTR;
            cornerRadiusBL = drawable.mCornerRadiusBL;
            cornerRadiusBR = drawable.mCornerRadiusBR;
            rect = drawable.mRect;
        }

                mViewRoot.dispatchBlurRegions(mTmpBlurRegionsArray, frameNumber);
        /**
         * Serializes this class into a float array that's more JNI friendly.
         */
        float[] toFloatArray() {
            final float[] floatArray = new float[10];
            floatArray[0] = blurRadius;
            floatArray[1] = alpha;
            floatArray[2] = rect.left;
            floatArray[3] = rect.top;
            floatArray[4] = rect.right;
            floatArray[5] = rect.bottom;
            floatArray[6] = cornerRadiusTL;
            floatArray[7] = cornerRadiusTR;
            floatArray[8] = cornerRadiusBL;
            floatArray[9] = cornerRadiusBR;
            return floatArray;
        }

        @Override
        public String toString() {
            return "BlurRegion{"
                    + "blurRadius=" + blurRadius
                    + ", corners={" + cornerRadiusTL
                    + "," + cornerRadiusTR
                    + "," + cornerRadiusBL
                    + "," + cornerRadiusBR
                    + "}, alpha=" + alpha
                    + ", rect=" + rect
                    + "}";
        }
    }
}
+318 −0

File added.

Preview size limit exceeded, changes collapsed.