summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Reck <jreck@google.com>2015-02-19 14:36:50 -0800
committerJohn Reck <jreck@google.com>2015-02-20 08:27:38 -0800
commitba6adf66d3c44c0aa2fd8a224862ff1901d64300 (patch)
tree8172a893f00caa283cf0386dd3d585ca8fac867c
parent004a46eb171bc86a3d40eb8fc6a4d9eed48027c7 (diff)
downloadframeworks_base-ba6adf66d3c44c0aa2fd8a224862ff1901d64300.zip
frameworks_base-ba6adf66d3c44c0aa2fd8a224862ff1901d64300.tar.gz
frameworks_base-ba6adf66d3c44c0aa2fd8a224862ff1901d64300.tar.bz2
Initial attempt at jank-tracking stat collection
Is a bit naive, perhaps overly aggressive, but sorta works Change-Id: I01a774e00dbe681439c02557d9728ae43c45ce50
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/view/Choreographer.java20
-rw-r--r--core/java/android/view/FrameInfo.java118
-rw-r--r--core/java/android/view/HardwareRenderer.java2
-rw-r--r--core/java/android/view/ThreadedRenderer.java77
-rw-r--r--core/java/android/view/ViewRootImpl.java16
-rw-r--r--core/java/android/view/WindowManagerGlobal.java4
-rw-r--r--core/jni/android_view_Surface.cpp10
-rw-r--r--core/jni/android_view_ThreadedRenderer.cpp22
-rw-r--r--libs/hwui/Android.common.mk2
-rw-r--r--libs/hwui/FrameInfo.cpp28
-rw-r--r--libs/hwui/FrameInfo.h116
-rw-r--r--libs/hwui/JankTracker.cpp136
-rw-r--r--libs/hwui/JankTracker.h68
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp46
-rw-r--r--libs/hwui/renderthread/CanvasContext.h26
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp16
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.h9
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp52
-rw-r--r--libs/hwui/renderthread/RenderProxy.h8
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp6
-rw-r--r--libs/hwui/renderthread/RenderThread.h12
-rw-r--r--libs/hwui/renderthread/TimeLord.cpp8
-rw-r--r--libs/hwui/renderthread/TimeLord.h4
-rw-r--r--libs/hwui/tests/main.cpp5
-rw-r--r--libs/hwui/utils/Macros.h8
-rw-r--r--libs/hwui/utils/RingBuffer.h71
27 files changed, 777 insertions, 115 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a8eb076..653b951 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1101,7 +1101,7 @@ public final class ActivityThread {
@Override
public void dumpGfxInfo(FileDescriptor fd, String[] args) {
dumpGraphicsInfo(fd);
- WindowManagerGlobal.getInstance().dumpGfxInfo(fd);
+ WindowManagerGlobal.getInstance().dumpGfxInfo(fd, args);
}
private void dumpDatabaseInfo(FileDescriptor fd, String[] args) {
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index f41afcf..c8149d9 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -141,6 +141,19 @@ public final class Choreographer {
private long mFrameIntervalNanos;
/**
+ * Contains information about the current frame for jank-tracking,
+ * mainly timings of key events along with a bit of metadata about
+ * view tree state
+ *
+ * TODO: Is there a better home for this? Currently Choreographer
+ * is the only one with CALLBACK_ANIMATION start time, hence why this
+ * resides here.
+ *
+ * @hide
+ */
+ FrameInfo mFrameInfo = new FrameInfo();
+
+ /**
* Callback type: Input callback. Runs first.
* @hide
*/
@@ -513,6 +526,7 @@ public final class Choreographer {
return; // no work to do
}
+ long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
@@ -541,12 +555,18 @@ public final class Choreographer {
return;
}
+ mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
+ mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+
+ mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+
+ mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
if (DEBUG) {
diff --git a/core/java/android/view/FrameInfo.java b/core/java/android/view/FrameInfo.java
new file mode 100644
index 0000000..c79547c
--- /dev/null
+++ b/core/java/android/view/FrameInfo.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class that contains all the timing information for the current frame. This
+ * is used in conjunction with the hardware renderer to provide
+ * continous-monitoring jank events
+ *
+ * All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime()
+ *
+ * To minimize overhead from System.nanoTime() calls we infer durations of
+ * things by knowing the ordering of the events. For example, to know how
+ * long layout & measure took it's displayListRecordStart - performTraversalsStart.
+ *
+ * These constants must be kept in sync with FrameInfo.h in libhwui and are
+ * used for indexing into AttachInfo's mFrameInfo long[], which is intended
+ * to be quick to pass down to native via JNI, hence a pre-packed format
+ *
+ * @hide
+ */
+final class FrameInfo {
+
+ long[] mFrameInfo = new long[9];
+
+ // Various flags set to provide extra metadata about the current frame
+ private static final int FLAGS = 0;
+
+ // Is this the first-draw following a window layout?
+ public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
+
+ @IntDef(flag = true, value = {
+ FLAG_WINDOW_LAYOUT_CHANGED })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FrameInfoFlags {}
+
+ // The intended vsync time, unadjusted by jitter
+ private static final int INTENDED_VSYNC = 1;
+
+ // Jitter-adjusted vsync time, this is what was used as input into the
+ // animation & drawing system
+ private static final int VSYNC = 2;
+
+ // The time of the oldest input event
+ private static final int OLDEST_INPUT_EVENT = 3;
+
+ // The time of the newest input event
+ private static final int NEWEST_INPUT_EVENT = 4;
+
+ // When input event handling started
+ private static final int HANDLE_INPUT_START = 5;
+
+ // When animation evaluations started
+ private static final int ANIMATION_START = 6;
+
+ // When ViewRootImpl#performTraversals() started
+ private static final int PERFORM_TRAVERSALS_START = 7;
+
+ // When View:draw() started
+ private static final int DRAW_START = 8;
+
+ public void setVsync(long intendedVsync, long usedVsync) {
+ mFrameInfo[INTENDED_VSYNC] = intendedVsync;
+ mFrameInfo[VSYNC] = usedVsync;
+ mFrameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
+ mFrameInfo[NEWEST_INPUT_EVENT] = 0;
+ mFrameInfo[FLAGS] = 0;
+ }
+
+ public void updateInputEventTime(long inputEventTime, long inputEventOldestTime) {
+ if (inputEventOldestTime < mFrameInfo[OLDEST_INPUT_EVENT]) {
+ mFrameInfo[OLDEST_INPUT_EVENT] = inputEventOldestTime;
+ }
+ if (inputEventTime > mFrameInfo[NEWEST_INPUT_EVENT]) {
+ mFrameInfo[NEWEST_INPUT_EVENT] = inputEventTime;
+ }
+ }
+
+ public void markInputHandlingStart() {
+ mFrameInfo[HANDLE_INPUT_START] = System.nanoTime();
+ }
+
+ public void markAnimationsStart() {
+ mFrameInfo[ANIMATION_START] = System.nanoTime();
+ }
+
+ public void markPerformTraversalsStart() {
+ mFrameInfo[PERFORM_TRAVERSALS_START] = System.nanoTime();
+ }
+
+ public void markDrawStart() {
+ mFrameInfo[DRAW_START] = System.nanoTime();
+ }
+
+ public void addFlags(@FrameInfoFlags long flags) {
+ mFrameInfo[FLAGS] |= flags;
+ }
+
+}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index c5c3f83..aa61885 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -278,7 +278,7 @@ public abstract class HardwareRenderer {
/**
* Outputs extra debugging information in the specified file descriptor.
*/
- abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd);
+ abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args);
/**
* Loads system properties used by the renderer. This method is invoked
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index ad4a048..df0838f 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -16,8 +16,7 @@
package android.view;
-import com.android.internal.R;
-
+import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -27,16 +26,18 @@ import android.graphics.drawable.Drawable;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.os.Trace;
import android.util.Log;
import android.util.LongSparseArray;
-import android.util.TimeUtils;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
+import com.android.internal.R;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashSet;
@@ -74,6 +75,14 @@ public class ThreadedRenderer extends HardwareRenderer {
PROFILE_PROPERTY_VISUALIZE_BARS,
};
+ private static final int FLAG_DUMP_FRAMESTATS = 1 << 0;
+ private static final int FLAG_DUMP_RESET = 1 << 1;
+
+ @IntDef(flag = true, value = {
+ FLAG_DUMP_FRAMESTATS, FLAG_DUMP_RESET })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DumpFlags {}
+
// Size of the rendered content.
private int mWidth, mHeight;
@@ -93,12 +102,12 @@ public class ThreadedRenderer extends HardwareRenderer {
private final float mLightRadius;
private final int mAmbientShadowAlpha;
private final int mSpotShadowAlpha;
+ private final float mDensity;
private long mNativeProxy;
private boolean mInitialized = false;
private RenderNode mRootNode;
private Choreographer mChoreographer;
- private boolean mProfilingEnabled;
private boolean mRootNodeNeedsUpdate;
ThreadedRenderer(Context context, boolean translucent) {
@@ -110,6 +119,7 @@ public class ThreadedRenderer extends HardwareRenderer {
(int) (255 * a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0) + 0.5f);
mSpotShadowAlpha = (int) (255 * a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0) + 0.5f);
a.recycle();
+ mDensity = context.getResources().getDisplayMetrics().density;
long rootNodePtr = nCreateRootRenderNode();
mRootNode = RenderNode.adopt(rootNodePtr);
@@ -214,7 +224,7 @@ public class ThreadedRenderer extends HardwareRenderer {
mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight,
lightX, mLightY, mLightZ, mLightRadius,
- mAmbientShadowAlpha, mSpotShadowAlpha);
+ mAmbientShadowAlpha, mSpotShadowAlpha, mDensity);
}
@Override
@@ -233,32 +243,25 @@ public class ThreadedRenderer extends HardwareRenderer {
}
@Override
- void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) {
+ void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args) {
pw.flush();
- nDumpProfileInfo(mNativeProxy, fd);
- }
-
- private static int search(String[] values, String value) {
- for (int i = 0; i < values.length; i++) {
- if (values[i].equals(value)) return i;
+ int flags = 0;
+ for (int i = 0; i < args.length; i++) {
+ switch (args[i]) {
+ case "framestats":
+ flags |= FLAG_DUMP_FRAMESTATS;
+ break;
+ case "reset":
+ flags |= FLAG_DUMP_RESET;
+ break;
+ }
}
- return -1;
- }
-
- private static boolean checkIfProfilingRequested() {
- String profiling = SystemProperties.get(HardwareRenderer.PROFILE_PROPERTY);
- int graphType = search(VISUALIZERS, profiling);
- return (graphType >= 0) || Boolean.parseBoolean(profiling);
+ nDumpProfileInfo(mNativeProxy, fd, flags);
}
@Override
boolean loadSystemProperties() {
boolean changed = nLoadSystemProperties(mNativeProxy);
- boolean wantProfiling = checkIfProfilingRequested();
- if (wantProfiling != mProfilingEnabled) {
- mProfilingEnabled = wantProfiling;
- changed = true;
- }
if (changed) {
invalidateRoot();
}
@@ -307,20 +310,12 @@ public class ThreadedRenderer extends HardwareRenderer {
@Override
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
attachInfo.mIgnoreDirtyState = true;
- long frameTimeNanos = mChoreographer.getFrameTimeNanos();
- attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS;
- long recordDuration = 0;
- if (mProfilingEnabled) {
- recordDuration = System.nanoTime();
- }
+ final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
+ choreographer.mFrameInfo.markDrawStart();
updateRootDisplayList(view, callbacks);
- if (mProfilingEnabled) {
- recordDuration = System.nanoTime() - recordDuration;
- }
-
attachInfo.mIgnoreDirtyState = false;
// register animating rendernodes which started animating prior to renderer
@@ -337,8 +332,8 @@ public class ThreadedRenderer extends HardwareRenderer {
attachInfo.mPendingAnimatingRenderNodes = null;
}
- int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos,
- recordDuration, view.getResources().getDisplayMetrics().density);
+ final long[] frameInfo = choreographer.mFrameInfo.mFrameInfo;
+ int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
setEnabled(false);
attachInfo.mViewRootImpl.mSurface.release();
@@ -500,10 +495,9 @@ public class ThreadedRenderer extends HardwareRenderer {
private static native boolean nPauseSurface(long nativeProxy, Surface window);
private static native void nSetup(long nativeProxy, int width, int height,
float lightX, float lightY, float lightZ, float lightRadius,
- int ambientShadowAlpha, int spotShadowAlpha);
+ int ambientShadowAlpha, int spotShadowAlpha, float density);
private static native void nSetOpaque(long nativeProxy, boolean opaque);
- private static native int nSyncAndDrawFrame(long nativeProxy,
- long frameTimeNanos, long recordDuration, float density);
+ private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
private static native void nDestroy(long nativeProxy);
private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode);
@@ -523,5 +517,6 @@ public class ThreadedRenderer extends HardwareRenderer {
private static native void nStopDrawing(long nativeProxy);
private static native void nNotifyFramePending(long nativeProxy);
- private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd);
+ private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
+ @DumpFlags int dumpFlags);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 24fae8a..87fe216 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -56,6 +56,7 @@ import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
+import android.util.TimeUtils;
import android.util.TypedValue;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
@@ -1513,6 +1514,7 @@ public final class ViewRootImpl implements ViewParent,
// to resume them
mDirty.set(0, 0, mWidth, mHeight);
}
+ mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
}
final int surfaceGenerationId = mSurface.getGenerationId();
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
@@ -2516,6 +2518,9 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ mAttachInfo.mDrawingTime =
+ mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
+
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
// If accessibility focus moved, always invalidate the root.
@@ -2635,7 +2640,6 @@ public final class ViewRootImpl implements ViewParent,
dirty.setEmpty();
mIsAnimating = false;
- attachInfo.mDrawingTime = SystemClock.uptimeMillis();
mView.mPrivateFlags |= View.PFLAG_DRAWN;
if (DEBUG_DRAW) {
@@ -5789,6 +5793,16 @@ public final class ViewRootImpl implements ViewParent,
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
+ long eventTime = q.mEvent.getEventTimeNano();
+ long oldestEventTime = eventTime;
+ if (q.mEvent instanceof MotionEvent) {
+ MotionEvent me = (MotionEvent)q.mEvent;
+ if (me.getHistorySize() > 0) {
+ oldestEventTime = me.getHistoricalEventTimeNano(0);
+ }
+ }
+ mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
+
deliverInputEvent(q);
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 279627a..1cebe3f 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -492,7 +492,7 @@ public final class WindowManagerGlobal {
}
}
- public void dumpGfxInfo(FileDescriptor fd) {
+ public void dumpGfxInfo(FileDescriptor fd, String[] args) {
FileOutputStream fout = new FileOutputStream(fd);
PrintWriter pw = new FastPrintWriter(fout);
try {
@@ -509,7 +509,7 @@ public final class WindowManagerGlobal {
HardwareRenderer renderer =
root.getView().mAttachInfo.mHardwareRenderer;
if (renderer != null) {
- renderer.dumpGfxInfo(pw, fd);
+ renderer.dumpGfxInfo(pw, fd, args);
}
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 7f6c50f..bfa0534 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -49,6 +49,7 @@
#include <AnimationContext.h>
#include <DisplayListRenderer.h>
+#include <FrameInfo.h>
#include <RenderNode.h>
#include <renderthread/RenderProxy.h>
@@ -394,7 +395,7 @@ static jlong create(JNIEnv* env, jclass clazz, jlong rootNodePtr, jlong surfaceP
proxy->initialize(surface);
// Shadows can't be used via this interface, so just set the light source
// to all 0s. (and width & height are unused, TODO remove them)
- proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0);
+ proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0, 1.0f);
return (jlong) proxy;
}
@@ -406,8 +407,11 @@ static void setSurface(JNIEnv* env, jclass clazz, jlong rendererPtr, jlong surfa
static void draw(JNIEnv* env, jclass clazz, jlong rendererPtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(rendererPtr);
- nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC);
- proxy->syncAndDrawFrame(frameTimeNs, 0, 1.0f);
+ nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+ UiFrameInfoBuilder(proxy->frameInfo())
+ .setVsync(vsync, vsync)
+ .addFlag(FrameInfoFlags::kSurfaceCanvas);
+ proxy->syncAndDrawFrame();
}
static void destroy(JNIEnv* env, jclass clazz, jlong rendererPtr) {
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 068b24e..ad93301 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -281,10 +281,10 @@ static jboolean android_view_ThreadedRenderer_pauseSurface(JNIEnv* env, jobject
static void android_view_ThreadedRenderer_setup(JNIEnv* env, jobject clazz, jlong proxyPtr,
jint width, jint height,
jfloat lightX, jfloat lightY, jfloat lightZ, jfloat lightRadius,
- jint ambientShadowAlpha, jint spotShadowAlpha) {
+ jint ambientShadowAlpha, jint spotShadowAlpha, jfloat density) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
proxy->setup(width, height, (Vector3){lightX, lightY, lightZ}, lightRadius,
- ambientShadowAlpha, spotShadowAlpha);
+ ambientShadowAlpha, spotShadowAlpha, density);
}
static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz,
@@ -294,9 +294,13 @@ static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz,
}
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density) {
+ jlong proxyPtr, jlongArray frameInfo, jint frameInfoSize) {
+ LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
+ "Mismatched size expectations, given %d expected %d",
+ frameInfoSize, UI_THREAD_FRAME_INFO_SIZE);
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density);
+ env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
+ return proxy->syncAndDrawFrame();
}
static void android_view_ThreadedRenderer_destroy(JNIEnv* env, jobject clazz,
@@ -391,10 +395,10 @@ static void android_view_ThreadedRenderer_notifyFramePending(JNIEnv* env, jobjec
}
static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject clazz,
- jlong proxyPtr, jobject javaFileDescriptor) {
+ jlong proxyPtr, jobject javaFileDescriptor, jint dumpFlags) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
- proxy->dumpProfileInfo(fd);
+ proxy->dumpProfileInfo(fd, dumpFlags);
}
// ----------------------------------------------------------------------------
@@ -425,9 +429,9 @@ static JNINativeMethod gMethods[] = {
{ "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
{ "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
{ "nPauseSurface", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_pauseSurface },
- { "nSetup", "(JIIFFFFII)V", (void*) android_view_ThreadedRenderer_setup },
+ { "nSetup", "(JIIFFFFIIF)V", (void*) android_view_ThreadedRenderer_setup },
{ "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque },
- { "nSyncAndDrawFrame", "(JJJF)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
+ { "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
{ "nDestroy", "(J)V", (void*) android_view_ThreadedRenderer_destroy },
{ "nRegisterAnimatingRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_registerAnimatingRenderNode },
{ "nInvokeFunctor", "(JZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
@@ -442,7 +446,7 @@ static JNINativeMethod gMethods[] = {
{ "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence },
{ "nStopDrawing", "(J)V", (void*) android_view_ThreadedRenderer_stopDrawing },
{ "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending },
- { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
+ { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;I)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo },
{ "setupShadersDiskCache", "(Ljava/lang/String;)V",
(void*) android_view_ThreadedRenderer_setupShadersDiskCache },
};
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk
index e05dd55..5dacbb5 100644
--- a/libs/hwui/Android.common.mk
+++ b/libs/hwui/Android.common.mk
@@ -43,11 +43,13 @@ LOCAL_SRC_FILES := \
Extensions.cpp \
FboCache.cpp \
FontRenderer.cpp \
+ FrameInfo.cpp \
GammaFontRenderer.cpp \
GlopBuilder.cpp \
GradientCache.cpp \
Image.cpp \
Interpolator.cpp \
+ JankTracker.cpp \
Layer.cpp \
LayerCache.cpp \
LayerRenderer.cpp \
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
new file mode 100644
index 0000000..6da1fa8
--- /dev/null
+++ b/libs/hwui/FrameInfo.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "FrameInfo.h"
+
+#include <cstring>
+
+namespace android {
+namespace uirenderer {
+
+void FrameInfo::importUiThreadInfo(int64_t* info) {
+ memcpy(mFrameInfo, info, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
new file mode 100644
index 0000000..3c31677
--- /dev/null
+++ b/libs/hwui/FrameInfo.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef FRAMEINFO_H_
+#define FRAMEINFO_H_
+
+#include "utils/Macros.h"
+
+#include <cutils/compiler.h>
+#include <utils/Timers.h>
+
+#include <memory.h>
+
+namespace android {
+namespace uirenderer {
+
+#define UI_THREAD_FRAME_INFO_SIZE 9
+
+HWUI_ENUM(FrameInfoIndex,
+ kFlags = 0,
+ kIntendedVsync,
+ kVsync,
+ kOldestInputEvent,
+ kNewestInputEvent,
+ kHandleInputStart,
+ kAnimationStart,
+ kPerformTraversalsStart,
+ kDrawStart,
+ // End of UI frame info
+
+ kSyncStart,
+ kIssueDrawCommandsStart,
+ kSwapBuffers,
+ kFrameCompleted,
+
+ // Must be the last value!
+ kNumIndexes
+);
+
+HWUI_ENUM(FrameInfoFlags,
+ kWindowLayoutChanged = 1 << 0,
+ kRTAnimation = 1 << 1,
+ kSurfaceCanvas = 1 << 2,
+);
+
+class ANDROID_API UiFrameInfoBuilder {
+public:
+ UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
+ memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
+ }
+
+ UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync) {
+ mBuffer[FrameInfoIndex::kVsync] = vsyncTime;
+ mBuffer[FrameInfoIndex::kIntendedVsync] = intendedVsync;
+ return *this;
+ }
+
+ UiFrameInfoBuilder& addFlag(FrameInfoFlagsEnum flag) {
+ mBuffer[FrameInfoIndex::kFlags] |= static_cast<uint64_t>(flag);
+ return *this;
+ }
+
+private:
+ int64_t* mBuffer;
+};
+
+class FrameInfo {
+public:
+ void importUiThreadInfo(int64_t* info);
+
+ void markSyncStart() {
+ mFrameInfo[FrameInfoIndex::kSyncStart] = systemTime(CLOCK_MONOTONIC);
+ }
+
+ void markIssueDrawCommandsStart() {
+ mFrameInfo[FrameInfoIndex::kIssueDrawCommandsStart] = systemTime(CLOCK_MONOTONIC);
+ }
+
+ void markSwapBuffers() {
+ mFrameInfo[FrameInfoIndex::kSwapBuffers] = systemTime(CLOCK_MONOTONIC);
+ }
+
+ void markFrameCompleted() {
+ mFrameInfo[FrameInfoIndex::kFrameCompleted] = systemTime(CLOCK_MONOTONIC);
+ }
+
+ int64_t operator[](FrameInfoIndexEnum index) const {
+ if (index == FrameInfoIndex::kNumIndexes) return 0;
+ return mFrameInfo[static_cast<int>(index)];
+ }
+
+ int64_t operator[](int index) const {
+ if (index < 0 || index >= FrameInfoIndex::kNumIndexes) return 0;
+ return mFrameInfo[static_cast<int>(index)];
+ }
+
+private:
+ int64_t mFrameInfo[FrameInfoIndex::kNumIndexes];
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* FRAMEINFO_H_ */
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
new file mode 100644
index 0000000..62cb97c
--- /dev/null
+++ b/libs/hwui/JankTracker.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "JankTracker.h"
+
+#include <cstdio>
+#include <inttypes.h>
+
+namespace android {
+namespace uirenderer {
+
+static const char* JANK_TYPE_NAMES[] = {
+ "Missed Vsync",
+ "High input latency",
+ "Slow UI thread",
+ "Slow bitmap uploads",
+ "Slow draw",
+};
+
+struct Comparison {
+ FrameInfoIndexEnum start;
+ FrameInfoIndexEnum end;
+};
+
+static const Comparison COMPARISONS[] = {
+ {FrameInfoIndex::kIntendedVsync, FrameInfoIndex::kVsync},
+ {FrameInfoIndex::kOldestInputEvent, FrameInfoIndex::kVsync},
+ {FrameInfoIndex::kVsync, FrameInfoIndex::kSyncStart},
+ {FrameInfoIndex::kSyncStart, FrameInfoIndex::kIssueDrawCommandsStart},
+ {FrameInfoIndex::kIssueDrawCommandsStart, FrameInfoIndex::kFrameCompleted},
+};
+
+// If the event exceeds 10 seconds throw it away, this isn't a jank event
+// it's an ANR and will be handled as such
+static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10);
+
+/*
+ * Frames that are exempt from jank metrics.
+ * First-draw frames, for example, are expected to
+ * be slow, this is hidden from the user with window animations and
+ * other tricks
+ *
+ * Similarly, we don't track direct-drawing via Surface:lockHardwareCanvas()
+ * for now
+ *
+ * TODO: kSurfaceCanvas can negatively impact other drawing by using up
+ * time on the RenderThread, figure out how to attribute that as a jank-causer
+ */
+static const int64_t EXEMPT_FRAMES_FLAGS
+ = FrameInfoFlags::kWindowLayoutChanged
+ | FrameInfoFlags::kSurfaceCanvas;
+
+JankTracker::JankTracker(nsecs_t frameIntervalNanos) {
+ reset();
+ setFrameInterval(frameIntervalNanos);
+}
+
+void JankTracker::setFrameInterval(nsecs_t frameInterval) {
+ mFrameInterval = frameInterval;
+ mThresholds[kMissedVsync] = 1;
+ /*
+ * Due to interpolation and sample rate differences between the touch
+ * panel and the display (example, 85hz touch panel driving a 60hz display)
+ * we call high latency 1.5 * frameinterval
+ *
+ * NOTE: Be careful when tuning this! A theoretical 1,000hz touch panel
+ * on a 60hz display will show kOldestInputEvent - kIntendedVsync of being 15ms
+ * Thus this must always be larger than frameInterval, or it will fail
+ */
+ mThresholds[kHighInputLatency] = static_cast<int64_t>(1.5 * frameInterval);
+
+ // Note that these do not add up to 1. This is intentional. It's to deal
+ // with variance in values, and should be sort of an upper-bound on what
+ // is reasonable to expect.
+ mThresholds[kSlowUI] = static_cast<int64_t>(.5 * frameInterval);
+ mThresholds[kSlowSync] = static_cast<int64_t>(.2 * frameInterval);
+ mThresholds[kSlowRT] = static_cast<int64_t>(.75 * frameInterval);
+
+}
+
+void JankTracker::addFrame(const FrameInfo& frame) {
+ using namespace FrameInfoIndex;
+ mTotalFrameCount++;
+ // Fast-path for jank-free frames
+ int64_t totalDuration = frame[kFrameCompleted] - frame[kIntendedVsync];
+ if (CC_LIKELY(totalDuration < mFrameInterval)) {
+ return;
+ }
+
+ if (frame[kFlags] & EXEMPT_FRAMES_FLAGS) {
+ return;
+ }
+
+ mJankFrameCount++;
+
+ for (int i = 0; i < NUM_BUCKETS; i++) {
+ int64_t delta = frame[COMPARISONS[i].end] - frame[COMPARISONS[i].start];
+ if (delta >= mThresholds[i] && delta < IGNORE_EXCEEDING) {
+ mBuckets[i].count++;
+ }
+ }
+}
+
+void JankTracker::dump(int fd) {
+ FILE* file = fdopen(fd, "a");
+ fprintf(file, "\nFrame stats:");
+ fprintf(file, "\n Total frames rendered: %u", mTotalFrameCount);
+ fprintf(file, "\n Janky frames: %u (%.2f%%)", mJankFrameCount,
+ (float) mJankFrameCount / (float) mTotalFrameCount * 100.0f);
+ for (int i = 0; i < NUM_BUCKETS; i++) {
+ fprintf(file, "\n Number %s: %u", JANK_TYPE_NAMES[i], mBuckets[i].count);
+ }
+ fprintf(file, "\n");
+ fflush(file);
+}
+
+void JankTracker::reset() {
+ memset(mBuckets, 0, sizeof(JankBucket) * NUM_BUCKETS);
+ mTotalFrameCount = 0;
+ mJankFrameCount = 0;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
new file mode 100644
index 0000000..aa554cd
--- /dev/null
+++ b/libs/hwui/JankTracker.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef JANKTRACKER_H_
+#define JANKTRACKER_H_
+
+#include "FrameInfo.h"
+#include "renderthread/TimeLord.h"
+#include "utils/RingBuffer.h"
+
+#include <memory>
+
+namespace android {
+namespace uirenderer {
+
+enum JankType {
+ kMissedVsync = 0,
+ kHighInputLatency,
+ kSlowUI,
+ kSlowSync,
+ kSlowRT,
+
+ // must be last
+ NUM_BUCKETS,
+};
+
+struct JankBucket {
+ // Number of frames that hit this bucket
+ uint32_t count;
+};
+
+// TODO: Replace DrawProfiler with this
+class JankTracker {
+public:
+ JankTracker(nsecs_t frameIntervalNanos);
+
+ void setFrameInterval(nsecs_t frameIntervalNanos);
+
+ void addFrame(const FrameInfo& frame);
+
+ void dump(int fd);
+ void reset();
+
+private:
+ JankBucket mBuckets[NUM_BUCKETS];
+ int64_t mThresholds[NUM_BUCKETS];
+
+ int64_t mFrameInterval;
+ uint32_t mTotalFrameCount;
+ uint32_t mJankFrameCount;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* JANKTRACKER_H_ */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 6346479..80c60d9 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -48,7 +48,8 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
, mCanvas(nullptr)
, mHaveNewSurface(false)
, mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
- , mRootRenderNode(rootRenderNode) {
+ , mRootRenderNode(rootRenderNode)
+ , mCurrentFrameInfo(nullptr) {
mRenderThread.renderState().registerCanvasContext(this);
}
@@ -151,9 +152,13 @@ void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) {
}
}
-void CanvasContext::prepareTree(TreeInfo& info) {
+void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo) {
mRenderThread.removeFrameCallback(this);
+ mCurrentFrameInfo = &mFrames.next();
+ mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
+ mCurrentFrameInfo->markSyncStart();
+
info.damageAccumulator = &mDamageAccumulator;
info.renderer = mCanvas;
if (mPrefetechedLayers.size() && info.mode == TreeInfo::MODE_FULL) {
@@ -203,6 +208,7 @@ void CanvasContext::draw() {
"drawRenderNode called on a context with no canvas or surface!");
profiler().markPlaybackStart();
+ mCurrentFrameInfo->markIssueDrawCommandsStart();
SkRect dirty;
mDamageAccumulator.finish(&dirty);
@@ -239,12 +245,19 @@ void CanvasContext::draw() {
profiler().markPlaybackEnd();
+ // Even if we decided to cancel the frame, from the perspective of jank
+ // metrics the frame was swapped at this point
+ mCurrentFrameInfo->markSwapBuffers();
+
if (drew) {
swapBuffers();
} else {
mEglManager.cancelFrame();
}
+ // TODO: Use a fence for real completion?
+ mCurrentFrameInfo->markFrameCompleted();
+ mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
profiler().finishFrame();
}
@@ -257,9 +270,14 @@ void CanvasContext::doFrame() {
ATRACE_CALL();
profiler().startFrame();
+ int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
+ UiFrameInfoBuilder(frameInfo)
+ .addFlag(FrameInfoFlags::kRTAnimation)
+ .setVsync(mRenderThread.timeLord().computeFrameTimeNanos(),
+ mRenderThread.timeLord().latestVsync());
TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState());
- prepareTree(info);
+ prepareTree(info, frameInfo);
if (info.out.canDrawThisFrame) {
draw();
}
@@ -372,6 +390,28 @@ void CanvasContext::setTextureAtlas(RenderThread& thread,
thread.eglManager().setTextureAtlas(buffer, map, mapSize);
}
+void CanvasContext::dumpFrames(int fd) {
+ FILE* file = fdopen(fd, "a");
+ fprintf(file, "\n\n---PROFILEDATA---");
+ for (size_t i = 0; i < mFrames.size(); i++) {
+ FrameInfo& frame = mFrames[i];
+ if (frame[FrameInfoIndex::kSyncStart] == 0) {
+ continue;
+ }
+ fprintf(file, "\n");
+ for (int i = 0; i < FrameInfoIndex::kNumIndexes; i++) {
+ fprintf(file, "%" PRId64 ",", frame[i]);
+ }
+ }
+ fprintf(file, "\n---PROFILEDATA---\n\n");
+ fflush(file);
+}
+
+void CanvasContext::resetFrameStats() {
+ mFrames.clear();
+ mRenderThread.jankTracker().reset();
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index d3fbde8..9a60dc7 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -17,7 +17,14 @@
#ifndef CANVASCONTEXT_H_
#define CANVASCONTEXT_H_
-#include <set>
+#include "DamageAccumulator.h"
+#include "DrawProfiler.h"
+#include "IContextFactory.h"
+#include "FrameInfo.h"
+#include "RenderNode.h"
+#include "utils/RingBuffer.h"
+#include "renderthread/RenderTask.h"
+#include "renderthread/RenderThread.h"
#include <cutils/compiler.h>
#include <EGL/egl.h>
@@ -25,14 +32,7 @@
#include <utils/Functor.h>
#include <utils/Vector.h>
-#include "../DamageAccumulator.h"
-#include "../DrawProfiler.h"
-#include "../IContextFactory.h"
-#include "../RenderNode.h"
-#include "RenderTask.h"
-#include "RenderThread.h"
-
-#define FUNCTOR_PROCESS_DELAY 4
+#include <set>
namespace android {
namespace uirenderer {
@@ -75,7 +75,7 @@ public:
void setOpaque(bool opaque);
void makeCurrent();
void processLayerUpdate(DeferredLayerUpdater* layerUpdater);
- void prepareTree(TreeInfo& info);
+ void prepareTree(TreeInfo& info, int64_t* uiFrameInfo);
void draw();
void destroy();
@@ -103,6 +103,9 @@ public:
DrawProfiler& profiler() { return mProfiler; }
+ void dumpFrames(int fd);
+ void resetFrameStats();
+
private:
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
@@ -133,6 +136,9 @@ private:
const sp<RenderNode> mRootRenderNode;
DrawProfiler mProfiler;
+ FrameInfo* mCurrentFrameInfo;
+ // Ring buffer large enough for 1 second worth of frames
+ RingBuffer<FrameInfo, 60> mFrames;
std::set<RenderNode*> mPrefetechedLayers;
};
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 4d8a469..679a00f 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -34,8 +34,6 @@ namespace renderthread {
DrawFrameTask::DrawFrameTask()
: mRenderThread(nullptr)
, mContext(nullptr)
- , mFrameTimeNanos(0)
- , mRecordDurationNanos(0)
, mDensity(1.0f) // safe enough default
, mSyncResult(kSync_OK) {
}
@@ -68,18 +66,12 @@ void DrawFrameTask::removeLayerUpdate(DeferredLayerUpdater* layer) {
}
}
-int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) {
+int DrawFrameTask::drawFrame() {
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
mSyncResult = kSync_OK;
- mFrameTimeNanos = frameTimeNanos;
- mRecordDurationNanos = recordDurationNanos;
postAndWait();
- // Reset the single-frame data
- mFrameTimeNanos = 0;
- mRecordDurationNanos = 0;
-
return mSyncResult;
}
@@ -93,7 +85,7 @@ void DrawFrameTask::run() {
ATRACE_NAME("DrawFrame");
mContext->profiler().setDensity(mDensity);
- mContext->profiler().startFrame(mRecordDurationNanos);
+ mContext->profiler().startFrame();
bool canUnblockUiThread;
bool canDrawThisFrame;
@@ -122,7 +114,7 @@ void DrawFrameTask::run() {
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
ATRACE_CALL();
- mRenderThread->timeLord().vsyncReceived(mFrameTimeNanos);
+ mRenderThread->timeLord().vsyncReceived(mFrameInfo[FrameInfoIndex::kVsync]);
mContext->makeCurrent();
Caches::getInstance().textureCache.resetMarkInUse();
@@ -130,7 +122,7 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) {
mContext->processLayerUpdate(mLayers[i].get());
}
mLayers.clear();
- mContext->prepareTree(info);
+ mContext->prepareTree(info, mFrameInfo);
// This is after the prepareTree so that any pending operations
// (RenderNode tree state, prefetched layers, etc...) will be flushed.
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 953f012..0e56bea 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -25,6 +25,7 @@
#include "RenderTask.h"
#include "../Rect.h"
+#include "../FrameInfo.h"
#include "../TreeInfo.h"
namespace android {
@@ -62,7 +63,9 @@ public:
void removeLayerUpdate(DeferredLayerUpdater* layer);
void setDensity(float density) { mDensity = density; }
- int drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos);
+ int drawFrame();
+
+ int64_t* frameInfo() { return mFrameInfo; }
virtual void run() override;
@@ -80,12 +83,12 @@ private:
/*********************************************
* Single frame data
*********************************************/
- nsecs_t mFrameTimeNanos;
- nsecs_t mRecordDurationNanos;
float mDensity;
std::vector< sp<DeferredLayerUpdater> > mLayers;
int mSyncResult;
+
+ int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 4dc4248..3a31db0 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -16,14 +16,14 @@
#include "RenderProxy.h"
-#include "CanvasContext.h"
-#include "RenderTask.h"
-#include "RenderThread.h"
-
-#include "../DeferredLayerUpdater.h"
-#include "../DisplayList.h"
-#include "../LayerRenderer.h"
-#include "../Rect.h"
+#include "DeferredLayerUpdater.h"
+#include "DisplayList.h"
+#include "LayerRenderer.h"
+#include "Rect.h"
+#include "renderthread/CanvasContext.h"
+#include "renderthread/RenderTask.h"
+#include "renderthread/RenderThread.h"
+#include "utils/Macros.h"
namespace android {
namespace uirenderer {
@@ -52,6 +52,11 @@ namespace renderthread {
MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
ARGS(method) *args = (ARGS(method) *) task->payload()
+HWUI_ENUM(DumpFlags,
+ kFrameStats = 1 << 0,
+ kReset = 1 << 1,
+);
+
CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory) {
return new CanvasContext(*args->thread, args->translucent,
@@ -92,7 +97,7 @@ void RenderProxy::destroyContext() {
}
CREATE_BRIDGE2(setFrameInterval, RenderThread* thread, nsecs_t frameIntervalNanos) {
- args->thread->timeLord().setFrameInterval(args->frameIntervalNanos);
+ args->thread->setFrameInterval(args->frameIntervalNanos);
return nullptr;
}
@@ -175,7 +180,8 @@ CREATE_BRIDGE7(setup, CanvasContext* context, int width, int height,
}
void RenderProxy::setup(int width, int height, const Vector3& lightCenter, float lightRadius,
- uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density) {
+ mDrawFrameTask.setDensity(density);
SETUP_TASK(setup);
args->context = mContext;
args->width = width;
@@ -199,10 +205,12 @@ void RenderProxy::setOpaque(bool opaque) {
post(task);
}
-int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
- float density) {
- mDrawFrameTask.setDensity(density);
- return mDrawFrameTask.drawFrame(frameTimeNanos, recordDurationNanos);
+int64_t* RenderProxy::frameInfo() {
+ return mDrawFrameTask.frameInfo();
+}
+
+int RenderProxy::syncAndDrawFrame() {
+ return mDrawFrameTask.drawFrame();
}
CREATE_BRIDGE1(destroy, CanvasContext* context) {
@@ -375,19 +383,28 @@ void RenderProxy::notifyFramePending() {
mRenderThread.queueAtFront(task);
}
-CREATE_BRIDGE2(dumpProfileInfo, CanvasContext* context, int fd) {
+CREATE_BRIDGE3(dumpProfileInfo, CanvasContext* context, int fd, int dumpFlags) {
args->context->profiler().dumpData(args->fd);
+ if (args->dumpFlags & DumpFlags::kFrameStats) {
+ args->context->dumpFrames(args->fd);
+ }
+ if (args->dumpFlags & DumpFlags::kReset) {
+ args->context->resetFrameStats();
+ }
return nullptr;
}
-void RenderProxy::dumpProfileInfo(int fd) {
+void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) {
SETUP_TASK(dumpProfileInfo);
args->context = mContext;
args->fd = fd;
+ args->dumpFlags = dumpFlags;
postAndWait(task);
}
-CREATE_BRIDGE1(dumpGraphicsMemory, int fd) {
+CREATE_BRIDGE2(dumpGraphicsMemory, int fd, RenderThread* thread) {
+ args->thread->jankTracker().dump(args->fd);
+
FILE *file = fdopen(args->fd, "a");
if (Caches::hasInstance()) {
String8 cachesLog;
@@ -403,6 +420,7 @@ CREATE_BRIDGE1(dumpGraphicsMemory, int fd) {
void RenderProxy::dumpGraphicsMemory(int fd) {
SETUP_TASK(dumpGraphicsMemory);
args->fd = fd;
+ args->thread = &RenderThread::getInstance();
staticPostAndWait(task);
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index d87e777..19e73e5 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -71,10 +71,10 @@ public:
ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius,
- uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha);
+ uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density);
ANDROID_API void setOpaque(bool opaque);
- ANDROID_API int syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos,
- float density);
+ ANDROID_API int64_t* frameInfo();
+ ANDROID_API int syncAndDrawFrame();
ANDROID_API void destroy();
ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion);
@@ -95,7 +95,7 @@ public:
ANDROID_API void stopDrawing();
ANDROID_API void notifyFramePending();
- ANDROID_API void dumpProfileInfo(int fd);
+ ANDROID_API void dumpProfileInfo(int fd, int dumpFlags);
ANDROID_API static void dumpGraphicsMemory(int fd);
ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 9a0fbad..2a8baa7 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -151,6 +151,11 @@ RenderThread::~RenderThread() {
LOG_ALWAYS_FATAL("Can't destroy the render thread");
}
+void RenderThread::setFrameInterval(nsecs_t frameInterval) {
+ mTimeLord.setFrameInterval(frameInterval);
+ mJankTracker->setFrameInterval(frameInterval);
+}
+
void RenderThread::initializeDisplayEventReceiver() {
LOG_ALWAYS_FATAL_IF(mDisplayEventReceiver, "Initializing a second DisplayEventReceiver?");
mDisplayEventReceiver = new DisplayEventReceiver();
@@ -167,6 +172,7 @@ void RenderThread::initThreadLocals() {
initializeDisplayEventReceiver();
mEglManager = new EglManager(*this);
mRenderState = new RenderState(*this);
+ mJankTracker = new JankTracker(mTimeLord.frameIntervalNanos());
}
int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index 8fc8ca5..f169424 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -19,8 +19,8 @@
#include "RenderTask.h"
-#include <memory>
-#include <set>
+#include "../JankTracker.h"
+#include "TimeLord.h"
#include <cutils/compiler.h>
#include <utils/Looper.h>
@@ -28,7 +28,8 @@
#include <utils/Singleton.h>
#include <utils/Thread.h>
-#include "TimeLord.h"
+#include <memory>
+#include <set>
namespace android {
@@ -85,9 +86,12 @@ public:
// the next vsync. If it is not currently registered this does nothing.
void pushBackFrameCallback(IFrameCallback* callback);
+ void setFrameInterval(nsecs_t frameInterval);
+
TimeLord& timeLord() { return mTimeLord; }
RenderState& renderState() { return *mRenderState; }
EglManager& eglManager() { return *mEglManager; }
+ JankTracker& jankTracker() { return *mJankTracker; }
protected:
virtual bool threadLoop() override;
@@ -132,6 +136,8 @@ private:
TimeLord mTimeLord;
RenderState* mRenderState;
EglManager* mEglManager;
+
+ JankTracker* mJankTracker = nullptr;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/TimeLord.cpp b/libs/hwui/renderthread/TimeLord.cpp
index f187493..f846d6a 100644
--- a/libs/hwui/renderthread/TimeLord.cpp
+++ b/libs/hwui/renderthread/TimeLord.cpp
@@ -32,7 +32,7 @@ bool TimeLord::vsyncReceived(nsecs_t vsync) {
return false;
}
-nsecs_t TimeLord::computeFrameTimeMs() {
+nsecs_t TimeLord::computeFrameTimeNanos() {
// Logic copied from Choreographer.java
nsecs_t now = systemTime(CLOCK_MONOTONIC);
nsecs_t jitterNanos = now - mFrameTimeNanos;
@@ -40,7 +40,11 @@ nsecs_t TimeLord::computeFrameTimeMs() {
nsecs_t lastFrameOffset = jitterNanos % mFrameIntervalNanos;
mFrameTimeNanos = now - lastFrameOffset;
}
- return nanoseconds_to_milliseconds(mFrameTimeNanos);
+ return mFrameTimeNanos;
+}
+
+nsecs_t TimeLord::computeFrameTimeMs() {
+ return nanoseconds_to_milliseconds(computeFrameTimeNanos());
}
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/TimeLord.h b/libs/hwui/renderthread/TimeLord.h
index 7c155d2..5464399 100644
--- a/libs/hwui/renderthread/TimeLord.h
+++ b/libs/hwui/renderthread/TimeLord.h
@@ -29,9 +29,13 @@ class RenderThread;
class TimeLord {
public:
void setFrameInterval(nsecs_t intervalNanos) { mFrameIntervalNanos = intervalNanos; }
+ nsecs_t frameIntervalNanos() const { return mFrameIntervalNanos; }
+
// returns true if the vsync is newer, false if it was rejected for staleness
bool vsyncReceived(nsecs_t vsync);
+ nsecs_t latestVsync() { return mFrameTimeNanos; }
nsecs_t computeFrameTimeMs();
+ nsecs_t computeFrameTimeNanos();
private:
friend class RenderThread;
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index b6d2c4d..0431e22 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -85,7 +85,7 @@ public:
proxy->initialize(surface);
float lightX = width / 2.0;
proxy->setup(width, height, (Vector3){lightX, dp(-200.0f), dp(800.0f)},
- dp(800.0f), 255 * 0.075, 255 * 0.15);
+ dp(800.0f), 255 * 0.075, 255 * 0.15, gDisplay.density);
android::uirenderer::Rect DUMMY;
@@ -100,8 +100,7 @@ public:
ATRACE_NAME("UI-Draw Frame");
animation.doFrame(i);
- nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC);
- proxy->syncAndDrawFrame(frameTimeNs, 0, gDisplay.density);
+ proxy->syncAndDrawFrame();
}
sleep(5);
diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h
index 5ca9083..b93f720 100644
--- a/libs/hwui/utils/Macros.h
+++ b/libs/hwui/utils/Macros.h
@@ -35,4 +35,12 @@
static_assert(std::is_standard_layout<Type>::value, \
#Type " must have standard layout")
+#define HWUI_ENUM(name, ...) \
+ namespace name { \
+ enum _##name { \
+ __VA_ARGS__ \
+ }; \
+ } \
+ typedef enum name::_##name name##Enum
+
#endif /* MACROS_H */
diff --git a/libs/hwui/utils/RingBuffer.h b/libs/hwui/utils/RingBuffer.h
new file mode 100644
index 0000000..fc9aec0
--- /dev/null
+++ b/libs/hwui/utils/RingBuffer.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef RINGBUFFER_H_
+#define RINGBUFFER_H_
+
+#include "utils/Macros.h"
+
+#include <stddef.h>
+
+namespace android {
+namespace uirenderer {
+
+template<class T, size_t SIZE>
+class RingBuffer {
+ PREVENT_COPY_AND_ASSIGN(RingBuffer);
+
+public:
+ RingBuffer() {}
+ ~RingBuffer() {}
+
+ size_t capacity() { return SIZE; }
+ size_t size() { return mCount; }
+
+ T& next() {
+ mHead = (mHead + 1) % SIZE;
+ if (mCount < SIZE) {
+ mCount++;
+ }
+ return mBuffer[mHead];
+ }
+
+ T& front() {
+ return this[0];
+ }
+
+ T& back() {
+ return this[size() - 1];
+ }
+
+ T& operator[](size_t index) {
+ return mBuffer[(mHead + index + 1) % mCount];
+ }
+
+ void clear() {
+ mCount = 0;
+ mHead = -1;
+ }
+
+private:
+ T mBuffer[SIZE];
+ int mHead = -1;
+ size_t mCount = 0;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif /* RINGBUFFER_H_ */