Loading core/java/android/view/Choreographer.java +154 −3 Original line number Diff line number Diff line Loading @@ -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") Loading @@ -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 Loading Loading @@ -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); Loading @@ -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; Loading @@ -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; Loading @@ -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; } Loading @@ -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; } Loading core/java/android/view/DisplayEventReceiver.java +6 −1 Original line number Diff line number Diff line Loading @@ -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++) { Loading @@ -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) { Loading @@ -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() { Loading core/java/android/view/SurfaceControl.java +25 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading core/java/android/view/ViewRootImpl.java +3 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading core/java/android/view/flags/view_flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -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
core/java/android/view/Choreographer.java +154 −3 Original line number Diff line number Diff line Loading @@ -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") Loading @@ -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 Loading Loading @@ -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); Loading @@ -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; Loading @@ -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; Loading @@ -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; } Loading @@ -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; } Loading
core/java/android/view/DisplayEventReceiver.java +6 −1 Original line number Diff line number Diff line Loading @@ -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++) { Loading @@ -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) { Loading @@ -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() { Loading
core/java/android/view/SurfaceControl.java +25 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading
core/java/android/view/ViewRootImpl.java +3 −0 Original line number Diff line number Diff line Loading @@ -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) { Loading
core/java/android/view/flags/view_flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -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