diff options
| -rw-r--r-- | core/java/android/view/Choreographer.java | 143 | ||||
| -rw-r--r-- | core/java/android/view/ViewRootImpl.java | 1 | ||||
| -rw-r--r-- | core/jni/android_graphics_BLASTBufferQueue.cpp | 51 | ||||
| -rw-r--r-- | graphics/java/android/graphics/BLASTBufferQueue.java | 18 |
4 files changed, 138 insertions, 75 deletions
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 8cb96ae1d611..089b5c256b6e 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -42,6 +42,7 @@ import android.view.animation.AnimationUtils; import java.io.PrintWriter; import java.util.Locale; +import java.util.concurrent.atomic.AtomicBoolean; /** * Coordinates the timing of animations, input and drawing. @@ -208,7 +209,7 @@ public final class Choreographer { private final FrameData mFrameData = new FrameData(); private volatile boolean mInDoFrameCallback = false; - private static class BufferStuffingData { + private static class BufferStuffingState { enum RecoveryAction { // No recovery NONE, @@ -218,21 +219,15 @@ public final class Choreographer { // back toward threshold. DELAY_FRAME } - // The maximum number of times frames will be delayed per buffer stuffing event. - // Since buffer stuffing can persist for several consecutive frames following the - // initial missed frame, we want to adjust the timeline with enough frame delays and - // offsets to return the queued buffer count back to threshold. - public static final int MAX_FRAME_DELAYS = 3; + // Indicates if recovery should begin. Is true whenever the client was blocked + // on dequeuing a buffer. When buffer stuffing recovery begins, this is reset + // since the scheduled frame delay reduces the number of queued buffers. + public AtomicBoolean isStuffed = new AtomicBoolean(false); // Whether buffer stuffing recovery has begun. Recovery can only end // when events are idle. public boolean isRecovering = false; - // The number of frames delayed so far during recovery. Used to compare with - // MAX_FRAME_DELAYS to safeguard against excessive frame delays during recovery. - // Also used as unique cookie for tracing. - public int numberFrameDelays = 0; - // The number of additional frame delays scheduled during recovery to wait for the next // vsync. These are scheduled when frame times appear to go backward or frames are // being skipped due to FPSDivisor. @@ -245,12 +240,20 @@ public final class Choreographer { */ public void reset() { isRecovering = false; - numberFrameDelays = 0; numberWaitsForNextVsync = 0; } } - private final BufferStuffingData mBufferStuffingData = new BufferStuffingData(); + private final BufferStuffingState mBufferStuffingState = new BufferStuffingState(); + + /** + * Set flag to indicate that client is blocked waiting for buffer release and + * buffer stuffing recovery should soon begin. + * @hide + */ + public void onWaitForBufferRelease() { + mBufferStuffingState.isStuffed.set(true); + } /** * Contains information about the current frame for jank-tracking, @@ -901,67 +904,56 @@ public final class Choreographer { // Conducts logic for beginning or ending buffer stuffing recovery. // Returns an enum for the recovery action that should be taken in doFrame(). - BufferStuffingData.RecoveryAction checkBufferStuffingRecovery(long frameTimeNanos, + BufferStuffingState.RecoveryAction updateBufferStuffingState(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) { - // Canned animations can recover from buffer stuffing whenever more - // than 2 buffers are queued. - if (vsyncEventData.numberQueuedBuffers > 2) { - mBufferStuffingData.isRecovering = true; - // Intentional frame delay that can happen at most MAX_FRAME_DELAYS times per - // buffer stuffing event until the buffer count returns to threshold. The - // delayed frames are compensated for by the negative offsets added to the - // animation timestamps. - if (mBufferStuffingData.numberFrameDelays < mBufferStuffingData.MAX_FRAME_DELAYS) { - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.asyncTraceForTrackBegin( - Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", "Thread " - + android.os.Process.myTid() + ", recover frame #" - + mBufferStuffingData.numberFrameDelays, - mBufferStuffingData.numberFrameDelays); - } - mBufferStuffingData.numberFrameDelays++; - scheduleVsyncLocked(); - return BufferStuffingData.RecoveryAction.DELAY_FRAME; + if (!mBufferStuffingState.isRecovering) { + if (!mBufferStuffingState.isStuffed.getAndSet(false)) { + return BufferStuffingState.RecoveryAction.NONE; + } + // Canned animations can recover from buffer stuffing whenever the + // client is blocked on dequeueBuffer. Frame delay only occurs at + // the start of recovery to free a buffer. + mBufferStuffingState.isRecovering = true; + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.asyncTraceForTrackBegin( + Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", "Thread " + + android.os.Process.myTid() + ", recover frame", 0); } + return BufferStuffingState.RecoveryAction.DELAY_FRAME; } - if (mBufferStuffingData.isRecovering) { - // Includes an additional expected frame delay from the natural scheduling - // of the next vsync event. - int totalFrameDelays = mBufferStuffingData.numberFrameDelays - + mBufferStuffingData.numberWaitsForNextVsync + 1; - long vsyncsSinceLastCallback = mLastFrameIntervalNanos > 0 - ? (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos : 0; - - // Detected idle state due to a longer inactive period since the last vsync callback - // than the total expected number of vsync frame delays. End buffer stuffing recovery. - // There are no frames to animate and offsets no longer need to be added - // since the idle state gives the animation a chance to catch up. - if (vsyncsSinceLastCallback > totalFrameDelays) { - if (DEBUG_JANK) { - Log.d(TAG, "End buffer stuffing recovery"); - } - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - for (int i = 0; i < mBufferStuffingData.numberFrameDelays; i++) { - Trace.asyncTraceForTrackEnd( - Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", i); - } - } - mBufferStuffingData.reset(); - - } else { - if (DEBUG_JANK) { - Log.d(TAG, "Adjust animation timeline with a negative offset"); - } - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.instantForTrack( - Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", - "Negative offset added to animation"); - } - return BufferStuffingData.RecoveryAction.OFFSET; + // Total number of frame delays used to detect idle state. Includes an additional + // expected frame delay from the natural scheduling of the next vsync event and + // the intentional frame delay that was scheduled when stuffing was first detected. + int totalFrameDelays = mBufferStuffingState.numberWaitsForNextVsync + 2; + long vsyncsSinceLastCallback = mLastFrameIntervalNanos > 0 + ? (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos : 0; + + // Detected idle state due to a longer inactive period since the last vsync callback + // than the total expected number of vsync frame delays. End buffer stuffing recovery. + // There are no frames to animate and offsets no longer need to be added + // since the idle state gives the animation a chance to catch up. + if (vsyncsSinceLastCallback > totalFrameDelays) { + if (DEBUG_JANK) { + Log.d(TAG, "End buffer stuffing recovery"); } + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.asyncTraceForTrackEnd( + Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", 0); + } + mBufferStuffingState.reset(); + return BufferStuffingState.RecoveryAction.NONE; + } + + if (DEBUG_JANK) { + Log.d(TAG, "Adjust animation timeline with a negative offset"); + } + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instantForTrack( + Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", + "Negative offset added to animation"); } - return BufferStuffingData.RecoveryAction.NONE; + return BufferStuffingState.RecoveryAction.OFFSET; } void doFrame(long frameTimeNanos, int frame, @@ -973,7 +965,7 @@ public final class Choreographer { // Evaluate if buffer stuffing recovery needs to start or end, and // what actions need to be taken for recovery. - switch (checkBufferStuffingRecovery(frameTimeNanos, vsyncEventData)) { + switch (updateBufferStuffingState(frameTimeNanos, vsyncEventData)) { case NONE: // Without buffer stuffing recovery, offsetFrameTimeNanos is // synonymous with frameTimeNanos. @@ -984,7 +976,8 @@ public final class Choreographer { offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos; break; case DELAY_FRAME: - // Intentional frame delay to help restore queued buffer count to threshold. + // Intentional frame delay to help reduce queued buffer count. + scheduleVsyncLocked(); return; default: break; @@ -1037,7 +1030,7 @@ public final class Choreographer { + " ms in the past."); } } - if (mBufferStuffingData.isRecovering) { + if (mBufferStuffingState.isRecovering) { frameTimeNanos -= frameIntervalNanos; if (DEBUG_JANK) { Log.d(TAG, "Adjusted animation timeline with a negative offset after" @@ -1055,8 +1048,8 @@ public final class Choreographer { + "previously skipped frame. Waiting for next vsync."); } traceMessage("Frame time goes backward"); - if (mBufferStuffingData.isRecovering) { - mBufferStuffingData.numberWaitsForNextVsync++; + if (mBufferStuffingState.isRecovering) { + mBufferStuffingState.numberWaitsForNextVsync++; } scheduleVsyncLocked(); return; @@ -1066,8 +1059,8 @@ public final class Choreographer { long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) { traceMessage("Frame skipped due to FPSDivisor"); - if (mBufferStuffingData.isRecovering) { - mBufferStuffingData.numberWaitsForNextVsync++; + if (mBufferStuffingState.isRecovering) { + mBufferStuffingState.numberWaitsForNextVsync++; } scheduleVsyncLocked(); return; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index c1b92ee3f74e..609f1ef06612 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2772,6 +2772,7 @@ public final class ViewRootImpl implements ViewParent, mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format); mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback); + mBlastBufferQueue.setWaitForBufferReleaseCallback(mChoreographer::onWaitForBufferRelease); // If we create and destroy BBQ without recreating the SurfaceControl, we can end up // queuing buffers on multiple apply tokens causing out of order buffer submissions. We // fix this by setting the same apply token on all BBQs created by this VRI. diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index b9c3bf73f11c..7b61a5db0b41 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -87,6 +87,38 @@ private: jobject mTransactionHangObject; }; +struct { + jmethodID onWaitForBufferRelease; +} gWaitForBufferReleaseCallback; + +class WaitForBufferReleaseCallbackWrapper + : public LightRefBase<WaitForBufferReleaseCallbackWrapper> { +public: + explicit WaitForBufferReleaseCallbackWrapper(JNIEnv* env, jobject jobject) { + env->GetJavaVM(&mVm); + mWaitForBufferReleaseObject = env->NewGlobalRef(jobject); + LOG_ALWAYS_FATAL_IF(!mWaitForBufferReleaseObject, "Failed to make global ref"); + } + + ~WaitForBufferReleaseCallbackWrapper() { + if (mWaitForBufferReleaseObject != nullptr) { + getenv(mVm)->DeleteGlobalRef(mWaitForBufferReleaseObject); + mWaitForBufferReleaseObject = nullptr; + } + } + + void onWaitForBufferRelease() { + JNIEnv* env = getenv(mVm); + getenv(mVm)->CallVoidMethod(mWaitForBufferReleaseObject, + gWaitForBufferReleaseCallback.onWaitForBufferRelease); + DieIfException(env, "Uncaught exception in WaitForBufferReleaseCallback."); + } + +private: + JavaVM* mVm; + jobject mWaitForBufferReleaseObject; +}; + static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jboolean updateDestinationFrame) { ScopedUtfChars name(env, jName); @@ -215,6 +247,18 @@ static void nativeSetApplyToken(JNIEnv* env, jclass clazz, jlong ptr, jobject ap return queue->setApplyToken(std::move(token)); } +static void nativeSetWaitForBufferReleaseCallback(JNIEnv* env, jclass clazz, jlong ptr, + jobject waitForBufferReleaseCallback) { + sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); + if (waitForBufferReleaseCallback == nullptr) { + queue->setWaitForBufferReleaseCallback(nullptr); + } else { + sp<WaitForBufferReleaseCallbackWrapper> wrapper = + new WaitForBufferReleaseCallbackWrapper{env, waitForBufferReleaseCallback}; + queue->setWaitForBufferReleaseCallback([wrapper]() { wrapper->onWaitForBufferRelease(); }); + } +} + static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ // clang-format off @@ -234,6 +278,9 @@ static const JNINativeMethod gMethods[] = { "(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V", (void*)nativeSetTransactionHangCallback}, {"nativeSetApplyToken", "(JLandroid/os/IBinder;)V", (void*)nativeSetApplyToken}, + {"nativeSetWaitForBufferReleaseCallback", + "(JLandroid/graphics/BLASTBufferQueue$WaitForBufferReleaseCallback;)V", + (void*)nativeSetWaitForBufferReleaseCallback}, // clang-format on }; @@ -255,6 +302,10 @@ int register_android_graphics_BLASTBufferQueue(JNIEnv* env) { gTransactionHangCallback.onTransactionHang = GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang", "(Ljava/lang/String;)V"); + jclass waitForBufferReleaseClass = + FindClassOrDie(env, "android/graphics/BLASTBufferQueue$WaitForBufferReleaseCallback"); + gWaitForBufferReleaseCallback.onWaitForBufferRelease = + GetMethodIDOrDie(env, waitForBufferReleaseClass, "onWaitForBufferRelease", "()V"); return 0; } diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 90723b2f1493..906c71d9caca 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -49,11 +49,22 @@ public final class BLASTBufferQueue { private static native void nativeSetTransactionHangCallback(long ptr, TransactionHangCallback callback); private static native void nativeSetApplyToken(long ptr, IBinder applyToken); + private static native void nativeSetWaitForBufferReleaseCallback(long ptr, + WaitForBufferReleaseCallback callback); public interface TransactionHangCallback { void onTransactionHang(String reason); } + + public interface WaitForBufferReleaseCallback { + /** + * Indicates that the client is waiting on buffer release + * due to no free buffers being available to render into. + */ + void onWaitForBufferRelease(); + } + /** Create a new connection with the surface flinger. */ public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height, @PixelFormat.Format int format) { @@ -210,4 +221,11 @@ public final class BLASTBufferQueue { public void setApplyToken(IBinder applyToken) { nativeSetApplyToken(mNativeObject, applyToken); } + + /** + * Propagate callback about being blocked on buffer release. + */ + public void setWaitForBufferReleaseCallback(WaitForBufferReleaseCallback waitCallback) { + nativeSetWaitForBufferReleaseCallback(mNativeObject, waitCallback); + } } |