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
This commit is contained in:
Galia Peycheva
2021-02-06 13:00:14 +01:00
parent 09f52aa440
commit d129f1e00e
4 changed files with 578 additions and 142 deletions

View File

@@ -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;
invalidateSelf();
if (mAlpha != alpha / 255f) {
mAlpha = alpha / 255f;
invalidateSelf();
mAggregator.onBlurDrawableUpdated(this);
}
}
/**
* Blur radius in pixels.
*/
public void setBlurRadius(int blurRadius) {
mBlurRegion.blurRadius = blurRadius;
invalidateSelf();
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);
}
updatePath();
invalidateSelf();
}
@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;
if (DEBUG) {
Log.d(TAG, "Remove " + blurRegion);
@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, "Add " + drawable);
}
} else {
if (DEBUG) {
Log.d(TAG, "Update " + drawable);
}
}
} else {
mBlurRegions.put(drawable, blurRegion);
mNeedsUpdate = true;
} else if (!shouldBeDrawn && isDrawn) {
mHasUiUpdates = true;
mDrawables.remove(drawable);
if (DEBUG) {
Log.d(TAG, "Update " + blurRegion);
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);
}
}
/**
* If there are any blur regions visible on the screen at the moment.
* @return true if there are any updates that need to be sent to SF
*/
@UiThread
public boolean hasUpdates() {
return mHasUiUpdates;
}
/**
* @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));
}
mNeedsUpdate = false;
if (mTmpBlurRegionsArray == null
|| mTmpBlurRegionsArray.length != mBlurRegions.size()) {
mTmpBlurRegionsArray = new float[mBlurRegions.size()][];
}
if (DEBUG) {
Log.d(TAG, "onBlurRegionUpdated will dispatch " + mTmpBlurRegionsArray.length
+ " regions for frame " + frameNumber);
}
for (int i = 0; i < mTmpBlurRegionsArray.length; i++) {
mTmpBlurRegionsArray[i] = mBlurRegions.valueAt(i).toFloatArray();
}
mViewRoot.dispatchBlurRegions(mTmpBlurRegionsArray, frameNumber);
mHasUiUpdates = false;
}
return mTmpBlurRegionsForFrame;
}
/**
* 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, 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);
}
}
}
/**
* 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;
}
/**
* 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
+ "}";
}
}
}