Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 08eb790a authored by Melody Hsu's avatar Melody Hsu
Browse files

Recover from buffer stuffing multiple times in 1 animation

Buffer stuffing recovery is limited to 1 recovery attempt
per animation when the app is maximally stuffed. Expand these
limits by allowing multiple recovery attempts to occur within
an animation.

Use SF backdoor command 1045 to inject jank.
Usage: adb shell service call SurfaceFlinger 1045 f 1

Bug: b/441566552
Flag: android.view.flags.buffer_stuffing_multi_recovery
Test: presubmit, perfetto traces when injecting jank
Change-Id: I96996b72586da5b5a5d2c3037f9a0409af9b8634
parent 0f97f746
Loading
Loading
Loading
Loading
+45 −18
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.view;

import static android.view.flags.Flags.bufferStuffingRecovery;
import static android.view.flags.Flags.bufferStuffingMultiRecovery;
import static android.view.flags.Flags.FLAG_EXPECTED_PRESENTATION_TIME_API;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP;
import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER;
@@ -942,13 +943,36 @@ public final class Choreographer {
    // Returns an enum for the recovery action that should be taken in doFrame().
    BufferStuffingState.RecoveryAction updateBufferStuffingState(long frameTimeNanos,
            DisplayEventReceiver.VsyncEventData vsyncEventData) {
        // Multi-recovery allows the app to recover from stuffing multiple times within
        // the same animation. Without multi-recovery, only 1 attempt at recovering from
        // stuffing is attempted when it is first detected in an animation.
        if (bufferStuffingMultiRecovery()) {
            // Canned animations can recover from buffer stuffing whenever the
            // client is blocked on dequeueBuffer.
            if (mBufferStuffingState.isStuffed.getAndSet(false)) {
                // The start of recovery
                if (!mBufferStuffingState.isRecovering) {
                    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                        Trace.asyncTraceForTrackBegin(
                                Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", "Thread "
                                + android.os.Process.myTid() + ", recover frame", 0);
                    }
                    mBufferStuffingState.isRecovering = true;
                }
                Trace.instant(Trace.TRACE_TAG_VIEW, "buffer stuffed");
                return BufferStuffingState.RecoveryAction.DELAY_FRAME;

            // No recovery action needed when there is no buffer stuffing and
            // no recovery currently occurring.
            } else if (!mBufferStuffingState.isRecovering) {
                return BufferStuffingState.RecoveryAction.NONE;
            }
        } else {
            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.
                // 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(
@@ -957,12 +981,14 @@ public final class Choreographer {
                }
                return BufferStuffingState.RecoveryAction.DELAY_FRAME;
            }
        }

        // 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
        // Recovery is actively happening. Continue the recovery or check between every
        // frame if the animations have become idle long enough for recovery to end. The
        // 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.
        final int totalFrameDelays = mBufferStuffingState.numberWaitsForNextVsync + 1;
        final long vsyncsSinceLastCallback = mLastFrameIntervalNanos > 0
                ? (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos : 0;

        // Detected idle state due to a longer inactive period since the last vsync callback
@@ -986,8 +1012,8 @@ public final class Choreographer {
        }
        if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
            Trace.instantForTrack(
                    Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery",
                    "Negative offset added to animation");
                    Trace.TRACE_TAG_VIEW, "Buffer stuffing recovery", "Negative offset of "
                    + vsyncEventData.frameInterval + " ns added to animation");
        }
        return BufferStuffingState.RecoveryAction.OFFSET;
    }
@@ -1017,6 +1043,7 @@ public final class Choreographer {
                    break;
                case DELAY_FRAME:
                    // Intentional frame delay to help reduce queued buffer count.
                    mBufferStuffingState.numberWaitsForNextVsync++;
                    scheduleVsyncLocked();
                    return;
                default: