diff options
| author | 2022-01-18 20:30:18 +0000 | |
|---|---|---|
| committer | 2022-01-18 20:30:18 +0000 | |
| commit | 34f56bf78adbd20dbe779e6804af1ad5fd821c70 (patch) | |
| tree | e020754ff93f4bc6fd2f7f228be050642fdfb3b2 | |
| parent | 87c13a545d7d8e5feabb13ca2c396969f0c21369 (diff) | |
| parent | 5af0462a57e7df4aef44f57250c5a3e0ed8514d7 (diff) | |
Merge "Implement Java Choreographer multi frame timeline."
| -rw-r--r-- | core/api/current.txt | 18 | ||||
| -rw-r--r-- | core/java/android/view/Choreographer.java | 212 | ||||
| -rw-r--r-- | core/java/android/view/DisplayEventReceiver.java | 53 | ||||
| -rw-r--r-- | core/jni/android_view_DisplayEventReceiver.cpp | 63 |
4 files changed, 314 insertions, 32 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index ea6ab0ce14a1..280eb8023766 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -48079,15 +48079,33 @@ package android.view { public final class Choreographer { method public static android.view.Choreographer getInstance(); + method public void postExtendedFrameCallback(@NonNull android.view.Choreographer.ExtendedFrameCallback); method public void postFrameCallback(android.view.Choreographer.FrameCallback); method public void postFrameCallbackDelayed(android.view.Choreographer.FrameCallback, long); + method public void removeExtendedFrameCallback(@Nullable android.view.Choreographer.ExtendedFrameCallback); method public void removeFrameCallback(android.view.Choreographer.FrameCallback); } + public static interface Choreographer.ExtendedFrameCallback { + method public void onVsync(@NonNull android.view.Choreographer.FrameData); + } + public static interface Choreographer.FrameCallback { method public void doFrame(long); } + public static class Choreographer.FrameData { + method public long getFrameTimeNanos(); + method @NonNull public android.view.Choreographer.FrameTimeline[] getFrameTimelines(); + method @NonNull public android.view.Choreographer.FrameTimeline getPreferredFrameTimeline(); + } + + public static class Choreographer.FrameTimeline { + method public long getDeadlineNanos(); + method public long getExpectedPresentTimeNanos(); + method public long getVsyncId(); + } + public interface CollapsibleActionView { method public void onActionViewCollapsed(); method public void onActionViewExpanded(); diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index be172f748b55..9b8523f9b006 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -19,6 +19,9 @@ package android.view; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_APP; import static android.view.DisplayEventReceiver.VSYNC_SOURCE_SURFACE_FLINGER; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.FrameInfo; @@ -151,10 +154,15 @@ public final class Choreographer { private static final int MSG_DO_SCHEDULE_VSYNC = 1; private static final int MSG_DO_SCHEDULE_CALLBACK = 2; - // All frame callbacks posted by applications have this token. + // All frame callbacks posted by applications have this token or EXTENDED_FRAME_CALLBACK_TOKEN. private static final Object FRAME_CALLBACK_TOKEN = new Object() { public String toString() { return "FRAME_CALLBACK_TOKEN"; } }; + private static final Object EXTENDED_FRAME_CALLBACK_TOKEN = new Object() { + public String toString() { + return "EXTENDED_FRAME_CALLBACK_TOKEN"; + } + }; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final Object mLock = new Object(); @@ -484,6 +492,24 @@ public final class Choreographer { } /** + * Posts an extended frame callback to run on the next frame. + * <p> + * The callback runs once then is automatically removed. + * </p> + * + * @param callback The extended frame callback to run during the next frame. + * + * @see #removeExtendedFrameCallback + */ + public void postExtendedFrameCallback(@NonNull ExtendedFrameCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, EXTENDED_FRAME_CALLBACK_TOKEN, 0); + } + + /** * Removes callbacks that have the specified action and token. * * @param callbackType The callback type. @@ -573,6 +599,21 @@ public final class Choreographer { } /** + * Removes a previously posted extended frame callback. + * + * @param callback The extended frame callback to remove. + * + * @see #postExtendedFrameCallback + */ + public void removeExtendedFrameCallback(@Nullable ExtendedFrameCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("callback must not be null"); + } + + removeCallbacksInternal(CALLBACK_ANIMATION, callback, EXTENDED_FRAME_CALLBACK_TOKEN); + } + + /** * Gets the time when the current frame started. * <p> * This method provides the time in milliseconds when the frame started being rendered. @@ -673,7 +714,7 @@ public final class Choreographer { * @hide */ public long getVsyncId() { - return mLastVsyncEventData.id; + return mLastVsyncEventData.preferredFrameTimeline().vsyncId; } /** @@ -684,7 +725,7 @@ public final class Choreographer { * @hide */ public long getFrameDeadline() { - return mLastVsyncEventData.frameDeadline; + return mLastVsyncEventData.preferredFrameTimeline().deadline; } void setFPSDivisor(int divisor) { @@ -705,8 +746,9 @@ public final class Choreographer { try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "Choreographer#doFrame " + vsyncEventData.id); + "Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId); } + FrameData frameData = new FrameData(frameTimeNanos, vsyncEventData); synchronized (mLock) { if (!mFrameScheduled) { traceMessage("Frame not scheduled"); @@ -737,6 +779,7 @@ public final class Choreographer { + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); } frameTimeNanos = startNanos - lastFrameOffset; + frameData.setFrameTimeNanos(-lastFrameOffset); } if (frameTimeNanos < mLastFrameTimeNanos) { @@ -758,8 +801,10 @@ public final class Choreographer { } } - mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id, - vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval); + mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, + vsyncEventData.preferredFrameTimeline().vsyncId, + vsyncEventData.preferredFrameTimeline().deadline, startNanos, + vsyncEventData.frameInterval); mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; mLastFrameIntervalNanos = frameIntervalNanos; @@ -769,17 +814,17 @@ public final class Choreographer { AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); - doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_INPUT, frameData, frameIntervalNanos); mFrameInfo.markAnimationsStart(); - doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos); - doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos, + doCallbacks(Choreographer.CALLBACK_ANIMATION, frameData, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameData, frameIntervalNanos); mFrameInfo.markPerformTraversalsStart(); - doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameData, frameIntervalNanos); - doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos); + doCallbacks(Choreographer.CALLBACK_COMMIT, frameData, frameIntervalNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); @@ -793,8 +838,9 @@ public final class Choreographer { } } - void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) { + void doCallbacks(int callbackType, FrameData frameData, long frameIntervalNanos) { CallbackRecord callbacks; + long frameTimeNanos = frameData.mFrameTimeNanos; synchronized (mLock) { // We use "now" to determine when callbacks become due because it's possible // for earlier processing phases in a frame to post callbacks that should run @@ -831,6 +877,7 @@ public final class Choreographer { } frameTimeNanos = now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; + frameData.setFrameTimeNanos(frameTimeNanos); } } } @@ -842,7 +889,7 @@ public final class Choreographer { + ", action=" + c.action + ", token=" + c.token + ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime)); } - c.run(frameTimeNanos); + c.run(frameData); } } finally { synchronized (mLock) { @@ -942,6 +989,130 @@ public final class Choreographer { public void doFrame(long frameTimeNanos); } + /** Holds data that describes one possible VSync frame event to render at. */ + public static class FrameTimeline { + static final FrameTimeline INVALID_FRAME_TIMELINE = new FrameTimeline( + FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE); + + FrameTimeline(long vsyncId, long expectedPresentTimeNanos, long deadlineNanos) { + this.mVsyncId = vsyncId; + this.mExpectedPresentTimeNanos = expectedPresentTimeNanos; + this.mDeadlineNanos = deadlineNanos; + } + + private long mVsyncId; + private long mExpectedPresentTimeNanos; + private long mDeadlineNanos; + + /** + * The id that corresponds to this frame timeline, used to correlate a frame + * produced by HWUI with the timeline data stored in Surface Flinger. + */ + public long getVsyncId() { + return mVsyncId; + } + + /** Sets the vsync ID. */ + void resetVsyncId() { + mVsyncId = FrameInfo.INVALID_VSYNC_ID; + } + + /** + * The time in {@link System#nanoTime()} timebase which this frame is expected to be + * presented. + */ + public long getExpectedPresentTimeNanos() { + return mExpectedPresentTimeNanos; + } + + /** + * The time in {@link System#nanoTime()} timebase which this frame needs to be ready by. + */ + public long getDeadlineNanos() { + return mDeadlineNanos; + } + } + + /** + * The payload for {@link ExtendedFrameCallback} which includes frame information such as when + * the frame started being rendered, and multiple possible frame timelines and their + * information including deadline and expected present time. + */ + public static class FrameData { + static final FrameTimeline[] INVALID_FRAME_TIMELINES = new FrameTimeline[0]; + FrameData() { + this.mFrameTimelines = INVALID_FRAME_TIMELINES; + this.mPreferredFrameTimeline = FrameTimeline.INVALID_FRAME_TIMELINE; + } + + FrameData(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) { + FrameTimeline[] frameTimelines = + new FrameTimeline[vsyncEventData.frameTimelines.length]; + for (int i = 0; i < vsyncEventData.frameTimelines.length; i++) { + DisplayEventReceiver.VsyncEventData.FrameTimeline frameTimeline = + vsyncEventData.frameTimelines[i]; + frameTimelines[i] = new FrameTimeline(frameTimeline.vsyncId, + frameTimeline.expectedPresentTime, frameTimeline.deadline); + } + this.mFrameTimeNanos = frameTimeNanos; + this.mFrameTimelines = frameTimelines; + this.mPreferredFrameTimeline = + frameTimelines[vsyncEventData.preferredFrameTimelineIndex]; + } + + private long mFrameTimeNanos; + private final FrameTimeline[] mFrameTimelines; + private final FrameTimeline mPreferredFrameTimeline; + + void setFrameTimeNanos(long frameTimeNanos) { + mFrameTimeNanos = frameTimeNanos; + for (FrameTimeline ft : mFrameTimelines) { + // The ID is no longer valid because the frame time that was registered with the ID + // no longer matches. + // TODO(b/205721584): Ask SF for valid vsync information. + ft.resetVsyncId(); + } + } + + /** The time in nanoseconds when the frame started being rendered. */ + public long getFrameTimeNanos() { + return mFrameTimeNanos; + } + + /** The possible frame timelines, sorted chronologically. */ + @NonNull + @SuppressLint("ArrayReturn") // For API consistency and speed. + public FrameTimeline[] getFrameTimelines() { + return mFrameTimelines; + } + + /** The platform-preferred frame timeline. */ + @NonNull + public FrameTimeline getPreferredFrameTimeline() { + return mPreferredFrameTimeline; + } + } + + /** + * Implement this interface to receive a callback to start the next frame. The callback is + * invoked on the {@link Looper} thread to which the {@link Choreographer} is attached. The + * callback payload contains information about multiple possible frames, allowing choice of + * the appropriate frame based on latency requirements. + * + * @see FrameCallback + */ + public interface ExtendedFrameCallback { + /** + * Called when a new display frame is being rendered. + * + * @param data The payload which includes frame information. Divide nanosecond values by + * {@code 1000000} to convert it to the {@link SystemClock#uptimeMillis()} + * time base. + * @see FrameCallback#doFrame + **/ + void onVsync(@NonNull FrameData data); + } + private final class FrameHandler extends Handler { public FrameHandler(Looper looper) { super(looper); @@ -983,7 +1154,8 @@ public final class Choreographer { try { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "Choreographer#onVsync " + vsyncEventData.id); + "Choreographer#onVsync " + + vsyncEventData.preferredFrameTimeline().vsyncId); } // Post the vsync event to the Handler. // The idea is to prevent incoming vsync events from completely starving @@ -1026,7 +1198,9 @@ public final class Choreographer { private static final class CallbackRecord { public CallbackRecord next; public long dueTime; - public Object action; // Runnable or FrameCallback + /** Runnable or FrameCallback or ExtendedFrameCallback object. */ + public Object action; + /** Denotes the action type. */ public Object token; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1037,6 +1211,14 @@ public final class Choreographer { ((Runnable)action).run(); } } + + void run(FrameData frameData) { + if (token == EXTENDED_FRAME_CALLBACK_TOKEN) { + ((ExtendedFrameCallback) action).onVsync(frameData); + } else { + run(frameData.getFrameTimeNanos()); + } + } } private final class CallbackQueue { diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index 5c086328bda7..774bab41fb9a 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -138,13 +138,28 @@ public abstract class DisplayEventReceiver { } static final class VsyncEventData { - // The frame timeline vsync id, used to correlate a frame - // produced by HWUI with the timeline data stored in Surface Flinger. - public final long id; - // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is - // allotted for the frame to be completed. - public final long frameDeadline; + static final FrameTimeline[] INVALID_FRAME_TIMELINES = + {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)}; + + public static class FrameTimeline { + FrameTimeline(long vsyncId, long expectedPresentTime, long deadline) { + this.vsyncId = vsyncId; + this.expectedPresentTime = expectedPresentTime; + this.deadline = deadline; + } + + // The frame timeline vsync id, used to correlate a frame + // produced by HWUI with the timeline data stored in Surface Flinger. + public final long vsyncId; + + // The frame timestamp for when the frame is expected to be presented. + public final long expectedPresentTime; + + // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is + // allotted for the frame to be completed. + public final long deadline; + } /** * The current interval between frames in ns. This will be used to align @@ -153,16 +168,27 @@ public abstract class DisplayEventReceiver { */ public final long frameInterval; - VsyncEventData(long id, long frameDeadline, long frameInterval) { - this.id = id; - this.frameDeadline = frameDeadline; + public final FrameTimeline[] frameTimelines; + + public final int preferredFrameTimelineIndex; + + // Called from native code. + @SuppressWarnings("unused") + VsyncEventData(FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex, + long frameInterval) { + this.frameTimelines = frameTimelines; + this.preferredFrameTimelineIndex = preferredFrameTimelineIndex; this.frameInterval = frameInterval; } VsyncEventData() { - this.id = FrameInfo.INVALID_VSYNC_ID; - this.frameDeadline = Long.MAX_VALUE; this.frameInterval = -1; + this.frameTimelines = INVALID_FRAME_TIMELINES; + this.preferredFrameTimelineIndex = 0; + } + + public FrameTimeline preferredFrameTimeline() { + return frameTimelines[preferredFrameTimelineIndex]; } } @@ -256,9 +282,8 @@ public abstract class DisplayEventReceiver { // Called from native code. @SuppressWarnings("unused") private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame, - long frameTimelineVsyncId, long frameDeadline, long frameInterval) { - onVsync(timestampNanos, physicalDisplayId, frame, - new VsyncEventData(frameTimelineVsyncId, frameDeadline, frameInterval)); + VsyncEventData vsyncEventData) { + onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData); } // Called from native code. diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp index ce772cf9faff..d91d526e3d4c 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -48,6 +48,16 @@ static struct { jmethodID init; } frameRateOverrideClassInfo; + struct { + jclass clazz; + jmethodID init; + } frameTimelineClassInfo; + + struct { + jclass clazz; + jmethodID init; + } vsyncEventDataClassInfo; + } gDisplayEventReceiverClassInfo; @@ -105,9 +115,38 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal)); if (receiverObj.get()) { ALOGV("receiver %p ~ Invoking vsync handler.", this); + + ScopedLocalRef<jobjectArray> + frameTimelineObjs(env, + env->NewObjectArray(vsyncEventData.frameTimelines.size(), + gDisplayEventReceiverClassInfo + .frameTimelineClassInfo.clazz, + /*initial element*/ NULL)); + for (int i = 0; i < vsyncEventData.frameTimelines.size(); i++) { + VsyncEventData::FrameTimeline frameTimeline = vsyncEventData.frameTimelines[i]; + ScopedLocalRef<jobject> + frameTimelineObj(env, + env->NewObject(gDisplayEventReceiverClassInfo + .frameTimelineClassInfo.clazz, + gDisplayEventReceiverClassInfo + .frameTimelineClassInfo.init, + frameTimeline.id, + frameTimeline.expectedPresentTime, + frameTimeline.deadlineTimestamp)); + env->SetObjectArrayElement(frameTimelineObjs.get(), i, frameTimelineObj.get()); + } + ScopedLocalRef<jobject> + vsyncEventDataJava(env, + env->NewObject(gDisplayEventReceiverClassInfo + .vsyncEventDataClassInfo.clazz, + gDisplayEventReceiverClassInfo + .vsyncEventDataClassInfo.init, + frameTimelineObjs.get(), + vsyncEventData.preferredFrameTimelineIndex, + vsyncEventData.frameInterval)); + env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync, - timestamp, displayId.value, count, vsyncEventData.id, - vsyncEventData.deadlineTimestamp, vsyncEventData.frameInterval); + timestamp, displayId.value, count, vsyncEventDataJava.get()); ALOGV("receiver %p ~ Returned from vsync handler.", this); } @@ -239,7 +278,7 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { gDisplayEventReceiverClassInfo.dispatchVsync = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", - "(JJIJJJ)V"); + "(JJILandroid/view/DisplayEventReceiver$VsyncEventData;)V"); gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V"); gDisplayEventReceiverClassInfo.dispatchModeChanged = @@ -258,6 +297,24 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameRateOverrideClassInfo.clazz, "<init>", "(IF)V"); + jclass frameTimelineClazz = + FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData$FrameTimeline"); + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz = + MakeGlobalRefOrDie(env, frameTimelineClazz); + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.init = + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, + "<init>", "(JJJ)V"); + + jclass vsyncEventDataClazz = + FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData"); + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz = + MakeGlobalRefOrDie(env, vsyncEventDataClazz); + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.init = + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, + "<init>", + "([Landroid/view/" + "DisplayEventReceiver$VsyncEventData$FrameTimeline;IJ)V"); + return res; } |