Merge "Fix synchronisation in BackgroundBlurDrawable" into sc-dev

This commit is contained in:
Galia Peycheva
2021-02-26 14:30:04 +00:00
committed by Android (Google) Code Review
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
+ "}";
}
}
}