summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/view/Choreographer.java143
-rw-r--r--core/java/android/view/ViewRootImpl.java1
-rw-r--r--core/jni/android_graphics_BLASTBufferQueue.cpp51
-rw-r--r--graphics/java/android/graphics/BLASTBufferQueue.java18
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);
+ }
}