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:
@@ -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
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user