diff options
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_ */ |