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

Commit 2c44495a authored by Melody Hsu's avatar Melody Hsu Committed by Android (Google) Code Review
Browse files

Merge "Recover from buffer stuffing for canned animations" into main

parents 9b603e65 a3f13550
Loading
Loading
Loading
Loading
+154 −3
Original line number Diff line number Diff line
@@ -189,6 +189,11 @@ public final class Choreographer {
    @UnsupportedAppUsage
    private long mLastFrameTimeNanos;

    // Keeps track of the last scheduled frame time without additional offsets
    // added from buffer stuffing recovery. Used to compare timing of vsyncs to
    // determine idle state.
    private long mLastNoOffsetFrameTimeNanos;

    /** DO NOT USE since this will not updated when screen refresh changes. */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
            publicAlternatives = "Use {@link android.view.Display#getRefreshRate} instead")
@@ -203,6 +208,50 @@ public final class Choreographer {
    private final FrameData mFrameData = new FrameData();
    private volatile boolean mInDoFrameCallback = false;

    private static class BufferStuffingData {
        enum RecoveryAction {
            // No recovery
            NONE,
            // Recovery has started, adds a negative offset
            OFFSET,
            // Recovery has started, delays a frame to return buffer count
            // 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;

        // 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.
        public int numberWaitsForNextVsync = 0;

        /**
         * After buffer stuffing recovery has ended with a detected idle state, the
         * recovery data trackers can be reset in preparation for any future
         * stuffing events.
         */
        public void reset() {
            isRecovering = false;
            numberFrameDelays = 0;
            numberWaitsForNextVsync = 0;
        }
    }

    private final BufferStuffingData mBufferStuffingData = new BufferStuffingData();

    /**
     * Contains information about the current frame for jank-tracking,
     * mainly timings of key events along with a bit of metadata about
@@ -850,13 +899,99 @@ public final class Choreographer {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }

    // 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,
            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 (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 =
                    (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos;

            // 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;
            }
        }
        return BufferStuffingData.RecoveryAction.NONE;
    }

    void doFrame(long frameTimeNanos, int frame,
            DisplayEventReceiver.VsyncEventData vsyncEventData) {
        final long startNanos;
        final long frameIntervalNanos = vsyncEventData.frameInterval;
        boolean resynced = false;
        long offsetFrameTimeNanos = frameTimeNanos;

        // Evaluate if buffer stuffing recovery needs to start or end, and
        // what actions need to be taken for recovery.
        switch (checkBufferStuffingRecovery(frameTimeNanos, vsyncEventData)) {
            case NONE:
                // Without buffer stuffing recovery, offsetFrameTimeNanos is
                // synonymous with frameTimeNanos.
                break;
            case OFFSET:
                // Add animation offset. Used to update frame timeline with
                // offset before jitter is calculated.
                offsetFrameTimeNanos = frameTimeNanos - frameIntervalNanos;
                break;
            case DELAY_FRAME:
                // Intentional frame delay to help restore queued buffer count to threshold.
                return;
            default:
                break;
        }

        try {
            FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);
            FrameTimeline timeline = mFrameData.update(offsetFrameTimeNanos, vsyncEventData);
            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                Trace.traceBegin(
                        Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId);
@@ -867,15 +1002,18 @@ public final class Choreographer {
                    traceMessage("Frame not scheduled");
                    return; // no work to do
                }
                mLastNoOffsetFrameTimeNanos = frameTimeNanos;

                if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
                    mDebugPrintNextFrameTimeDelta = false;
                    Log.d(TAG, "Frame time delta: "
                            + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
                            + ((offsetFrameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
                }

                long intendedFrameTimeNanos = frameTimeNanos;
                long intendedFrameTimeNanos = offsetFrameTimeNanos;
                startNanos = System.nanoTime();
                // Calculating jitter involves using the original frame time without
                // adjustments from buffer stuffing
                final long jitterNanos = startNanos - frameTimeNanos;
                if (jitterNanos >= frameIntervalNanos) {
                    frameTimeNanos = startNanos;
@@ -899,6 +1037,13 @@ public final class Choreographer {
                                    + " ms in the past.");
                        }
                    }
                    if (mBufferStuffingData.isRecovering) {
                        frameTimeNanos -= frameIntervalNanos;
                        if (DEBUG_JANK) {
                            Log.d(TAG, "Adjusted animation timeline with a negative offset after"
                                    + " jitter calculation");
                        }
                    }
                    timeline = mFrameData.update(
                            frameTimeNanos, mDisplayEventReceiver, jitterNanos);
                    resynced = true;
@@ -910,6 +1055,9 @@ public final class Choreographer {
                                + "previously skipped frame.  Waiting for next vsync.");
                    }
                    traceMessage("Frame time goes backward");
                    if (mBufferStuffingData.isRecovering) {
                        mBufferStuffingData.numberWaitsForNextVsync++;
                    }
                    scheduleVsyncLocked();
                    return;
                }
@@ -918,6 +1066,9 @@ 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++;
                        }
                        scheduleVsyncLocked();
                        return;
                    }
+6 −1
Original line number Diff line number Diff line
@@ -207,6 +207,8 @@ public abstract class DisplayEventReceiver {
        // reasonable timestamps.
        public int frameTimelinesLength = 1;

        public int numberQueuedBuffers = 0;

        VsyncEventData() {
            frameTimelines = new FrameTimeline[FRAME_TIMELINES_CAPACITY];
            for (int i = 0; i < frameTimelines.length; i++) {
@@ -217,11 +219,13 @@ public abstract class DisplayEventReceiver {
        // Called from native code.
        @SuppressWarnings("unused")
        VsyncEventData(FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex,
                int frameTimelinesLength, long frameInterval) {
                int frameTimelinesLength, long frameInterval,
                int numberQueuedBuffers) {
            this.frameTimelines = frameTimelines;
            this.preferredFrameTimelineIndex = preferredFrameTimelineIndex;
            this.frameTimelinesLength = frameTimelinesLength;
            this.frameInterval = frameInterval;
            this.numberQueuedBuffers = numberQueuedBuffers;
        }

        void copyFrom(VsyncEventData other) {
@@ -231,6 +235,7 @@ public abstract class DisplayEventReceiver {
            for (int i = 0; i < frameTimelines.length; i++) {
                frameTimelines[i].copyFrom(other.frameTimelines[i]);
            }
            numberQueuedBuffers = other.numberQueuedBuffers;
        }

        public FrameTimeline preferredFrameTimeline() {
+25 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.graphics.Matrix.MSKEW_X;
import static android.graphics.Matrix.MSKEW_Y;
import static android.graphics.Matrix.MTRANS_X;
import static android.graphics.Matrix.MTRANS_Y;
import static android.view.flags.Flags.bufferStuffingRecovery;
import static android.view.SurfaceControlProto.HASH_CODE;
import static android.view.SurfaceControlProto.LAYER_ID;
import static android.view.SurfaceControlProto.NAME;
@@ -662,6 +663,13 @@ public final class SurfaceControl implements Parcelable {
     */
    public static final int CAN_OCCLUDE_PRESENTATION = 0x00001000;

    /**
     * Indicates that the SurfaceControl should recover from buffer stuffing when
     * possible. This is the case when the SurfaceControl is a ViewRootImpl.
     * @hide
     */
    public static final int RECOVERABLE_FROM_BUFFER_STUFFING = 0x00002000;

    /**
     * Surface creation flag: Creates a surface where color components are interpreted
     * as "non pre-multiplied" by their alpha channel. Of course this flag is
@@ -4867,6 +4875,23 @@ public final class SurfaceControl implements Parcelable {
            nativeSetDesiredPresentTimeNanos(mNativeObject, desiredPresentTimeNanos);
            return this;
        }

        /**
         * Specifies that the SurfaceControl is a buffer producer that should recover from buffer
         * stuffing, meaning that the SurfaceControl is a ViewRootImpl.
         *
         * @hide
         */
        @NonNull
        public Transaction setRecoverableFromBufferStuffing(@NonNull SurfaceControl sc) {
            if (bufferStuffingRecovery()) {
                checkPreconditions(sc);
                nativeSetFlags(mNativeObject, sc.mNativeObject, RECOVERABLE_FROM_BUFFER_STUFFING,
                        RECOVERABLE_FROM_BUFFER_STUFFING);
            }
            return this;
        }

        /**
         * Writes the transaction to parcel, clearing the transaction as if it had been applied so
         * it can be used to store future transactions. It's the responsibility of the parcel
+3 −0
Original line number Diff line number Diff line
@@ -2758,6 +2758,9 @@ public final class ViewRootImpl implements ViewParent,
        // Only call transferFrom if the surface has changed to prevent inc the generation ID and
        // causing EGL resources to be recreated.
        mSurface.transferFrom(blastSurface);
        // Since the SurfaceControl is a VRI, indicate that it can recover from buffer stuffing.
        mTransaction.setRecoverableFromBufferStuffing(mSurfaceControl).applyAsyncUnsafe();
    }
    private void setBoundsLayerCrop(Transaction t) {
+8 −0
Original line number Diff line number Diff line
@@ -133,3 +133,11 @@ flag {
    bug: "333417898"
    is_fixed_read_only: true
}

flag {
    name: "buffer_stuffing_recovery"
    namespace: "window_surfaces"
    description: "Recover from buffer stuffing when SurfaceFlinger misses a frame"
    bug: "294922229"
    is_fixed_read_only: true
}
 No newline at end of file
Loading