Merge "Implement Java Choreographer multi frame timeline."

This commit is contained in:
Rachel Lee
2022-01-18 20:30:18 +00:00
committed by Android (Google) Code Review
4 changed files with 314 additions and 32 deletions

View File

@@ -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();

View File

@@ -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();
@@ -483,6 +491,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.
*
@@ -572,6 +598,21 @@ public final class Choreographer {
removeCallbacksInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN);
}
/**
* 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>
@@ -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 {

View File

@@ -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.

View File

@@ -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;
}