Ensure thread safety with render thread and UI Thread.

There were a few places that were not thread safe.

1. finishBLASTSync is called from the Render Thread. It
was updating mSurfaceChangedTransaction, which send to WMS on the
UI Thread. Instead, create a new local Transaction object to allow the
Render Thread to merge the mRtBLASTSyncTransaction into it. Then on the
UI thread, merge the temporary transaction into mSurfaceChangedTransaction

2. finishBLASTSync was getting called if the draw was unable to run
asynchronously. This would mean it would get executed on the UI Thread,
possibly causing a race. Instead, remove since there should be nothing
on the blast sync transaction, mRtNextFrameReportedConsumeWithBlast
would never have been set, and mSendNextFrameToWm is set to false
beforehand.

Test: YT with and without Blast
Change-Id: I72e70fea258a933f51aaaf78c7056a0d3fbac8b3
This commit is contained in:
chaviw
2020-10-01 11:55:47 -07:00
parent ae00abf8ed
commit 7b5c08e5e6

View File

@@ -105,6 +105,7 @@ import android.graphics.BLASTBufferQueue;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.FrameInfo;
import android.graphics.HardwareRenderer;
import android.graphics.HardwareRenderer.FrameDrawingCallback;
import android.graphics.Insets;
import android.graphics.Matrix;
@@ -3810,6 +3811,82 @@ public final class ViewRootImpl implements ViewParent,
}
}
/**
* The callback will run on the render thread.
*/
private HardwareRenderer.FrameCompleteCallback createFrameCompleteCallback(Handler handler,
boolean reportNextDraw, ArrayList<Runnable> commitCallbacks) {
return frameNr -> {
// Use a new transaction here since mRtBLASTSyncTransaction can only be accessed by
// the render thread and mSurfaceChangedTransaction can only be accessed by the UI
// thread. The temporary transaction is used so mRtBLASTSyncTransaction can be merged
// with mSurfaceChangedTransaction without synchronization issues.
final Transaction t = new Transaction();
finishBLASTSyncOnRT(!mSendNextFrameToWm, t);
handler.postAtFrontOfQueue(() -> {
mSurfaceChangedTransaction.merge(t);
if (reportNextDraw) {
// TODO: Use the frame number
pendingDrawFinished();
}
if (commitCallbacks != null) {
for (int i = 0; i < commitCallbacks.size(); i++) {
commitCallbacks.get(i).run();
}
}
});
};
}
private boolean addFrameCompleteCallbackIfNeeded() {
if (mAttachInfo.mThreadedRenderer == null || !mAttachInfo.mThreadedRenderer.isEnabled()) {
return false;
}
ArrayList<Runnable> commitCallbacks = mAttachInfo.mTreeObserver
.captureFrameCommitCallbacks();
final boolean needFrameCompleteCallback =
mNextDrawUseBLASTSyncTransaction || mReportNextDraw
|| (commitCallbacks != null && commitCallbacks.size() > 0);
if (needFrameCompleteCallback) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(
createFrameCompleteCallback(mAttachInfo.mHandler, mReportNextDraw,
commitCallbacks));
return true;
}
return false;
}
/**
* The callback will run on a worker thread pool from the render thread.
*/
private HardwareRenderer.FrameDrawingCallback createFrameDrawingCallback() {
return frame -> {
mRtNextFrameReportedConsumeWithBlast = true;
if (mBlastBufferQueue != null) {
// We don't need to synchronize mRtBLASTSyncTransaction here since it's not
// being modified and only sent to BlastBufferQueue.
mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
}
};
}
private void addFrameCallbackIfNeeded() {
if (!mNextDrawUseBLASTSyncTransaction) {
return;
}
// Frame callbacks will always occur after submitting draw requests and before
// the draw actually occurs. This will ensure that we set the next transaction
// for the frame that's about to get drawn and not on a previous frame that.
//
// This is thread safe since mRtNextFrameReportConsumeWithBlast will only be
// modified in onFrameDraw and then again in onFrameComplete. This is to ensure the
// next frame completed should be reported with the blast sync transaction.
registerRtFrameCallback(createFrameDrawingCallback());
mNextDrawUseBLASTSyncTransaction = false;
}
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
@@ -3823,58 +3900,14 @@ public final class ViewRootImpl implements ViewParent,
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
boolean usingAsyncReport = false;
boolean reportNextDraw = mReportNextDraw; // Capture the original value
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
ArrayList<Runnable> commitCallbacks = mAttachInfo.mTreeObserver
.captureFrameCommitCallbacks();
final boolean needFrameCompleteCallback = mNextDrawUseBLASTSyncTransaction ||
(commitCallbacks != null && commitCallbacks.size() > 0) ||
mReportNextDraw;
usingAsyncReport = mReportNextDraw;
if (needFrameCompleteCallback) {
final Handler handler = mAttachInfo.mHandler;
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> {
finishBLASTSync(!mSendNextFrameToWm);
handler.postAtFrontOfQueue(() -> {
if (reportNextDraw) {
// TODO: Use the frame number
pendingDrawFinished();
}
if (commitCallbacks != null) {
for (int i = 0; i < commitCallbacks.size(); i++) {
commitCallbacks.get(i).run();
}
}
});
});
}
}
boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded();
addFrameCallbackIfNeeded();
try {
if (mNextDrawUseBLASTSyncTransaction) {
// Frame callbacks will always occur after submitting draw requests and before
// the draw actually occurs. This will ensure that we set the next transaction
// for the frame that's about to get drawn and not on a previous frame that.
//
// This is thread safe since mRtNextFrameReportConsumeWithBlast will only be
// modified in onFrameDraw and then again in onFrameComplete. This is to ensure the
// next frame completed should be reported with the blast sync transaction.
registerRtFrameCallback(frame -> {
mRtNextFrameReportedConsumeWithBlast = true;
if (mBlastBufferQueue != null) {
// We don't need to synchronize mRtBLASTSyncTransaction here since it's not
// being modified and only sent to BlastBufferQueue.
mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
}
});
mNextDrawUseBLASTSyncTransaction = false;
}
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
finishBLASTSync(true /* apply */);
}
} finally {
mIsDrawing = false;
@@ -9839,7 +9872,12 @@ public final class ViewRootImpl implements ViewParent,
mNextDrawUseBLASTSyncTransaction = true;
}
private void finishBLASTSync(boolean apply) {
/**
* This should only be called from the render thread.
*/
private void finishBLASTSyncOnRT(boolean apply, Transaction t) {
// This is safe to modify on the render thread since the only other place it's modified
// is on the UI thread when the render thread is paused.
mSendNextFrameToWm = false;
if (mRtNextFrameReportedConsumeWithBlast) {
mRtNextFrameReportedConsumeWithBlast = false;
@@ -9850,7 +9888,7 @@ public final class ViewRootImpl implements ViewParent,
if (apply) {
mRtBLASTSyncTransaction.apply();
} else {
mSurfaceChangedTransaction.merge(mRtBLASTSyncTransaction);
t.merge(mRtBLASTSyncTransaction);
}
}
}