From d5a4a1aac980c304d6f46f07f15bfc5c94d8f9d0 Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 19 Feb 2015 14:36:50 -0800 Subject: [PATCH 1/2] DO NOT MERGE Backport of limited jank-tracking metrics Bug: 19821830 Cherry-pick of ba6adf66d3c44c0aa2fd8a224862ff1901d64300 Cherry-pick of e70c5754d01f2ab0ff47ea3eabaa88aca5ed2a36 Change-Id: Id342fa0ab345f204bec58acf45ce72f6de950cfb --- core/java/android/app/ActivityThread.java | 2 +- core/java/android/view/Choreographer.java | 20 +++ core/java/android/view/FrameInfo.java | 118 +++++++++++++ core/java/android/view/HardwareRenderer.java | 2 +- core/java/android/view/ThreadedRenderer.java | 77 ++++----- core/java/android/view/ViewRootImpl.java | 16 +- .../android/view/WindowManagerGlobal.java | 4 +- core/jni/android_view_Surface.cpp | 10 +- core/jni/android_view_ThreadedRenderer.cpp | 22 ++- libs/hwui/Android.mk | 2 + libs/hwui/FrameInfo.cpp | 28 +++ libs/hwui/FrameInfo.h | 116 +++++++++++++ libs/hwui/JankTracker.cpp | 159 ++++++++++++++++++ libs/hwui/JankTracker.h | 71 ++++++++ libs/hwui/renderthread/CanvasContext.cpp | 46 ++++- libs/hwui/renderthread/CanvasContext.h | 26 +-- libs/hwui/renderthread/DrawFrameTask.cpp | 16 +- libs/hwui/renderthread/DrawFrameTask.h | 9 +- libs/hwui/renderthread/RenderProxy.cpp | 52 ++++-- libs/hwui/renderthread/RenderProxy.h | 8 +- libs/hwui/renderthread/RenderThread.cpp | 9 +- libs/hwui/renderthread/RenderThread.h | 12 +- libs/hwui/renderthread/TimeLord.cpp | 8 +- libs/hwui/renderthread/TimeLord.h | 4 + libs/hwui/tests/main.cpp | 5 +- libs/hwui/utils/Macros.h | 8 + libs/hwui/utils/RingBuffer.h | 71 ++++++++ 27 files changed, 805 insertions(+), 116 deletions(-) create mode 100644 core/java/android/view/FrameInfo.java create mode 100644 libs/hwui/FrameInfo.cpp create mode 100644 libs/hwui/FrameInfo.h create mode 100644 libs/hwui/JankTracker.cpp create mode 100644 libs/hwui/JankTracker.h create mode 100644 libs/hwui/utils/RingBuffer.h diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 8353d5472b8ea..4a8714c55a917 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1083,7 +1083,7 @@ private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean check @Override public void dumpGfxInfo(FileDescriptor fd, String[] args) { dumpGraphicsInfo(fd); - WindowManagerGlobal.getInstance().dumpGfxInfo(fd); + WindowManagerGlobal.getInstance().dumpGfxInfo(fd, args); } @Override diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index f41afcff03046..c8149d9346f86 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -140,6 +140,19 @@ protected Choreographer initialValue() { private long mLastFrameTimeNanos; 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 @@ void doFrame(long frameTimeNanos, int frame) { return; // no work to do } + long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime(); final long jitterNanos = startNanos - frameTimeNanos; if (jitterNanos >= mFrameIntervalNanos) { @@ -541,12 +555,18 @@ void doFrame(long frameTimeNanos, int frame) { 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 0000000000000..c79547c8313eb --- /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 c5c3f830fe3e8..aa6188543fc1b 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -278,7 +278,7 @@ public static boolean isAvailable() { /** * 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 ad4a048808c3d..df0838f6a643c 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.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 @@ void setup(int width, int height, Rect surfaceInsets) { mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight); nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, mLightY, mLightZ, mLightRadius, - mAmbientShadowAlpha, mSpotShadowAlpha); + mAmbientShadowAlpha, mSpotShadowAlpha, mDensity); } @Override @@ -233,32 +243,25 @@ int getHeight() { } @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 @@ void invalidateRoot() { @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 @@ void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) { 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 @@ private static void validateMap(Context context, long[] map) { 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 @@ private static native int nSyncAndDrawFrame(long nativeProxy, 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 e4d82b16d185d..356614014f1df 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -56,6 +56,7 @@ 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; @@ -1502,6 +1503,7 @@ private void performTraversals() { // 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); @@ -2505,6 +2507,9 @@ private void draw(boolean fullRedrawNeeded) { } } + 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. @@ -2624,7 +2629,6 @@ private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, i dirty.setEmpty(); mIsAnimating = false; - attachInfo.mDrawingTime = SystemClock.uptimeMillis(); mView.mPrivateFlags |= View.PFLAG_DRAWN; if (DEBUG_DRAW) { @@ -5778,6 +5782,16 @@ void doProcessInputEvents() { 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 a14c7661fb452..643340ab0b003 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -459,7 +459,7 @@ private void doTrimForeground() { } } - public void dumpGfxInfo(FileDescriptor fd) { + public void dumpGfxInfo(FileDescriptor fd, String[] args) { FileOutputStream fout = new FileOutputStream(fd); PrintWriter pw = new FastPrintWriter(fout); try { @@ -476,7 +476,7 @@ public void dumpGfxInfo(FileDescriptor fd) { 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 a39ff8eb6511b..8590acaf24368 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -49,6 +49,7 @@ #include #include +#include #include #include @@ -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(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 b9d849c72ebbd..00d7fc8331707 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -283,10 +283,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(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, @@ -296,9 +296,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(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, @@ -393,10 +397,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(proxyPtr); int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); - proxy->dumpProfileInfo(fd); + proxy->dumpProfileInfo(fd, dumpFlags); } #endif @@ -430,9 +434,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 }, @@ -447,7 +451,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 }, #endif { "setupShadersDiskCache", "(Ljava/lang/String;)V", (void*) android_view_ThreadedRenderer_setupShadersDiskCache }, diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 49560ffdd6283..507472aa61a82 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -18,6 +18,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) AssetAtlas.cpp \ DamageAccumulator.cpp \ FontRenderer.cpp \ + FrameInfo.cpp \ GammaFontRenderer.cpp \ Caches.cpp \ DisplayList.cpp \ @@ -32,6 +33,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) 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 0000000000000..6da1fa808d0a7 --- /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 + +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 0000000000000..3c3167721d2b9 --- /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 +#include + +#include + +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(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(index)]; + } + + int64_t operator[](int index) const { + if (index < 0 || index >= FrameInfoIndex::kNumIndexes) return 0; + return mFrameInfo[static_cast(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 0000000000000..f7c81953b67b9 --- /dev/null +++ b/libs/hwui/JankTracker.cpp @@ -0,0 +1,159 @@ +/* + * 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 +#include +#include + +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(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(.5 * frameInterval); + mThresholds[kSlowSync] = static_cast(.2 * frameInterval); + mThresholds[kSlowRT] = static_cast(.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]; + uint32_t framebucket = std::min( + static_cast(ns2ms(totalDuration)), + sizeof(mFrameCounts) / sizeof(mFrameCounts[0])); + // Keep the fast path as fast as possible. + if (CC_LIKELY(totalDuration < mFrameInterval)) { + mFrameCounts[framebucket]++; + return; + } + + if (frame[kFlags] & EXEMPT_FRAMES_FLAGS) { + return; + } + + mFrameCounts[framebucket]++; + 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); + fprintf(file, "\n 90th percentile: %ums", findPercentile(90)); + fprintf(file, "\n 95th percentile: %ums", findPercentile(95)); + fprintf(file, "\n 99th percentile: %ums", findPercentile(99)); + 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(mBuckets)); + memset(mFrameCounts, 0, sizeof(mFrameCounts)); + mTotalFrameCount = 0; + mJankFrameCount = 0; +} + +uint32_t JankTracker::findPercentile(int percentile) { + int pos = percentile * mTotalFrameCount / 100; + int remaining = mTotalFrameCount - pos; + for (int i = sizeof(mFrameCounts) / sizeof(mFrameCounts[0]) - 1; i >= 0; i--) { + remaining -= mFrameCounts[i]; + if (remaining <= 0) { + return i; + } + } + return 0; +} + +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h new file mode 100644 index 0000000000000..3d4929b215c62 --- /dev/null +++ b/libs/hwui/JankTracker.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 JANKTRACKER_H_ +#define JANKTRACKER_H_ + +#include "FrameInfo.h" +#include "renderthread/TimeLord.h" +#include "utils/RingBuffer.h" + +#include + +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: + uint32_t findPercentile(int p); + + JankBucket mBuckets[NUM_BUCKETS]; + int64_t mThresholds[NUM_BUCKETS]; + uint32_t mFrameCounts[128]; + + 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 75bd0676d70a4..2eaa7719227ca 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -47,7 +47,8 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, , mOpaque(!translucent) , mCanvas(NULL) , mHaveNewSurface(false) - , mRootRenderNode(rootRenderNode) { + , mRootRenderNode(rootRenderNode) + , mCurrentFrameInfo(NULL) { mAnimationContext = contextFactory->createAnimationContext(mRenderThread.timeLord()); mRenderThread.renderState().registerCanvasContext(this); } @@ -152,9 +153,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) { @@ -204,6 +209,7 @@ void CanvasContext::draw() { "drawRenderNode called on a context with no canvas or surface!"); profiler().markPlaybackStart(); + mCurrentFrameInfo->markIssueDrawCommandsStart(); SkRect dirty; mDamageAccumulator.finish(&dirty); @@ -241,12 +247,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 (status & DrawGlInfo::kStatusDrew) { swapBuffers(); } else { mEglManager.cancelFrame(); } + // TODO: Use a fence for real completion? + mCurrentFrameInfo->markFrameCompleted(); + mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo); profiler().finishFrame(); } @@ -259,9 +272,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(); } @@ -374,6 +392,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 0cc2c7c2811ab..0e8be9d9ed593 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -17,7 +17,14 @@ #ifndef CANVASCONTEXT_H_ #define CANVASCONTEXT_H_ -#include +#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 #include @@ -25,14 +32,7 @@ #include #include -#include "../DamageAccumulator.h" -#include "../DrawProfiler.h" -#include "../IContextFactory.h" -#include "../RenderNode.h" -#include "RenderTask.h" -#include "RenderThread.h" - -#define FUNCTOR_PROCESS_DELAY 4 +#include namespace android { namespace uirenderer { @@ -75,7 +75,7 @@ class CanvasContext : public IFrameCallback { 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 @@ class CanvasContext : public IFrameCallback { 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 @@ class CanvasContext : public IFrameCallback { const sp mRootRenderNode; DrawProfiler mProfiler; + FrameInfo* mCurrentFrameInfo; + // Ring buffer large enough for 1 second worth of frames + RingBuffer mFrames; std::set mPrefetechedLayers; }; diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 97b31a95f278f..23a020233f27e 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -34,8 +34,6 @@ namespace renderthread { DrawFrameTask::DrawFrameTask() : mRenderThread(NULL) , mContext(NULL) - , 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 28f6cb26fb57a..eccb87f293831 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 @@ class DrawFrameTask : public RenderTask { 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(); @@ -80,12 +83,12 @@ class DrawFrameTask : public RenderTask { /********************************************* * Single frame data *********************************************/ - nsecs_t mFrameTimeNanos; - nsecs_t mRecordDurationNanos; float mDensity; std::vector< sp > 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 6d063a4b0ce00..088c65b939ef4 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 NULL; } @@ -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) { @@ -371,19 +379,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 NULL; } -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(outputLogBuffer, int fd) { +CREATE_BRIDGE2(outputLogBuffer, int fd, RenderThread* thread) { + args->thread->jankTracker().dump(args->fd); RenderNode::outputLogBuffer(args->fd); return NULL; } @@ -391,6 +408,7 @@ CREATE_BRIDGE1(outputLogBuffer, int fd) { void RenderProxy::outputLogBuffer(int fd) { SETUP_TASK(outputLogBuffer); args->fd = fd; + args->thread = &RenderThread::getInstance(); staticPostAndWait(task); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index fd1fe05ffbdbc..d86f1fca466ff 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -71,10 +71,10 @@ class ANDROID_API RenderProxy { ANDROID_API void updateSurface(const sp& window); ANDROID_API bool pauseSurface(const sp& 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 @@ class ANDROID_API RenderProxy { 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 outputLogBuffer(int fd); ANDROID_API void setTextureAtlas(const sp& buffer, int64_t* map, size_t size); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 84826b7a3bff3..7a8c3cee088dd 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -143,7 +143,8 @@ RenderThread::RenderThread() : Thread(true), Singleton() , mFrameCallbackTaskPending(false) , mFrameCallbackTask(0) , mRenderState(NULL) - , mEglManager(NULL) { + , mEglManager(NULL) + , mJankTracker(NULL) { mFrameCallbackTask = new DispatchFrameCallbacks(this); mLooper = new Looper(false); run("RenderThread"); @@ -153,6 +154,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(); @@ -169,6 +175,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 99c2e1527ea25..8a28a35aabc25 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -19,8 +19,8 @@ #include "RenderTask.h" -#include -#include +#include "../JankTracker.h" +#include "TimeLord.h" #include #include @@ -28,7 +28,8 @@ #include #include -#include "TimeLord.h" +#include +#include namespace android { @@ -85,9 +86,12 @@ class ANDROID_API RenderThread : public Thread, protected Singletoninitialize(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; @@ -116,8 +116,7 @@ int main(int argc, char* argv[]) { cards[ci]->mutateStagingProperties().setTranslationY(i); cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } - nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC); - proxy->syncAndDrawFrame(frameTimeNs, 0, gDisplay.density); + proxy->syncAndDrawFrame(); usleep(12000); } diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h index 5b7c87ceef8f7..068b32b3fe682 100644 --- a/libs/hwui/utils/Macros.h +++ b/libs/hwui/utils/Macros.h @@ -29,4 +29,12 @@ friend inline int compare_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs); } \ friend inline hash_t hash_type(const Type& entry) { return entry.hash(); } +#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 0000000000000..62b22fdcef424 --- /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 + +namespace android { +namespace uirenderer { + +template +class RingBuffer { + PREVENT_COPY_AND_ASSIGN(RingBuffer); + +public: + RingBuffer() : mHead(-1), mCount(0) {} + ~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; + size_t mCount; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif /* RINGBUFFER_H_ */ From 0e40462e11d27eb859b829b112cecb8c6f0d7afb Mon Sep 17 00:00:00 2001 From: John Reck Date: Wed, 13 May 2015 21:42:28 +0000 Subject: [PATCH 2/2] Revert "DO NOT MERGE Backport of limited jank-tracking metrics" This reverts commit 2614bd225f84f7a23e6b30fc6b47bede153e5f4c. Change-Id: I344b4cbaa0bb0caf50bceb806d1446ee27ea52d8 --- core/java/android/app/ActivityThread.java | 2 +- core/java/android/view/Choreographer.java | 20 --- core/java/android/view/FrameInfo.java | 118 ------------- core/java/android/view/HardwareRenderer.java | 2 +- core/java/android/view/ThreadedRenderer.java | 77 +++++---- core/java/android/view/ViewRootImpl.java | 16 +- .../android/view/WindowManagerGlobal.java | 4 +- core/jni/android_view_Surface.cpp | 10 +- core/jni/android_view_ThreadedRenderer.cpp | 22 +-- libs/hwui/Android.mk | 2 - libs/hwui/FrameInfo.cpp | 28 --- libs/hwui/FrameInfo.h | 116 ------------- libs/hwui/JankTracker.cpp | 159 ------------------ libs/hwui/JankTracker.h | 71 -------- libs/hwui/renderthread/CanvasContext.cpp | 46 +---- libs/hwui/renderthread/CanvasContext.h | 26 ++- libs/hwui/renderthread/DrawFrameTask.cpp | 16 +- libs/hwui/renderthread/DrawFrameTask.h | 9 +- libs/hwui/renderthread/RenderProxy.cpp | 52 ++---- libs/hwui/renderthread/RenderProxy.h | 8 +- libs/hwui/renderthread/RenderThread.cpp | 9 +- libs/hwui/renderthread/RenderThread.h | 12 +- libs/hwui/renderthread/TimeLord.cpp | 8 +- libs/hwui/renderthread/TimeLord.h | 4 - libs/hwui/tests/main.cpp | 5 +- libs/hwui/utils/Macros.h | 8 - libs/hwui/utils/RingBuffer.h | 71 -------- 27 files changed, 116 insertions(+), 805 deletions(-) delete mode 100644 core/java/android/view/FrameInfo.java delete mode 100644 libs/hwui/FrameInfo.cpp delete mode 100644 libs/hwui/FrameInfo.h delete mode 100644 libs/hwui/JankTracker.cpp delete mode 100644 libs/hwui/JankTracker.h delete mode 100644 libs/hwui/utils/RingBuffer.h diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4a8714c55a917..8353d5472b8ea 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1083,7 +1083,7 @@ private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean check @Override public void dumpGfxInfo(FileDescriptor fd, String[] args) { dumpGraphicsInfo(fd); - WindowManagerGlobal.getInstance().dumpGfxInfo(fd, args); + WindowManagerGlobal.getInstance().dumpGfxInfo(fd); } @Override diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index c8149d9346f86..f41afcff03046 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -140,19 +140,6 @@ protected Choreographer initialValue() { private long mLastFrameTimeNanos; 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 @@ -526,7 +513,6 @@ void doFrame(long frameTimeNanos, int frame) { return; // no work to do } - long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime(); final long jitterNanos = startNanos - frameTimeNanos; if (jitterNanos >= mFrameIntervalNanos) { @@ -555,18 +541,12 @@ void doFrame(long frameTimeNanos, int frame) { 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 deleted file mode 100644 index c79547c8313eb..0000000000000 --- a/core/java/android/view/FrameInfo.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 aa6188543fc1b..c5c3f830fe3e8 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -278,7 +278,7 @@ public static boolean isAvailable() { /** * Outputs extra debugging information in the specified file descriptor. */ - abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args); + abstract void dumpGfxInfo(PrintWriter pw, FileDescriptor fd); /** * 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 df0838f6a643c..ad4a048808c3d 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -16,7 +16,8 @@ package android.view; -import android.annotation.IntDef; +import com.android.internal.R; + import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -26,18 +27,16 @@ 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; @@ -75,14 +74,6 @@ 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; @@ -102,12 +93,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) { @@ -119,7 +110,6 @@ 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); @@ -224,7 +214,7 @@ void setup(int width, int height, Rect surfaceInsets) { mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight); nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, mLightY, mLightZ, mLightRadius, - mAmbientShadowAlpha, mSpotShadowAlpha, mDensity); + mAmbientShadowAlpha, mSpotShadowAlpha); } @Override @@ -243,25 +233,32 @@ int getHeight() { } @Override - void dumpGfxInfo(PrintWriter pw, FileDescriptor fd, String[] args) { + void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) { pw.flush(); - 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; - } + 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; } - nDumpProfileInfo(mNativeProxy, fd, flags); + return -1; + } + + private static boolean checkIfProfilingRequested() { + String profiling = SystemProperties.get(HardwareRenderer.PROFILE_PROPERTY); + int graphType = search(VISUALIZERS, profiling); + return (graphType >= 0) || Boolean.parseBoolean(profiling); } @Override boolean loadSystemProperties() { boolean changed = nLoadSystemProperties(mNativeProxy); + boolean wantProfiling = checkIfProfilingRequested(); + if (wantProfiling != mProfilingEnabled) { + mProfilingEnabled = wantProfiling; + changed = true; + } if (changed) { invalidateRoot(); } @@ -310,12 +307,20 @@ void invalidateRoot() { @Override void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) { attachInfo.mIgnoreDirtyState = true; + long frameTimeNanos = mChoreographer.getFrameTimeNanos(); + attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS; - final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer; - choreographer.mFrameInfo.markDrawStart(); + long recordDuration = 0; + if (mProfilingEnabled) { + recordDuration = System.nanoTime(); + } updateRootDisplayList(view, callbacks); + if (mProfilingEnabled) { + recordDuration = System.nanoTime() - recordDuration; + } + attachInfo.mIgnoreDirtyState = false; // register animating rendernodes which started animating prior to renderer @@ -332,8 +337,8 @@ void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) { attachInfo.mPendingAnimatingRenderNodes = null; } - final long[] frameInfo = choreographer.mFrameInfo.mFrameInfo; - int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length); + int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos, + recordDuration, view.getResources().getDisplayMetrics().density); if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) { setEnabled(false); attachInfo.mViewRootImpl.mSurface.release(); @@ -495,9 +500,10 @@ private static void validateMap(Context context, long[] map) { 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, float density); + int ambientShadowAlpha, int spotShadowAlpha); private static native void nSetOpaque(long nativeProxy, boolean opaque); - private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size); + private static native int nSyncAndDrawFrame(long nativeProxy, + long frameTimeNanos, long recordDuration, float density); private static native void nDestroy(long nativeProxy); private static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode); @@ -517,6 +523,5 @@ private static native void nSetup(long nativeProxy, int width, int height, private static native void nStopDrawing(long nativeProxy); private static native void nNotifyFramePending(long nativeProxy); - private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, - @DumpFlags int dumpFlags); + private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 356614014f1df..e4d82b16d185d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -56,7 +56,6 @@ 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; @@ -1503,7 +1502,6 @@ private void performTraversals() { // 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); @@ -2507,9 +2505,6 @@ private void draw(boolean fullRedrawNeeded) { } } - 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. @@ -2629,6 +2624,7 @@ private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, i dirty.setEmpty(); mIsAnimating = false; + attachInfo.mDrawingTime = SystemClock.uptimeMillis(); mView.mPrivateFlags |= View.PFLAG_DRAWN; if (DEBUG_DRAW) { @@ -5782,16 +5778,6 @@ void doProcessInputEvents() { 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 643340ab0b003..a14c7661fb452 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -459,7 +459,7 @@ private void doTrimForeground() { } } - public void dumpGfxInfo(FileDescriptor fd, String[] args) { + public void dumpGfxInfo(FileDescriptor fd) { FileOutputStream fout = new FileOutputStream(fd); PrintWriter pw = new FastPrintWriter(fout); try { @@ -476,7 +476,7 @@ public void dumpGfxInfo(FileDescriptor fd, String[] args) { HardwareRenderer renderer = root.getView().mAttachInfo.mHardwareRenderer; if (renderer != null) { - renderer.dumpGfxInfo(pw, fd, args); + renderer.dumpGfxInfo(pw, fd); } } diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 8590acaf24368..a39ff8eb6511b 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -49,7 +49,6 @@ #include #include -#include #include #include @@ -395,7 +394,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, 1.0f); + proxy->setup(0, 0, (Vector3){0, 0, 0}, 0, 0, 0); return (jlong) proxy; } @@ -407,11 +406,8 @@ static void setSurface(JNIEnv* env, jclass clazz, jlong rendererPtr, jlong surfa static void draw(JNIEnv* env, jclass clazz, jlong rendererPtr) { RenderProxy* proxy = reinterpret_cast(rendererPtr); - nsecs_t vsync = systemTime(CLOCK_MONOTONIC); - UiFrameInfoBuilder(proxy->frameInfo()) - .setVsync(vsync, vsync) - .addFlag(FrameInfoFlags::kSurfaceCanvas); - proxy->syncAndDrawFrame(); + nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC); + proxy->syncAndDrawFrame(frameTimeNs, 0, 1.0f); } 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 00d7fc8331707..b9d849c72ebbd 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -283,10 +283,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, jfloat density) { + jint ambientShadowAlpha, jint spotShadowAlpha) { RenderProxy* proxy = reinterpret_cast(proxyPtr); proxy->setup(width, height, (Vector3){lightX, lightY, lightZ}, lightRadius, - ambientShadowAlpha, spotShadowAlpha, density); + ambientShadowAlpha, spotShadowAlpha); } static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz, @@ -296,13 +296,9 @@ static void android_view_ThreadedRenderer_setOpaque(JNIEnv* env, jobject clazz, } static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz, - 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); + jlong proxyPtr, jlong frameTimeNanos, jlong recordDuration, jfloat density) { RenderProxy* proxy = reinterpret_cast(proxyPtr); - env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo()); - return proxy->syncAndDrawFrame(); + return proxy->syncAndDrawFrame(frameTimeNanos, recordDuration, density); } static void android_view_ThreadedRenderer_destroy(JNIEnv* env, jobject clazz, @@ -397,10 +393,10 @@ static void android_view_ThreadedRenderer_notifyFramePending(JNIEnv* env, jobjec } static void android_view_ThreadedRenderer_dumpProfileInfo(JNIEnv* env, jobject clazz, - jlong proxyPtr, jobject javaFileDescriptor, jint dumpFlags) { + jlong proxyPtr, jobject javaFileDescriptor) { RenderProxy* proxy = reinterpret_cast(proxyPtr); int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); - proxy->dumpProfileInfo(fd, dumpFlags); + proxy->dumpProfileInfo(fd); } #endif @@ -434,9 +430,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", "(JIIFFFFIIF)V", (void*) android_view_ThreadedRenderer_setup }, + { "nSetup", "(JIIFFFFII)V", (void*) android_view_ThreadedRenderer_setup }, { "nSetOpaque", "(JZ)V", (void*) android_view_ThreadedRenderer_setOpaque }, - { "nSyncAndDrawFrame", "(J[JI)I", (void*) android_view_ThreadedRenderer_syncAndDrawFrame }, + { "nSyncAndDrawFrame", "(JJJF)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 }, @@ -451,7 +447,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;I)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo }, + { "nDumpProfileInfo", "(JLjava/io/FileDescriptor;)V", (void*) android_view_ThreadedRenderer_dumpProfileInfo }, #endif { "setupShadersDiskCache", "(Ljava/lang/String;)V", (void*) android_view_ThreadedRenderer_setupShadersDiskCache }, diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 507472aa61a82..49560ffdd6283 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -18,7 +18,6 @@ ifeq ($(USE_OPENGL_RENDERER),true) AssetAtlas.cpp \ DamageAccumulator.cpp \ FontRenderer.cpp \ - FrameInfo.cpp \ GammaFontRenderer.cpp \ Caches.cpp \ DisplayList.cpp \ @@ -33,7 +32,6 @@ ifeq ($(USE_OPENGL_RENDERER),true) 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 deleted file mode 100644 index 6da1fa808d0a7..0000000000000 --- a/libs/hwui/FrameInfo.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 - -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 deleted file mode 100644 index 3c3167721d2b9..0000000000000 --- a/libs/hwui/FrameInfo.h +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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 -#include - -#include - -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(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(index)]; - } - - int64_t operator[](int index) const { - if (index < 0 || index >= FrameInfoIndex::kNumIndexes) return 0; - return mFrameInfo[static_cast(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 deleted file mode 100644 index f7c81953b67b9..0000000000000 --- a/libs/hwui/JankTracker.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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 -#include -#include - -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(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(.5 * frameInterval); - mThresholds[kSlowSync] = static_cast(.2 * frameInterval); - mThresholds[kSlowRT] = static_cast(.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]; - uint32_t framebucket = std::min( - static_cast(ns2ms(totalDuration)), - sizeof(mFrameCounts) / sizeof(mFrameCounts[0])); - // Keep the fast path as fast as possible. - if (CC_LIKELY(totalDuration < mFrameInterval)) { - mFrameCounts[framebucket]++; - return; - } - - if (frame[kFlags] & EXEMPT_FRAMES_FLAGS) { - return; - } - - mFrameCounts[framebucket]++; - 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); - fprintf(file, "\n 90th percentile: %ums", findPercentile(90)); - fprintf(file, "\n 95th percentile: %ums", findPercentile(95)); - fprintf(file, "\n 99th percentile: %ums", findPercentile(99)); - 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(mBuckets)); - memset(mFrameCounts, 0, sizeof(mFrameCounts)); - mTotalFrameCount = 0; - mJankFrameCount = 0; -} - -uint32_t JankTracker::findPercentile(int percentile) { - int pos = percentile * mTotalFrameCount / 100; - int remaining = mTotalFrameCount - pos; - for (int i = sizeof(mFrameCounts) / sizeof(mFrameCounts[0]) - 1; i >= 0; i--) { - remaining -= mFrameCounts[i]; - if (remaining <= 0) { - return i; - } - } - return 0; -} - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h deleted file mode 100644 index 3d4929b215c62..0000000000000 --- a/libs/hwui/JankTracker.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 - -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: - uint32_t findPercentile(int p); - - JankBucket mBuckets[NUM_BUCKETS]; - int64_t mThresholds[NUM_BUCKETS]; - uint32_t mFrameCounts[128]; - - 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 2eaa7719227ca..75bd0676d70a4 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -47,8 +47,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, , mOpaque(!translucent) , mCanvas(NULL) , mHaveNewSurface(false) - , mRootRenderNode(rootRenderNode) - , mCurrentFrameInfo(NULL) { + , mRootRenderNode(rootRenderNode) { mAnimationContext = contextFactory->createAnimationContext(mRenderThread.timeLord()); mRenderThread.renderState().registerCanvasContext(this); } @@ -153,13 +152,9 @@ void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) { } } -void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo) { +void CanvasContext::prepareTree(TreeInfo& info) { 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) { @@ -209,7 +204,6 @@ void CanvasContext::draw() { "drawRenderNode called on a context with no canvas or surface!"); profiler().markPlaybackStart(); - mCurrentFrameInfo->markIssueDrawCommandsStart(); SkRect dirty; mDamageAccumulator.finish(&dirty); @@ -247,19 +241,12 @@ 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 (status & DrawGlInfo::kStatusDrew) { swapBuffers(); } else { mEglManager.cancelFrame(); } - // TODO: Use a fence for real completion? - mCurrentFrameInfo->markFrameCompleted(); - mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo); profiler().finishFrame(); } @@ -272,14 +259,9 @@ 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, frameInfo); + prepareTree(info); if (info.out.canDrawThisFrame) { draw(); } @@ -392,28 +374,6 @@ 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 0e8be9d9ed593..0cc2c7c2811ab 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -17,14 +17,7 @@ #ifndef CANVASCONTEXT_H_ #define CANVASCONTEXT_H_ -#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 #include #include @@ -32,7 +25,14 @@ #include #include -#include +#include "../DamageAccumulator.h" +#include "../DrawProfiler.h" +#include "../IContextFactory.h" +#include "../RenderNode.h" +#include "RenderTask.h" +#include "RenderThread.h" + +#define FUNCTOR_PROCESS_DELAY 4 namespace android { namespace uirenderer { @@ -75,7 +75,7 @@ class CanvasContext : public IFrameCallback { void setOpaque(bool opaque); void makeCurrent(); void processLayerUpdate(DeferredLayerUpdater* layerUpdater); - void prepareTree(TreeInfo& info, int64_t* uiFrameInfo); + void prepareTree(TreeInfo& info); void draw(); void destroy(); @@ -103,9 +103,6 @@ class CanvasContext : public IFrameCallback { DrawProfiler& profiler() { return mProfiler; } - void dumpFrames(int fd); - void resetFrameStats(); - private: friend class RegisterFrameCallbackTask; // TODO: Replace with something better for layer & other GL object @@ -136,9 +133,6 @@ class CanvasContext : public IFrameCallback { const sp mRootRenderNode; DrawProfiler mProfiler; - FrameInfo* mCurrentFrameInfo; - // Ring buffer large enough for 1 second worth of frames - RingBuffer mFrames; std::set mPrefetechedLayers; }; diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index 23a020233f27e..97b31a95f278f 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -34,6 +34,8 @@ namespace renderthread { DrawFrameTask::DrawFrameTask() : mRenderThread(NULL) , mContext(NULL) + , mFrameTimeNanos(0) + , mRecordDurationNanos(0) , mDensity(1.0f) // safe enough default , mSyncResult(kSync_OK) { } @@ -66,12 +68,18 @@ void DrawFrameTask::removeLayerUpdate(DeferredLayerUpdater* layer) { } } -int DrawFrameTask::drawFrame() { +int DrawFrameTask::drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos) { 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; } @@ -85,7 +93,7 @@ void DrawFrameTask::run() { ATRACE_NAME("DrawFrame"); mContext->profiler().setDensity(mDensity); - mContext->profiler().startFrame(); + mContext->profiler().startFrame(mRecordDurationNanos); bool canUnblockUiThread; bool canDrawThisFrame; @@ -114,7 +122,7 @@ void DrawFrameTask::run() { bool DrawFrameTask::syncFrameState(TreeInfo& info) { ATRACE_CALL(); - mRenderThread->timeLord().vsyncReceived(mFrameInfo[FrameInfoIndex::kVsync]); + mRenderThread->timeLord().vsyncReceived(mFrameTimeNanos); mContext->makeCurrent(); Caches::getInstance().textureCache.resetMarkInUse(); @@ -122,7 +130,7 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { mContext->processLayerUpdate(mLayers[i].get()); } mLayers.clear(); - mContext->prepareTree(info, mFrameInfo); + mContext->prepareTree(info); // 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 eccb87f293831..28f6cb26fb57a 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -25,7 +25,6 @@ #include "RenderTask.h" #include "../Rect.h" -#include "../FrameInfo.h" #include "../TreeInfo.h" namespace android { @@ -63,9 +62,7 @@ class DrawFrameTask : public RenderTask { void removeLayerUpdate(DeferredLayerUpdater* layer); void setDensity(float density) { mDensity = density; } - int drawFrame(); - - int64_t* frameInfo() { return mFrameInfo; } + int drawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos); virtual void run(); @@ -83,12 +80,12 @@ class DrawFrameTask : public RenderTask { /********************************************* * Single frame data *********************************************/ + nsecs_t mFrameTimeNanos; + nsecs_t mRecordDurationNanos; float mDensity; std::vector< sp > 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 088c65b939ef4..6d063a4b0ce00 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -16,14 +16,14 @@ #include "RenderProxy.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" +#include "CanvasContext.h" +#include "RenderTask.h" +#include "RenderThread.h" + +#include "../DeferredLayerUpdater.h" +#include "../DisplayList.h" +#include "../LayerRenderer.h" +#include "../Rect.h" namespace android { namespace uirenderer { @@ -52,11 +52,6 @@ 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, @@ -97,7 +92,7 @@ void RenderProxy::destroyContext() { } CREATE_BRIDGE2(setFrameInterval, RenderThread* thread, nsecs_t frameIntervalNanos) { - args->thread->setFrameInterval(args->frameIntervalNanos); + args->thread->timeLord().setFrameInterval(args->frameIntervalNanos); return NULL; } @@ -180,8 +175,7 @@ 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, float density) { - mDrawFrameTask.setDensity(density); + uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { SETUP_TASK(setup); args->context = mContext; args->width = width; @@ -205,12 +199,10 @@ void RenderProxy::setOpaque(bool opaque) { post(task); } -int64_t* RenderProxy::frameInfo() { - return mDrawFrameTask.frameInfo(); -} - -int RenderProxy::syncAndDrawFrame() { - return mDrawFrameTask.drawFrame(); +int RenderProxy::syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos, + float density) { + mDrawFrameTask.setDensity(density); + return mDrawFrameTask.drawFrame(frameTimeNanos, recordDurationNanos); } CREATE_BRIDGE1(destroy, CanvasContext* context) { @@ -379,28 +371,19 @@ void RenderProxy::notifyFramePending() { mRenderThread.queueAtFront(task); } -CREATE_BRIDGE3(dumpProfileInfo, CanvasContext* context, int fd, int dumpFlags) { +CREATE_BRIDGE2(dumpProfileInfo, CanvasContext* context, int fd) { 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 NULL; } -void RenderProxy::dumpProfileInfo(int fd, int dumpFlags) { +void RenderProxy::dumpProfileInfo(int fd) { SETUP_TASK(dumpProfileInfo); args->context = mContext; args->fd = fd; - args->dumpFlags = dumpFlags; postAndWait(task); } -CREATE_BRIDGE2(outputLogBuffer, int fd, RenderThread* thread) { - args->thread->jankTracker().dump(args->fd); +CREATE_BRIDGE1(outputLogBuffer, int fd) { RenderNode::outputLogBuffer(args->fd); return NULL; } @@ -408,7 +391,6 @@ CREATE_BRIDGE2(outputLogBuffer, int fd, RenderThread* thread) { void RenderProxy::outputLogBuffer(int fd) { SETUP_TASK(outputLogBuffer); args->fd = fd; - args->thread = &RenderThread::getInstance(); staticPostAndWait(task); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index d86f1fca466ff..fd1fe05ffbdbc 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -71,10 +71,10 @@ class ANDROID_API RenderProxy { ANDROID_API void updateSurface(const sp& window); ANDROID_API bool pauseSurface(const sp& window); ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius, - uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, float density); + uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); ANDROID_API void setOpaque(bool opaque); - ANDROID_API int64_t* frameInfo(); - ANDROID_API int syncAndDrawFrame(); + ANDROID_API int syncAndDrawFrame(nsecs_t frameTimeNanos, nsecs_t recordDurationNanos, + float density); ANDROID_API void destroy(); ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion); @@ -95,7 +95,7 @@ class ANDROID_API RenderProxy { ANDROID_API void stopDrawing(); ANDROID_API void notifyFramePending(); - ANDROID_API void dumpProfileInfo(int fd, int dumpFlags); + ANDROID_API void dumpProfileInfo(int fd); ANDROID_API static void outputLogBuffer(int fd); ANDROID_API void setTextureAtlas(const sp& buffer, int64_t* map, size_t size); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 7a8c3cee088dd..84826b7a3bff3 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -143,8 +143,7 @@ RenderThread::RenderThread() : Thread(true), Singleton() , mFrameCallbackTaskPending(false) , mFrameCallbackTask(0) , mRenderState(NULL) - , mEglManager(NULL) - , mJankTracker(NULL) { + , mEglManager(NULL) { mFrameCallbackTask = new DispatchFrameCallbacks(this); mLooper = new Looper(false); run("RenderThread"); @@ -154,11 +153,6 @@ 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(); @@ -175,7 +169,6 @@ 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 8a28a35aabc25..99c2e1527ea25 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -19,8 +19,8 @@ #include "RenderTask.h" -#include "../JankTracker.h" -#include "TimeLord.h" +#include +#include #include #include @@ -28,8 +28,7 @@ #include #include -#include -#include +#include "TimeLord.h" namespace android { @@ -86,12 +85,9 @@ class ANDROID_API RenderThread : public Thread, protected Singletoninitialize(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, gDisplay.density); + dp(800.0f), 255 * 0.075, 255 * 0.15); android::uirenderer::Rect DUMMY; @@ -116,7 +116,8 @@ int main(int argc, char* argv[]) { cards[ci]->mutateStagingProperties().setTranslationY(i); cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); } - proxy->syncAndDrawFrame(); + nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC); + proxy->syncAndDrawFrame(frameTimeNs, 0, gDisplay.density); usleep(12000); } diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h index 068b32b3fe682..5b7c87ceef8f7 100644 --- a/libs/hwui/utils/Macros.h +++ b/libs/hwui/utils/Macros.h @@ -29,12 +29,4 @@ friend inline int compare_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs); } \ friend inline hash_t hash_type(const Type& entry) { return entry.hash(); } -#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 deleted file mode 100644 index 62b22fdcef424..0000000000000 --- a/libs/hwui/utils/RingBuffer.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 - -namespace android { -namespace uirenderer { - -template -class RingBuffer { - PREVENT_COPY_AND_ASSIGN(RingBuffer); - -public: - RingBuffer() : mHead(-1), mCount(0) {} - ~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; - size_t mCount; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif /* RINGBUFFER_H_ */