am 862540cd: Merge "Revert "DO NOT MERGE Backport of limited jank-tracking metrics"" into lmp-mr1-dev
* commit '862540cdb11a7128ae1fd670136f398645b4af7b': Revert "DO NOT MERGE Backport of limited jank-tracking metrics"
This commit is contained in:
@@ -1083,7 +1083,7 @@ public final class ActivityThread {
|
||||
@Override
|
||||
public void dumpGfxInfo(FileDescriptor fd, String[] args) {
|
||||
dumpGraphicsInfo(fd);
|
||||
WindowManagerGlobal.getInstance().dumpGfxInfo(fd, args);
|
||||
WindowManagerGlobal.getInstance().dumpGfxInfo(fd);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -140,19 +140,6 @@ public final class Choreographer {
|
||||
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 @@ public final class Choreographer {
|
||||
return; // no work to do
|
||||
}
|
||||
|
||||
long intendedFrameTimeNanos = frameTimeNanos;
|
||||
startNanos = System.nanoTime();
|
||||
final long jitterNanos = startNanos - frameTimeNanos;
|
||||
if (jitterNanos >= mFrameIntervalNanos) {
|
||||
@@ -555,18 +541,12 @@ public final class Choreographer {
|
||||
return;
|
||||
}
|
||||
|
||||
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
|
||||
mFrameScheduled = false;
|
||||
mLastFrameTimeNanos = frameTimeNanos;
|
||||
}
|
||||
|
||||
mFrameInfo.markInputHandlingStart();
|
||||
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
|
||||
|
||||
mFrameInfo.markAnimationsStart();
|
||||
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
|
||||
|
||||
mFrameInfo.markPerformTraversalsStart();
|
||||
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
|
||||
|
||||
if (DEBUG) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -278,7 +278,7 @@ public abstract class HardwareRenderer {
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -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.graphics.drawable.Drawable;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.Trace;
|
||||
import android.util.Log;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.view.Surface.OutOfResourcesException;
|
||||
import android.view.View.AttachInfo;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
@@ -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 @@ public class ThreadedRenderer extends HardwareRenderer {
|
||||
mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
|
||||
nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight,
|
||||
lightX, mLightY, mLightZ, mLightRadius,
|
||||
mAmbientShadowAlpha, mSpotShadowAlpha, mDensity);
|
||||
mAmbientShadowAlpha, mSpotShadowAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -243,25 +233,32 @@ public class ThreadedRenderer extends HardwareRenderer {
|
||||
}
|
||||
|
||||
@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 @@ public class ThreadedRenderer extends HardwareRenderer {
|
||||
@Override
|
||||
void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) {
|
||||
attachInfo.mIgnoreDirtyState = true;
|
||||
long frameTimeNanos = mChoreographer.getFrameTimeNanos();
|
||||
attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS;
|
||||
|
||||
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 @@ public class ThreadedRenderer extends HardwareRenderer {
|
||||
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 @@ public class ThreadedRenderer extends HardwareRenderer {
|
||||
private static native boolean nPauseSurface(long nativeProxy, Surface window);
|
||||
private static native void nSetup(long nativeProxy, int width, int height,
|
||||
float lightX, float lightY, float lightZ, float lightRadius,
|
||||
int ambientShadowAlpha, int spotShadowAlpha, 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 @@ public class ThreadedRenderer extends HardwareRenderer {
|
||||
private static native void nStopDrawing(long nativeProxy);
|
||||
private static native void nNotifyFramePending(long nativeProxy);
|
||||
|
||||
private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
|
||||
@DumpFlags int dumpFlags);
|
||||
private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,6 @@ import android.util.AndroidRuntimeException;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.Surface.OutOfResourcesException;
|
||||
import android.view.View.AttachInfo;
|
||||
@@ -1503,7 +1502,6 @@ public final class ViewRootImpl implements ViewParent,
|
||||
// to resume them
|
||||
mDirty.set(0, 0, mWidth, mHeight);
|
||||
}
|
||||
mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
|
||||
}
|
||||
final int surfaceGenerationId = mSurface.getGenerationId();
|
||||
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
|
||||
@@ -2507,9 +2505,6 @@ public final class ViewRootImpl implements ViewParent,
|
||||
}
|
||||
}
|
||||
|
||||
mAttachInfo.mDrawingTime =
|
||||
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
|
||||
|
||||
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
|
||||
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
|
||||
// If accessibility focus moved, always invalidate the root.
|
||||
@@ -2629,6 +2624,7 @@ public final class ViewRootImpl implements ViewParent,
|
||||
|
||||
dirty.setEmpty();
|
||||
mIsAnimating = false;
|
||||
attachInfo.mDrawingTime = SystemClock.uptimeMillis();
|
||||
mView.mPrivateFlags |= View.PFLAG_DRAWN;
|
||||
|
||||
if (DEBUG_DRAW) {
|
||||
@@ -5782,16 +5778,6 @@ public final class ViewRootImpl implements ViewParent,
|
||||
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
|
||||
mPendingInputEventCount);
|
||||
|
||||
long eventTime = q.mEvent.getEventTimeNano();
|
||||
long oldestEventTime = eventTime;
|
||||
if (q.mEvent instanceof MotionEvent) {
|
||||
MotionEvent me = (MotionEvent)q.mEvent;
|
||||
if (me.getHistorySize() > 0) {
|
||||
oldestEventTime = me.getHistoricalEventTimeNano(0);
|
||||
}
|
||||
}
|
||||
mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
|
||||
|
||||
deliverInputEvent(q);
|
||||
}
|
||||
|
||||
|
||||
@@ -459,7 +459,7 @@ public final class WindowManagerGlobal {
|
||||
}
|
||||
}
|
||||
|
||||
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 final class WindowManagerGlobal {
|
||||
HardwareRenderer renderer =
|
||||
root.getView().mAttachInfo.mHardwareRenderer;
|
||||
if (renderer != null) {
|
||||
renderer.dumpGfxInfo(pw, fd, args);
|
||||
renderer.dumpGfxInfo(pw, fd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
|
||||
#include <AnimationContext.h>
|
||||
#include <DisplayListRenderer.h>
|
||||
#include <FrameInfo.h>
|
||||
#include <RenderNode.h>
|
||||
#include <renderthread/RenderProxy.h>
|
||||
|
||||
@@ -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<RenderProxy*>(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) {
|
||||
|
||||
@@ -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<RenderProxy*>(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<RenderProxy*>(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<RenderProxy*>(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 },
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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 <cstring>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
void FrameInfo::importUiThreadInfo(int64_t* info) {
|
||||
memcpy(mFrameInfo, info, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
|
||||
}
|
||||
|
||||
} /* namespace uirenderer */
|
||||
} /* namespace android */
|
||||
@@ -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 <cutils/compiler.h>
|
||||
#include <utils/Timers.h>
|
||||
|
||||
#include <memory.h>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
#define UI_THREAD_FRAME_INFO_SIZE 9
|
||||
|
||||
HWUI_ENUM(FrameInfoIndex,
|
||||
kFlags = 0,
|
||||
kIntendedVsync,
|
||||
kVsync,
|
||||
kOldestInputEvent,
|
||||
kNewestInputEvent,
|
||||
kHandleInputStart,
|
||||
kAnimationStart,
|
||||
kPerformTraversalsStart,
|
||||
kDrawStart,
|
||||
// End of UI frame info
|
||||
|
||||
kSyncStart,
|
||||
kIssueDrawCommandsStart,
|
||||
kSwapBuffers,
|
||||
kFrameCompleted,
|
||||
|
||||
// Must be the last value!
|
||||
kNumIndexes
|
||||
);
|
||||
|
||||
HWUI_ENUM(FrameInfoFlags,
|
||||
kWindowLayoutChanged = 1 << 0,
|
||||
kRTAnimation = 1 << 1,
|
||||
kSurfaceCanvas = 1 << 2,
|
||||
);
|
||||
|
||||
class ANDROID_API UiFrameInfoBuilder {
|
||||
public:
|
||||
UiFrameInfoBuilder(int64_t* buffer) : mBuffer(buffer) {
|
||||
memset(mBuffer, 0, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
|
||||
}
|
||||
|
||||
UiFrameInfoBuilder& setVsync(nsecs_t vsyncTime, nsecs_t intendedVsync) {
|
||||
mBuffer[FrameInfoIndex::kVsync] = vsyncTime;
|
||||
mBuffer[FrameInfoIndex::kIntendedVsync] = intendedVsync;
|
||||
return *this;
|
||||
}
|
||||
|
||||
UiFrameInfoBuilder& addFlag(FrameInfoFlagsEnum flag) {
|
||||
mBuffer[FrameInfoIndex::kFlags] |= static_cast<uint64_t>(flag);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t* mBuffer;
|
||||
};
|
||||
|
||||
class FrameInfo {
|
||||
public:
|
||||
void importUiThreadInfo(int64_t* info);
|
||||
|
||||
void markSyncStart() {
|
||||
mFrameInfo[FrameInfoIndex::kSyncStart] = systemTime(CLOCK_MONOTONIC);
|
||||
}
|
||||
|
||||
void markIssueDrawCommandsStart() {
|
||||
mFrameInfo[FrameInfoIndex::kIssueDrawCommandsStart] = systemTime(CLOCK_MONOTONIC);
|
||||
}
|
||||
|
||||
void markSwapBuffers() {
|
||||
mFrameInfo[FrameInfoIndex::kSwapBuffers] = systemTime(CLOCK_MONOTONIC);
|
||||
}
|
||||
|
||||
void markFrameCompleted() {
|
||||
mFrameInfo[FrameInfoIndex::kFrameCompleted] = systemTime(CLOCK_MONOTONIC);
|
||||
}
|
||||
|
||||
int64_t operator[](FrameInfoIndexEnum index) const {
|
||||
if (index == FrameInfoIndex::kNumIndexes) return 0;
|
||||
return mFrameInfo[static_cast<int>(index)];
|
||||
}
|
||||
|
||||
int64_t operator[](int index) const {
|
||||
if (index < 0 || index >= FrameInfoIndex::kNumIndexes) return 0;
|
||||
return mFrameInfo[static_cast<int>(index)];
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t mFrameInfo[FrameInfoIndex::kNumIndexes];
|
||||
};
|
||||
|
||||
} /* namespace uirenderer */
|
||||
} /* namespace android */
|
||||
|
||||
#endif /* FRAMEINFO_H_ */
|
||||
@@ -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 <algorithm>
|
||||
#include <cstdio>
|
||||
#include <inttypes.h>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
static const char* JANK_TYPE_NAMES[] = {
|
||||
"Missed Vsync",
|
||||
"High input latency",
|
||||
"Slow UI thread",
|
||||
"Slow bitmap uploads",
|
||||
"Slow draw",
|
||||
};
|
||||
|
||||
struct Comparison {
|
||||
FrameInfoIndexEnum start;
|
||||
FrameInfoIndexEnum end;
|
||||
};
|
||||
|
||||
static const Comparison COMPARISONS[] = {
|
||||
{FrameInfoIndex::kIntendedVsync, FrameInfoIndex::kVsync},
|
||||
{FrameInfoIndex::kOldestInputEvent, FrameInfoIndex::kVsync},
|
||||
{FrameInfoIndex::kVsync, FrameInfoIndex::kSyncStart},
|
||||
{FrameInfoIndex::kSyncStart, FrameInfoIndex::kIssueDrawCommandsStart},
|
||||
{FrameInfoIndex::kIssueDrawCommandsStart, FrameInfoIndex::kFrameCompleted},
|
||||
};
|
||||
|
||||
// If the event exceeds 10 seconds throw it away, this isn't a jank event
|
||||
// it's an ANR and will be handled as such
|
||||
static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10);
|
||||
|
||||
/*
|
||||
* Frames that are exempt from jank metrics.
|
||||
* First-draw frames, for example, are expected to
|
||||
* be slow, this is hidden from the user with window animations and
|
||||
* other tricks
|
||||
*
|
||||
* Similarly, we don't track direct-drawing via Surface:lockHardwareCanvas()
|
||||
* for now
|
||||
*
|
||||
* TODO: kSurfaceCanvas can negatively impact other drawing by using up
|
||||
* time on the RenderThread, figure out how to attribute that as a jank-causer
|
||||
*/
|
||||
static const int64_t EXEMPT_FRAMES_FLAGS
|
||||
= FrameInfoFlags::kWindowLayoutChanged
|
||||
| FrameInfoFlags::kSurfaceCanvas;
|
||||
|
||||
JankTracker::JankTracker(nsecs_t frameIntervalNanos) {
|
||||
reset();
|
||||
setFrameInterval(frameIntervalNanos);
|
||||
}
|
||||
|
||||
void JankTracker::setFrameInterval(nsecs_t frameInterval) {
|
||||
mFrameInterval = frameInterval;
|
||||
mThresholds[kMissedVsync] = 1;
|
||||
/*
|
||||
* Due to interpolation and sample rate differences between the touch
|
||||
* panel and the display (example, 85hz touch panel driving a 60hz display)
|
||||
* we call high latency 1.5 * frameinterval
|
||||
*
|
||||
* NOTE: Be careful when tuning this! A theoretical 1,000hz touch panel
|
||||
* on a 60hz display will show kOldestInputEvent - kIntendedVsync of being 15ms
|
||||
* Thus this must always be larger than frameInterval, or it will fail
|
||||
*/
|
||||
mThresholds[kHighInputLatency] = static_cast<int64_t>(1.5 * frameInterval);
|
||||
|
||||
// Note that these do not add up to 1. This is intentional. It's to deal
|
||||
// with variance in values, and should be sort of an upper-bound on what
|
||||
// is reasonable to expect.
|
||||
mThresholds[kSlowUI] = static_cast<int64_t>(.5 * frameInterval);
|
||||
mThresholds[kSlowSync] = static_cast<int64_t>(.2 * frameInterval);
|
||||
mThresholds[kSlowRT] = static_cast<int64_t>(.75 * frameInterval);
|
||||
|
||||
}
|
||||
|
||||
void JankTracker::addFrame(const FrameInfo& frame) {
|
||||
using namespace FrameInfoIndex;
|
||||
mTotalFrameCount++;
|
||||
// Fast-path for jank-free frames
|
||||
int64_t totalDuration = frame[kFrameCompleted] - frame[kIntendedVsync];
|
||||
uint32_t framebucket = std::min(
|
||||
static_cast<typeof sizeof(mFrameCounts)>(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 */
|
||||
@@ -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 <memory>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
enum JankType {
|
||||
kMissedVsync = 0,
|
||||
kHighInputLatency,
|
||||
kSlowUI,
|
||||
kSlowSync,
|
||||
kSlowRT,
|
||||
|
||||
// must be last
|
||||
NUM_BUCKETS,
|
||||
};
|
||||
|
||||
struct JankBucket {
|
||||
// Number of frames that hit this bucket
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
// TODO: Replace DrawProfiler with this
|
||||
class JankTracker {
|
||||
public:
|
||||
JankTracker(nsecs_t frameIntervalNanos);
|
||||
|
||||
void setFrameInterval(nsecs_t frameIntervalNanos);
|
||||
|
||||
void addFrame(const FrameInfo& frame);
|
||||
|
||||
void dump(int fd);
|
||||
void reset();
|
||||
|
||||
private:
|
||||
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_ */
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 <set>
|
||||
|
||||
#include <cutils/compiler.h>
|
||||
#include <EGL/egl.h>
|
||||
@@ -32,7 +25,14 @@
|
||||
#include <utils/Functor.h>
|
||||
#include <utils/Vector.h>
|
||||
|
||||
#include <set>
|
||||
#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 @@ public:
|
||||
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 @@ public:
|
||||
|
||||
DrawProfiler& profiler() { return mProfiler; }
|
||||
|
||||
void dumpFrames(int fd);
|
||||
void resetFrameStats();
|
||||
|
||||
private:
|
||||
friend class RegisterFrameCallbackTask;
|
||||
// TODO: Replace with something better for layer & other GL object
|
||||
@@ -136,9 +133,6 @@ private:
|
||||
const sp<RenderNode> mRootRenderNode;
|
||||
|
||||
DrawProfiler mProfiler;
|
||||
FrameInfo* mCurrentFrameInfo;
|
||||
// Ring buffer large enough for 1 second worth of frames
|
||||
RingBuffer<FrameInfo, 60> mFrames;
|
||||
|
||||
std::set<RenderNode*> mPrefetechedLayers;
|
||||
};
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "RenderTask.h"
|
||||
|
||||
#include "../Rect.h"
|
||||
#include "../FrameInfo.h"
|
||||
#include "../TreeInfo.h"
|
||||
|
||||
namespace android {
|
||||
@@ -63,9 +62,7 @@ public:
|
||||
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 @@ private:
|
||||
/*********************************************
|
||||
* Single frame data
|
||||
*********************************************/
|
||||
nsecs_t mFrameTimeNanos;
|
||||
nsecs_t mRecordDurationNanos;
|
||||
float mDensity;
|
||||
std::vector< sp<DeferredLayerUpdater> > mLayers;
|
||||
|
||||
int mSyncResult;
|
||||
|
||||
int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE];
|
||||
};
|
||||
|
||||
} /* namespace renderthread */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,10 +71,10 @@ public:
|
||||
ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
|
||||
ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window);
|
||||
ANDROID_API void setup(int width, int height, const Vector3& lightCenter, float lightRadius,
|
||||
uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha, 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 @@ public:
|
||||
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<GraphicBuffer>& buffer, int64_t* map, size_t size);
|
||||
|
||||
@@ -143,8 +143,7 @@ RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
|
||||
, 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) {
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
|
||||
#include "RenderTask.h"
|
||||
|
||||
#include "../JankTracker.h"
|
||||
#include "TimeLord.h"
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include <cutils/compiler.h>
|
||||
#include <utils/Looper.h>
|
||||
@@ -28,8 +28,7 @@
|
||||
#include <utils/Singleton.h>
|
||||
#include <utils/Thread.h>
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include "TimeLord.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
@@ -86,12 +85,9 @@ public:
|
||||
// the next vsync. If it is not currently registered this does nothing.
|
||||
void pushBackFrameCallback(IFrameCallback* callback);
|
||||
|
||||
void setFrameInterval(nsecs_t frameInterval);
|
||||
|
||||
TimeLord& timeLord() { return mTimeLord; }
|
||||
RenderState& renderState() { return *mRenderState; }
|
||||
EglManager& eglManager() { return *mEglManager; }
|
||||
JankTracker& jankTracker() { return *mJankTracker; }
|
||||
|
||||
protected:
|
||||
virtual bool threadLoop();
|
||||
@@ -136,8 +132,6 @@ private:
|
||||
TimeLord mTimeLord;
|
||||
RenderState* mRenderState;
|
||||
EglManager* mEglManager;
|
||||
|
||||
JankTracker* mJankTracker;
|
||||
};
|
||||
|
||||
} /* namespace renderthread */
|
||||
|
||||
@@ -32,7 +32,7 @@ bool TimeLord::vsyncReceived(nsecs_t vsync) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsecs_t TimeLord::computeFrameTimeNanos() {
|
||||
nsecs_t TimeLord::computeFrameTimeMs() {
|
||||
// Logic copied from Choreographer.java
|
||||
nsecs_t now = systemTime(CLOCK_MONOTONIC);
|
||||
nsecs_t jitterNanos = now - mFrameTimeNanos;
|
||||
@@ -40,11 +40,7 @@ nsecs_t TimeLord::computeFrameTimeNanos() {
|
||||
nsecs_t lastFrameOffset = jitterNanos % mFrameIntervalNanos;
|
||||
mFrameTimeNanos = now - lastFrameOffset;
|
||||
}
|
||||
return mFrameTimeNanos;
|
||||
}
|
||||
|
||||
nsecs_t TimeLord::computeFrameTimeMs() {
|
||||
return nanoseconds_to_milliseconds(computeFrameTimeNanos());
|
||||
return nanoseconds_to_milliseconds(mFrameTimeNanos);
|
||||
}
|
||||
|
||||
} /* namespace renderthread */
|
||||
|
||||
@@ -29,13 +29,9 @@ class RenderThread;
|
||||
class TimeLord {
|
||||
public:
|
||||
void setFrameInterval(nsecs_t intervalNanos) { mFrameIntervalNanos = intervalNanos; }
|
||||
nsecs_t frameIntervalNanos() const { return mFrameIntervalNanos; }
|
||||
|
||||
// returns true if the vsync is newer, false if it was rejected for staleness
|
||||
bool vsyncReceived(nsecs_t vsync);
|
||||
nsecs_t latestVsync() { return mFrameTimeNanos; }
|
||||
nsecs_t computeFrameTimeMs();
|
||||
nsecs_t computeFrameTimeNanos();
|
||||
|
||||
private:
|
||||
friend class RenderThread;
|
||||
|
||||
@@ -88,7 +88,7 @@ int main(int argc, char* argv[]) {
|
||||
proxy->initialize(surface);
|
||||
float lightX = width / 2.0;
|
||||
proxy->setup(width, height, (Vector3){lightX, dp(-200.0f), dp(800.0f)},
|
||||
dp(800.0f), 255 * 0.075, 255 * 0.15, 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 <stddef.h>
|
||||
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
template<class T, size_t SIZE>
|
||||
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_ */
|
||||
Reference in New Issue
Block a user