Send syncStatus and return FrameCommitCallback in FrameCallback

FrameCallback sends back the syncStatus and allows the caller to return
a FrameCommitCallback. This is so the caller can evaluate the results of
the sync and determine if it wants to continue waiting for a commit
callback. In cases, where the sync has failed, there will never be a
commit callback so the caller can avoid waiting.

This is partically helpful for VRI because it wants to sync with a frame
that will draw, but also doesn't want to get stuck indefinitely waiting
on the callback. VRI can check the syncStatus and only handle blast sync
and/or reportDraw when its determined that a draw will happen.

This allows VRI to remove frameCompleteCallback since it can now wait
until a buffer has drawn instead of when the vsync has completed. This makes
sure it waits on FrameDropped since that result indicates HWUI will draw on
the next vsync

Test: BlastSync
Bug: 200284684
Change-Id: Ic6e2f08ea21ac8a1634a3389c927fcb68ede3f7b
This commit is contained in:
chaviw
2021-12-13 15:46:29 -06:00
parent 6d2adf4215
commit b680371f77
8 changed files with 210 additions and 103 deletions

View File

@@ -477,6 +477,19 @@ public final class ThreadedRenderer extends HardwareRenderer {
mNextRtFrameCallbacks.add(callback);
}
/**
* Remove a frame drawing callback that was added via
* {@link #registerRtFrameCallback(FrameDrawingCallback)}
*
* @param callback The callback to unregister.
*/
void unregisterRtFrameCallback(@NonNull FrameDrawingCallback callback) {
if (mNextRtFrameCallbacks == null) {
return;
}
mNextRtFrameCallbacks.remove(callback);
}
/**
* Destroys all hardware rendering resources associated with the specified
* view hierarchy.
@@ -679,9 +692,31 @@ public final class ThreadedRenderer extends HardwareRenderer {
if (mNextRtFrameCallbacks != null) {
final ArrayList<FrameDrawingCallback> frameCallbacks = mNextRtFrameCallbacks;
mNextRtFrameCallbacks = null;
setFrameCallback(frame -> {
for (int i = 0; i < frameCallbacks.size(); ++i) {
frameCallbacks.get(i).onFrameDraw(frame);
setFrameCallback(new FrameDrawingCallback() {
@Override
public void onFrameDraw(long frame) {
}
@Override
public FrameCommitCallback onFrameDraw(int syncResult, long frame) {
ArrayList<FrameCommitCallback> frameCommitCallbacks = new ArrayList<>();
for (int i = 0; i < frameCallbacks.size(); ++i) {
FrameCommitCallback frameCommitCallback = frameCallbacks.get(i)
.onFrameDraw(syncResult, frame);
if (frameCommitCallback != null) {
frameCommitCallbacks.add(frameCommitCallback);
}
}
if (frameCommitCallbacks.isEmpty()) {
return null;
}
return didProduceBuffer -> {
for (int i = 0; i < frameCommitCallbacks.size(); ++i) {
frameCommitCallbacks.get(i).onFrameCommit(didProduceBuffer);
}
};
}
});
}

View File

@@ -16,6 +16,8 @@
package android.view;
import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
@@ -1395,11 +1397,20 @@ public final class ViewRootImpl implements ViewParent,
*/
public void registerRtFrameCallback(@NonNull FrameDrawingCallback callback) {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.registerRtFrameCallback(frame -> {
try {
callback.onFrameDraw(frame);
} catch (Exception e) {
Log.e(TAG, "Exception while executing onFrameDraw", e);
mAttachInfo.mThreadedRenderer.registerRtFrameCallback(new FrameDrawingCallback() {
@Override
public void onFrameDraw(long frame) {
}
@Override
public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult,
long frame) {
try {
return callback.onFrameDraw(syncResult, frame);
} catch (Exception e) {
Log.e(TAG, "Exception while executing onFrameDraw", e);
}
return null;
}
});
}
@@ -4026,61 +4037,6 @@ public final class ViewRootImpl implements ViewParent,
return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled();
}
private boolean addFrameCompleteCallbackIfNeeded(boolean useBlastSync,
boolean reportNextDraw) {
if (!isHardwareEnabled()) {
return false;
}
if (!useBlastSync && !reportNextDraw) {
return false;
}
if (DEBUG_BLAST) {
Log.d(mTag, "Creating frameCompleteCallback");
}
final Consumer<SurfaceControl.Transaction> blastSyncConsumer = mBLASTDrawConsumer;
mBLASTDrawConsumer = null;
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(() -> {
long frameNr = mBlastBufferQueue.getLastAcquiredFrameNum();
if (DEBUG_BLAST) {
Log.d(mTag, "Received frameCompleteCallback "
+ " lastAcquiredFrameNum=" + frameNr
+ " lastAttemptedDrawFrameNum=" + mRtLastAttemptedDrawFrameNum);
}
boolean frameWasNotDrawn = frameNr != mRtLastAttemptedDrawFrameNum;
// If frame wasn't drawn, clear out the next transaction so it doesn't affect the next
// draw attempt. The next transaction and transaction complete callback were only set
// for the current draw attempt.
if (frameWasNotDrawn) {
mBlastBufferQueue.setSyncTransaction(null);
// Apply the transactions that were sent to mergeWithNextTransaction since the
// frame didn't draw on this vsync. It's possible the frame will draw later, but
// it's better to not be sync than to block on a frame that may never come.
mBlastBufferQueue.applyPendingTransactions(mRtLastAttemptedDrawFrameNum);
}
Transaction tmpTransaction = new Transaction();
tmpTransaction.merge(mRtBLASTSyncTransaction);
mHandler.postAtFrontOfQueue(() -> {
if (useBlastSync) {
mSurfaceChangedTransaction.merge(tmpTransaction);
if (blastSyncConsumer != null) {
blastSyncConsumer.accept(mSurfaceChangedTransaction);
}
}
if (reportNextDraw) {
pendingDrawFinished();
}
});
});
return true;
}
private void addFrameCommitCallbackIfNeeded() {
if (!isHardwareEnabled()) {
return;
@@ -4111,51 +4067,131 @@ public final class ViewRootImpl implements ViewParent,
});
}
private void addFrameCallbackIfNeeded(boolean useBlastSync) {
private HardwareRenderer.FrameCommitCallback createFrameCommitCallbackForSync(
boolean useBlastSync, boolean reportNextDraw, Consumer<Transaction> blastSyncConsumer) {
return didProduceBuffer -> {
if (DEBUG_BLAST) {
Log.d(mTag, "Received frameCommittedCallback "
+ " lastAttemptedDrawFrameNum=" + mRtLastAttemptedDrawFrameNum
+ " didProduceBuffer=" + didProduceBuffer);
}
// If frame wasn't drawn, clear out the next transaction so it doesn't affect the next
// draw attempt. The next transaction and transaction complete callback were only set
// for the current draw attempt.
if (!didProduceBuffer) {
mBlastBufferQueue.setSyncTransaction(null);
// Apply the transactions that were sent to mergeWithNextTransaction since the
// frame didn't draw on this vsync. It's possible the frame will draw later, but
// it's better to not be sync than to block on a frame that may never come.
mBlastBufferQueue.applyPendingTransactions(mRtLastAttemptedDrawFrameNum);
}
Transaction tmpTransaction = new Transaction();
tmpTransaction.merge(mRtBLASTSyncTransaction);
// Post at front of queue so the buffer can be processed immediately and allow RT
// to continue processing new buffers. If RT tries to process buffers before the sync
// buffer is applied, the new buffers will not get acquired and could result in a
// deadlock. UI thread would wait on RT, but RT would be blocked waiting for a free
// buffer.
mHandler.postAtFrontOfQueue(() -> {
if (useBlastSync) {
mSurfaceChangedTransaction.merge(tmpTransaction);
if (blastSyncConsumer != null) {
blastSyncConsumer.accept(mSurfaceChangedTransaction);
}
}
if (reportNextDraw) {
pendingDrawFinished();
}
});
};
}
@Nullable
private FrameDrawingCallback createFrameDrawingCallbackIfNeeded(boolean useBlastSync,
boolean reportNextDraw) {
if (!isHardwareEnabled()) {
return null;
}
final boolean hasBlurUpdates = mBlurRegionAggregator.hasUpdates();
final boolean needsCallbackForBlur = hasBlurUpdates || mBlurRegionAggregator.hasRegions();
if (!useBlastSync && !needsCallbackForBlur) {
return;
if (!useBlastSync && !needsCallbackForBlur && !reportNextDraw) {
return null;
}
final Consumer<SurfaceControl.Transaction> blastSyncConsumer = mBLASTDrawConsumer;
mBLASTDrawConsumer = null;
if (DEBUG_BLAST) {
Log.d(mTag, "Creating frameDrawingCallback"
+ " nextDrawUseBlastSync=" + useBlastSync
+ " hasBlurUpdates=" + hasBlurUpdates);
+ " reportNextDraw=" + reportNextDraw
+ " hasBlurUpdates=" + hasBlurUpdates
+ " hasBlastSyncConsumer=" + (blastSyncConsumer != null));
}
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=" + useBlastSync);
return new FrameDrawingCallback() {
@Override
public void onFrameDraw(long frame) {
}
mRtLastAttemptedDrawFrameNum = frame;
@Override
public HardwareRenderer.FrameCommitCallback onFrameDraw(int syncResult, long frame) {
if (DEBUG_BLAST) {
Log.d(mTag,
"Received frameDrawingCallback syncResult=" + syncResult + " frameNum="
+ frame + ".");
}
if (needsCallbackForBlur) {
mBlurRegionAggregator
.dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates);
}
mRtLastAttemptedDrawFrameNum = frame;
if (mBlastBufferQueue == null) {
return;
}
if (needsCallbackForBlur) {
mBlurRegionAggregator.dispatchBlurTransactionIfNeeded(frame,
blurRegionsForFrame, hasBlurUpdates);
}
if (useBlastSync) {
// 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.
if (mBlastBufferQueue == null) {
return null;
}
// We don't need to synchronize mRtBLASTSyncTransaction here since it's not
// being modified and only sent to BlastBufferQueue.
mBlastBufferQueue.setSyncTransaction(mRtBLASTSyncTransaction);
if (!useBlastSync && !reportNextDraw) {
return null;
}
// If the syncResults are SYNC_LOST_SURFACE_REWARD_IF_FOUND or
// SYNC_CONTEXT_IS_STOPPED it means nothing will draw. There's no need to set up
// any blast sync or commit callback, and the code should directly call
// pendingDrawFinished.
if ((syncResult
& (SYNC_LOST_SURFACE_REWARD_IF_FOUND | SYNC_CONTEXT_IS_STOPPED)) != 0) {
if (reportNextDraw) {
mHandler.postAtFrontOfQueue(() -> pendingDrawFinished());
}
return null;
}
if (DEBUG_BLAST) {
Log.d(mTag, "Setting up sync and frameCommitCallback");
}
if (useBlastSync) {
// 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.
mBlastBufferQueue.setSyncTransaction(mRtBLASTSyncTransaction);
}
return createFrameCommitCallbackForSync(useBlastSync, reportNextDraw,
blastSyncConsumer);
}
};
registerRtFrameCallback(frameDrawingCallback);
}
private void performDraw(boolean useBlastSync) {
@@ -4171,15 +4207,20 @@ public final class ViewRootImpl implements ViewParent,
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
addFrameCallbackIfNeeded(useBlastSync);
FrameDrawingCallback frameDrawingCallback = createFrameDrawingCallbackIfNeeded(useBlastSync,
mReportNextDraw);
if (frameDrawingCallback != null) {
mAttachInfo.mThreadedRenderer.registerRtFrameCallback(frameDrawingCallback);
}
addFrameCommitCallbackIfNeeded();
boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded(useBlastSync, mReportNextDraw);
boolean usingAsyncReport = isHardwareEnabled() && (useBlastSync || mReportNextDraw);
try {
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
mAttachInfo.mThreadedRenderer.setFrameCallback(null);
usingAsyncReport = false;
mAttachInfo.mThreadedRenderer.unregisterRtFrameCallback(frameDrawingCallback);
}
} finally {
mIsDrawing = false;

View File

@@ -902,6 +902,20 @@ public class HardwareRenderer {
* @param frame The id of the frame being drawn.
*/
void onFrameDraw(long frame);
/**
* Invoked during a frame drawing.
*
* @param syncResult The result of the draw. Should be a value or a combination of values
* from {@link SyncAndDrawResult}
* @param frame The id of the frame being drawn.
*
* @return A {@link FrameCommitCallback} that will report back if the current vsync draws.
*/
default FrameCommitCallback onFrameDraw(@SyncAndDrawResult int syncResult, long frame) {
onFrameDraw(frame);
return null;
}
}
/**

View File

@@ -609,10 +609,19 @@ static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env,
LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM");
auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm,
env->NewGlobalRef(frameCallback));
proxy->setFrameCallback([globalCallbackRef](int64_t frameNr) {
proxy->setFrameCallback([globalCallbackRef](int32_t syncResult,
int64_t frameNr) -> std::function<void(bool)> {
JNIEnv* env = getenv(globalCallbackRef->vm());
env->CallVoidMethod(globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
static_cast<jlong>(frameNr));
ScopedLocalRef<jobject> frameCommitCallback(
env, env->CallObjectMethod(
globalCallbackRef->object(), gFrameDrawingCallback.onFrameDraw,
static_cast<jint>(syncResult), static_cast<jlong>(frameNr)));
if (frameCommitCallback == nullptr) {
return nullptr;
}
sp<FrameCommitWrapper> wrapper =
sp<FrameCommitWrapper>::make(env, frameCommitCallback.get());
return [wrapper](bool didProduceBuffer) { wrapper->onFrameCommit(didProduceBuffer); };
});
}
}
@@ -623,7 +632,7 @@ static void android_view_ThreadedRenderer_setFrameCommitCallback(JNIEnv* env, jo
if (!callback) {
proxy->setFrameCommitCallback(nullptr);
} else {
sp<FrameCommitWrapper> wrapper = new FrameCommitWrapper{env, callback};
sp<FrameCommitWrapper> wrapper = sp<FrameCommitWrapper>::make(env, callback);
proxy->setFrameCommitCallback(
[wrapper](bool didProduceBuffer) { wrapper->onFrameCommit(didProduceBuffer); });
}
@@ -1003,8 +1012,9 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) {
jclass frameCallbackClass = FindClassOrDie(env,
"android/graphics/HardwareRenderer$FrameDrawingCallback");
gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass,
"onFrameDraw", "(J)V");
gFrameDrawingCallback.onFrameDraw =
GetMethodIDOrDie(env, frameCallbackClass, "onFrameDraw",
"(IJ)Landroid/graphics/HardwareRenderer$FrameCommitCallback;");
jclass frameCommitClass =
FindClassOrDie(env, "android/graphics/HardwareRenderer$FrameCommitCallback");

View File

@@ -158,7 +158,8 @@ void DrawFrameTask::run() {
// Grab a copy of everything we need
CanvasContext* context = mContext;
std::function<void(int64_t)> frameCallback = std::move(mFrameCallback);
std::function<std::function<void(bool)>(int32_t, int64_t)> frameCallback =
std::move(mFrameCallback);
std::function<void()> frameCompleteCallback = std::move(mFrameCompleteCallback);
mFrameCallback = nullptr;
mFrameCompleteCallback = nullptr;
@@ -173,8 +174,13 @@ void DrawFrameTask::run() {
// Even if we aren't drawing this vsync pulse the next frame number will still be accurate
if (CC_UNLIKELY(frameCallback)) {
context->enqueueFrameWork(
[frameCallback, frameNr = context->getFrameNumber()]() { frameCallback(frameNr); });
context->enqueueFrameWork([frameCallback, context, syncResult = mSyncResult,
frameNr = context->getFrameNumber()]() {
auto frameCommitCallback = std::move(frameCallback(syncResult, frameNr));
if (frameCommitCallback) {
context->addFrameCommitListener(std::move(frameCommitCallback));
}
});
}
nsecs_t dequeueBufferDuration = 0;

View File

@@ -77,7 +77,7 @@ public:
void run();
void setFrameCallback(std::function<void(int64_t)>&& callback) {
void setFrameCallback(std::function<std::function<void(bool)>(int32_t, int64_t)>&& callback) {
mFrameCallback = std::move(callback);
}
@@ -126,7 +126,7 @@ private:
int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
std::function<void(int64_t)> mFrameCallback;
std::function<std::function<void(bool)>(int32_t, int64_t)> mFrameCallback;
std::function<void(bool)> mFrameCommitCallback;
std::function<void()> mFrameCompleteCallback;

View File

@@ -327,7 +327,8 @@ void RenderProxy::setPrepareSurfaceControlForWebviewCallback(
[this, cb = callback]() { mContext->setPrepareSurfaceControlForWebviewCallback(cb); });
}
void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) {
void RenderProxy::setFrameCallback(
std::function<std::function<void(bool)>(int32_t, int64_t)>&& callback) {
mDrawFrameTask.setFrameCallback(std::move(callback));
}

View File

@@ -124,7 +124,7 @@ public:
void setASurfaceTransactionCallback(
const std::function<bool(int64_t, int64_t, int64_t)>& callback);
void setPrepareSurfaceControlForWebviewCallback(const std::function<void()>& callback);
void setFrameCallback(std::function<void(int64_t)>&& callback);
void setFrameCallback(std::function<std::function<void(bool)>(int32_t, int64_t)>&& callback);
void setFrameCommitCallback(std::function<void(bool)>&& callback);
void setFrameCompleteCallback(std::function<void()>&& callback);