expose hwui frame stats through FrameStatsObserver
Change-Id: I88884bafc8e2f6d7f67a36d3609490e83cf8afd5
diff --git a/core/java/android/view/FrameStatsObserver.java b/core/java/android/view/FrameStatsObserver.java
new file mode 100644
index 0000000..0add607
--- /dev/null
+++ b/core/java/android/view/FrameStatsObserver.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 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.NonNull;
+import android.util.Log;
+import android.os.Looper;
+import android.os.MessageQueue;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+import java.lang.NullPointerException;
+import java.lang.ref.WeakReference;
+import java.lang.SuppressWarnings;
+
+/**
+ * Provides streaming access to frame stats information from the rendering
+ * subsystem to apps.
+ *
+ * @hide
+ */
+public abstract class FrameStatsObserver {
+ private static final String TAG = "FrameStatsObserver";
+
+ private MessageQueue mMessageQueue;
+ private long[] mBuffer;
+
+ private FrameStats mFrameStats;
+
+ /* package */ ThreadedRenderer mRenderer;
+ /* package */ VirtualRefBasePtr mNative;
+
+ /**
+ * Containing class for frame statistics reported
+ * by the rendering subsystem.
+ */
+ public static class FrameStats {
+ /**
+ * Precise timing data for various milestones in a frame
+ * lifecycle.
+ *
+ * This data is exactly the same as what is returned by
+ * `adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats`
+ *
+ * The fields reported may change from release to release.
+ *
+ * @see {@link http://developer.android.com/training/testing/performance.html}
+ * for a description of the fields present.
+ */
+ public long[] mTimingData;
+ }
+
+ /**
+ * Creates a FrameStatsObserver
+ *
+ * @param looper the looper to use when invoking callbacks
+ */
+ public FrameStatsObserver(@NonNull Looper looper) {
+ if (looper == null) {
+ throw new NullPointerException("looper cannot be null");
+ }
+
+ mMessageQueue = looper.getQueue();
+ if (mMessageQueue == null) {
+ throw new IllegalStateException("invalid looper, null message queue\n");
+ }
+
+ mFrameStats = new FrameStats();
+ }
+
+ /**
+ * Called on provided looper when frame stats data is available
+ * for the previous frame.
+ *
+ * Clients of this class must do as little work as possible within
+ * this callback, as the buffer is shared between the producer and consumer.
+ *
+ * If the consumer is still executing within this method when there is new
+ * data available that data will be dropped. The producer cannot
+ * wait on the consumer.
+ *
+ * @param data the newly available data
+ */
+ public abstract void onDataAvailable(FrameStats data);
+
+ /**
+ * Returns the number of reports dropped as a result of a slow
+ * consumer.
+ */
+ public long getDroppedReportCount() {
+ if (mRenderer == null) {
+ return 0;
+ }
+
+ return mRenderer.getDroppedFrameReportCount();
+ }
+
+ public boolean isRegistered() {
+ return mRenderer != null && mNative != null;
+ }
+
+ // === called by native === //
+ @SuppressWarnings("unused")
+ private void notifyDataAvailable() {
+ mFrameStats.mTimingData = mBuffer;
+ onDataAvailable(mFrameStats);
+ }
+}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 0e4bc84..78a63a6 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -24,7 +24,9 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -34,12 +36,14 @@
import android.view.View.AttachInfo;
import com.android.internal.R;
+import com.android.internal.util.VirtualRefBasePtr;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
/**
* Hardware renderer that proxies the rendering to a render thread. Most calls
@@ -339,6 +343,8 @@
private boolean mEnabled;
private boolean mRequested = true;
+ private HashSet<FrameStatsObserver> mFrameStatsObservers;
+
ThreadedRenderer(Context context, boolean translucent) {
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
@@ -947,6 +953,31 @@
}
}
+ void addFrameStatsObserver(FrameStatsObserver fso) {
+ if (mFrameStatsObservers == null) {
+ mFrameStatsObservers = new HashSet<>();
+ }
+
+ long nativeFso = nAddFrameStatsObserver(mNativeProxy, fso);
+ fso.mRenderer = this;
+ fso.mNative = new VirtualRefBasePtr(nativeFso);
+ mFrameStatsObservers.add(fso);
+ }
+
+ void removeFrameStatsObserver(FrameStatsObserver fso) {
+ if (!mFrameStatsObservers.remove(fso)) {
+ throw new IllegalArgumentException("attempt to remove FrameStatsObserver that was never added");
+ }
+
+ nRemoveFrameStatsObserver(mNativeProxy, fso.mNative.get());
+ fso.mRenderer = null;
+ fso.mNative = null;
+ }
+
+ long getDroppedFrameReportCount() {
+ return nGetDroppedFrameReportCount(mNativeProxy);
+ }
+
static native void setupShadersDiskCache(String cacheFile);
private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
@@ -1000,4 +1031,8 @@
private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
private static native void nSetContentDrawBounds(long nativeProxy, int left,
int top, int right, int bottom);
+
+ private static native long nAddFrameStatsObserver(long nativeProxy, FrameStatsObserver fso);
+ private static native void nRemoveFrameStatsObserver(long nativeProxy, long nativeFso);
+ private static native long nGetDroppedFrameReportCount(long nativeProxy);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b5b0baa..1b8549b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -109,6 +109,7 @@
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import java.lang.NullPointerException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -5380,6 +5381,36 @@
}
/**
+ * Set an observer to collect stats for each frame rendered for this view.
+ *
+ * @hide
+ */
+ public void addFrameStatsObserver(FrameStatsObserver fso) {
+ if (mAttachInfo != null) {
+ if (mAttachInfo.mHardwareRenderer != null) {
+ mAttachInfo.mHardwareRenderer.addFrameStatsObserver(fso);
+ } else {
+ throw new IllegalStateException("View must be hardware-accelerated");
+ }
+ } else {
+ // TODO: store as pending registration and merge when we are attached to a surface
+ throw new IllegalStateException("View not yet attached");
+ }
+ }
+
+ /**
+ * Remove observer configured to collect frame stats for this view.
+ *
+ * @hide
+ */
+ public void removeFrameStatsObserver(FrameStatsObserver fso) {
+ ThreadedRenderer renderer = getHardwareRenderer();
+ if (renderer != null) {
+ renderer.removeFrameStatsObserver(fso);
+ }
+ }
+
+ /**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index dfe0cc7..ee70891 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -34,6 +34,7 @@
import android.media.session.MediaController;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -794,6 +795,40 @@
return mCallback;
}
+ /**
+ * Set an observer to collect frame stats for each frame rendererd in this window.
+ *
+ * Must be in hardware rendering mode.
+ * @hide
+ */
+ public final void addFrameStatsObserver(@NonNull FrameStatsObserver fso) {
+ final View decorView = getDecorView();
+ if (decorView == null) {
+ throw new IllegalStateException("can't observe a Window without an attached view");
+ }
+
+ if (fso == null) {
+ throw new NullPointerException("FrameStatsObserver cannot be null");
+ }
+
+ if (fso.isRegistered()) {
+ throw new IllegalStateException("FrameStatsObserver already registered on a Window.");
+ }
+
+ decorView.addFrameStatsObserver(fso);
+ }
+
+ /**
+ * Remove observer and stop listening to frame stats for this window.
+ * @hide
+ */
+ public final void removeFrameStatsObserver(FrameStatsObserver fso) {
+ final View decorView = getDecorView();
+ if (decorView != null) {
+ getDecorView().removeFrameStatsObserver(fso);
+ }
+ }
+
/** @hide */
public final void setOnWindowDismissedCallback(OnWindowDismissedCallback dcb) {
mOnWindowDismissedCallback = dcb;
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 5aa6a73..edced56 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -28,14 +28,18 @@
#include <EGL/eglext.h>
#include <EGL/egl_cache.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
#include <utils/StrongPointer.h>
#include <android_runtime/android_view_Surface.h>
#include <system/window.h>
#include "android_view_GraphicBuffer.h"
+#include "android_os_MessageQueue.h"
#include <Animator.h>
#include <AnimationContext.h>
+#include <FrameInfo.h>
#include <IContextFactory.h>
#include <JankTracker.h>
#include <RenderNode.h>
@@ -50,6 +54,12 @@
using namespace android::uirenderer;
using namespace android::uirenderer::renderthread;
+struct {
+ jfieldID buffer;
+ jfieldID messageQueue;
+ jmethodID notifyData;
+} gFrameStatsObserverClassInfo;
+
static JNIEnv* getenv(JavaVM* vm) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -207,6 +217,99 @@
RootRenderNode* mRootNode;
};
+class ObserverProxy;
+
+class NotifyHandler : public MessageHandler {
+public:
+ NotifyHandler(JavaVM* vm) : mVm(vm) {}
+
+ void setObserver(ObserverProxy* observer) {
+ mObserver = observer;
+ }
+
+ void setBuffer(BufferPool::Buffer* buffer) {
+ mBuffer = buffer;
+ }
+
+ virtual void handleMessage(const Message& message);
+
+private:
+ JavaVM* mVm;
+
+ sp<ObserverProxy> mObserver;
+ BufferPool::Buffer* mBuffer;
+};
+
+class ObserverProxy : public FrameStatsObserver {
+public:
+ ObserverProxy(JavaVM *vm, jobject fso) : mVm(vm) {
+ JNIEnv* env = getenv(mVm);
+
+ jlongArray longArrayLocal = env->NewLongArray(kBufferSize);
+ LOG_ALWAYS_FATAL_IF(longArrayLocal == nullptr,
+ "OOM: can't allocate frame stats buffer");
+ env->SetObjectField(fso, gFrameStatsObserverClassInfo.buffer, longArrayLocal);
+
+ mFsoWeak = env->NewWeakGlobalRef(fso);
+ LOG_ALWAYS_FATAL_IF(mFsoWeak == nullptr,
+ "unable to create frame stats observer reference");
+
+ jobject messageQueueLocal =
+ env->GetObjectField(fso, gFrameStatsObserverClassInfo.messageQueue);
+ mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
+ LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
+
+ mMessageHandler = new NotifyHandler(mVm);
+ LOG_ALWAYS_FATAL_IF(mMessageHandler == nullptr,
+ "OOM: unable to allocate NotifyHandler");
+ }
+
+ ~ObserverProxy() {
+ JNIEnv* env = getenv(mVm);
+ env->DeleteWeakGlobalRef(mFsoWeak);
+ }
+
+ jweak getJavaObjectRef() {
+ return mFsoWeak;
+ }
+
+ virtual void notify(BufferPool::Buffer* buffer) {
+ buffer->incRef();
+ mMessageHandler->setBuffer(buffer);
+ mMessageHandler->setObserver(this);
+ mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
+ }
+
+private:
+ static const int kBufferSize = static_cast<int>(FrameInfoIndex::NumIndexes);
+
+ JavaVM* mVm;
+ jweak mFsoWeak;
+
+ sp<MessageQueue> mMessageQueue;
+ sp<NotifyHandler> mMessageHandler;
+ Message mMessage;
+};
+
+void NotifyHandler::handleMessage(const Message& message) {
+ JNIEnv* env = getenv(mVm);
+
+ jobject target = env->NewLocalRef(mObserver->getJavaObjectRef());
+
+ if (target != nullptr) {
+ jobject javaBuffer = env->GetObjectField(target, gFrameStatsObserverClassInfo.buffer);
+ if (javaBuffer != nullptr) {
+ env->SetLongArrayRegion(reinterpret_cast<jlongArray>(javaBuffer),
+ 0, mBuffer->getSize(), mBuffer->getBuffer());
+ env->CallVoidMethod(target, gFrameStatsObserverClassInfo.notifyData);
+ env->DeleteLocalRef(target);
+ }
+ }
+
+ mBuffer->release();
+ mObserver.clear();
+}
+
static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz,
jlong proxyPtr, jobject graphicBuffer, jlongArray atlasMapArray) {
sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer);
@@ -468,6 +571,42 @@
}
// ----------------------------------------------------------------------------
+// FrameStatsObserver
+// ----------------------------------------------------------------------------
+
+static jlong android_view_ThreadedRenderer_addFrameStatsObserver(JNIEnv* env,
+ jclass clazz, jlong proxyPtr, jobject fso) {
+ JavaVM* vm = nullptr;
+ if (env->GetJavaVM(&vm) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Unable to get Java VM");
+ return 0;
+ }
+
+ renderthread::RenderProxy* renderProxy =
+ reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+
+ FrameStatsObserver* observer = new ObserverProxy(vm, fso);
+ renderProxy->addFrameStatsObserver(observer);
+ return reinterpret_cast<jlong>(observer);
+}
+
+static void android_view_ThreadedRenderer_removeFrameStatsObserver(JNIEnv* env, jclass clazz,
+ jlong proxyPtr, jlong observerPtr) {
+ FrameStatsObserver* observer = reinterpret_cast<FrameStatsObserver*>(observerPtr);
+ renderthread::RenderProxy* renderProxy =
+ reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+
+ renderProxy->removeFrameStatsObserver(observer);
+}
+
+static jint android_view_ThreadedRenderer_getDroppedFrameReportCount(JNIEnv* env, jclass clazz,
+ jlong proxyPtr) {
+ renderthread::RenderProxy* renderProxy =
+ reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+ return renderProxy->getDroppedFrameReportCount();
+}
+
+// ----------------------------------------------------------------------------
// Shaders
// ----------------------------------------------------------------------------
@@ -523,9 +662,26 @@
{ "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
{ "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
{ "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
+ { "nAddFrameStatsObserver",
+ "(JLandroid/view/FrameStatsObserver;)J",
+ (void*)android_view_ThreadedRenderer_addFrameStatsObserver },
+ { "nRemoveFrameStatsObserver",
+ "(JJ)V",
+ (void*)android_view_ThreadedRenderer_removeFrameStatsObserver },
+ { "nGetDroppedFrameReportCount",
+ "(J)J",
+ (void*)android_view_ThreadedRenderer_getDroppedFrameReportCount },
};
int register_android_view_ThreadedRenderer(JNIEnv* env) {
+ jclass clazz = FindClassOrDie(env, "android/view/FrameStatsObserver");
+ gFrameStatsObserverClassInfo.messageQueue =
+ GetFieldIDOrDie(env, clazz, "mMessageQueue", "Landroid/os/MessageQueue;");
+ gFrameStatsObserverClassInfo.buffer =
+ GetFieldIDOrDie(env, clazz, "mBuffer", "[J");
+ gFrameStatsObserverClassInfo.notifyData =
+ GetMethodIDOrDie(env, clazz, "notifyDataAvailable", "()V");
+
return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
}
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8ba6318..483ccf7 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -235,7 +235,8 @@
tests/unit/LinearAllocatorTests.cpp \
tests/unit/VectorDrawableTests.cpp \
tests/unit/OffscreenBufferPoolTests.cpp \
- tests/unit/StringUtilsTests.cpp
+ tests/unit/StringUtilsTests.cpp \
+ tests/unit/BufferPoolTests.cpp
ifeq (true, $(HWUI_NEW_OPS))
LOCAL_SRC_FILES += \
diff --git a/libs/hwui/BufferPool.h b/libs/hwui/BufferPool.h
new file mode 100644
index 0000000..9bda233
--- /dev/null
+++ b/libs/hwui/BufferPool.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <utils/RefBase.h>
+#include <utils/Log.h>
+
+#include <atomic>
+#include <stdint.h>
+#include <memory>
+#include <mutex>
+
+namespace android {
+namespace uirenderer {
+
+/*
+ * Simple thread-safe pool of int64_t arrays of a provided size.
+ *
+ * Permits allocating a client-provided max number of buffers.
+ * If all buffers are in use, refuses to service any more
+ * acquire requests until buffers are re-released to the pool.
+ */
+class BufferPool : public VirtualLightRefBase {
+public:
+ class Buffer {
+ public:
+ int64_t* getBuffer() { return mBuffer.get(); }
+ size_t getSize() { return mSize; }
+
+ void release() {
+ LOG_ALWAYS_FATAL_IF(mPool.get() == nullptr, "attempt to release unacquired buffer");
+ mPool->release(this);
+ }
+
+ Buffer* incRef() {
+ mRefs++;
+ return this;
+ }
+
+ int decRef() {
+ int refs = mRefs.fetch_sub(1);
+ LOG_ALWAYS_FATAL_IF(refs == 0, "buffer reference decremented below 0");
+ return refs - 1;
+ }
+
+ private:
+ friend class BufferPool;
+
+ Buffer(BufferPool* pool, size_t size) {
+ mSize = size;
+ mBuffer.reset(new int64_t[size]);
+ mPool = pool;
+ mRefs++;
+ }
+
+ void setPool(BufferPool* pool) {
+ mPool = pool;
+ }
+
+ std::unique_ptr<Buffer> mNext;
+ std::unique_ptr<int64_t[]> mBuffer;
+ sp<BufferPool> mPool;
+ size_t mSize;
+
+ std::atomic_int mRefs;
+ };
+
+ BufferPool(size_t bufferSize, size_t count)
+ : mBufferSize(bufferSize), mCount(count) {}
+
+ /**
+ * Acquires a buffer from the buffer pool if available.
+ *
+ * Only `mCount` buffers are allowed to be in use at a single
+ * instance.
+ *
+ * If no buffer is available, i.e. `mCount` buffers are in use,
+ * returns nullptr.
+ *
+ * The pointer returned from this method *MUST NOT* be freed, instead
+ * BufferPool::release() must be called upon it when the client
+ * is done with it. Failing to release buffers will eventually make the
+ * BufferPool refuse to service any more BufferPool::acquire() requests.
+ */
+ BufferPool::Buffer* acquire() {
+ std::lock_guard<std::mutex> lock(mLock);
+
+ if (mHead.get() != nullptr) {
+ BufferPool::Buffer* res = mHead.release();
+ mHead = std::move(res->mNext);
+ res->mNext.reset(nullptr);
+ res->setPool(this);
+ res->incRef();
+ return res;
+ }
+
+ if (mAllocatedCount < mCount) {
+ ++mAllocatedCount;
+ return new BufferPool::Buffer(this, mBufferSize);
+ }
+
+ return nullptr;
+ }
+
+ /**
+ * Releases a buffer previously acquired by BufferPool::acquire().
+ *
+ * The released buffer is not valid after calling this method and
+ * attempting to use will result in undefined behavior.
+ */
+ void release(BufferPool::Buffer* buffer) {
+ std::lock_guard<std::mutex> lock(mLock);
+
+ if (buffer->decRef() != 0) {
+ return;
+ }
+
+ buffer->setPool(nullptr);
+
+ BufferPool::Buffer* list = mHead.get();
+ if (list == nullptr) {
+ mHead.reset(buffer);
+ mHead->mNext.reset(nullptr);
+ return;
+ }
+
+ while (list->mNext.get() != nullptr) {
+ list = list->mNext.get();
+ }
+
+ list->mNext.reset(buffer);
+ }
+
+ /*
+ * Used for testing.
+ */
+ size_t getAvailableBufferCount() {
+ size_t remainingToAllocateCount = mCount - mAllocatedCount;
+
+ BufferPool::Buffer* list = mHead.get();
+ if (list == nullptr) return remainingToAllocateCount;
+
+ int count = 1;
+ while (list->mNext.get() != nullptr) {
+ count++;
+ list = list->mNext.get();
+ }
+
+ return count + remainingToAllocateCount;
+ }
+
+private:
+ mutable std::mutex mLock;
+
+ size_t mBufferSize;
+ size_t mCount;
+ size_t mAllocatedCount = 0;
+ std::unique_ptr<BufferPool::Buffer> mHead;
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index f8013ab6..0baca39 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -118,6 +118,10 @@
set(FrameInfoIndex::Flags) |= static_cast<uint64_t>(frameInfoFlag);
}
+ const int64_t* data() const {
+ return mFrameInfo;
+ }
+
inline int64_t operator[](FrameInfoIndex index) const {
return get(index);
}
diff --git a/libs/hwui/FrameStatsObserver.h b/libs/hwui/FrameStatsObserver.h
new file mode 100644
index 0000000..7abc9f1
--- /dev/null
+++ b/libs/hwui/FrameStatsObserver.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <utils/RefBase.h>
+
+#include "BufferPool.h"
+
+namespace android {
+namespace uirenderer {
+
+class FrameStatsObserver : public VirtualLightRefBase {
+public:
+ virtual void notify(BufferPool::Buffer* buffer);
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FrameStatsReporter.h b/libs/hwui/FrameStatsReporter.h
new file mode 100644
index 0000000..b8a9432
--- /dev/null
+++ b/libs/hwui/FrameStatsReporter.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <utils/RefBase.h>
+#include <utils/Log.h>
+
+#include "BufferPool.h"
+#include "FrameInfo.h"
+#include "FrameStatsObserver.h"
+
+#include <string.h>
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+class FrameStatsReporter {
+public:
+ FrameStatsReporter() {
+ mBufferPool = new BufferPool(kBufferSize, kBufferCount);
+ LOG_ALWAYS_FATAL_IF(mBufferPool.get() == nullptr, "OOM: unable to allocate buffer pool");
+ }
+
+ void addObserver(FrameStatsObserver* observer) {
+ mObservers.push_back(observer);
+ }
+
+ bool removeObserver(FrameStatsObserver* observer) {
+ for (size_t i = 0; i < mObservers.size(); i++) {
+ if (mObservers[i].get() == observer) {
+ mObservers.erase(mObservers.begin() + i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool hasObservers() {
+ return mObservers.size() > 0;
+ }
+
+ void reportFrameStats(const int64_t* stats) {
+ BufferPool::Buffer* statsBuffer = mBufferPool->acquire();
+
+ if (statsBuffer != nullptr) {
+ // copy in frame stats
+ memcpy(statsBuffer->getBuffer(), stats, kBufferSize * sizeof(*stats));
+
+ // notify on requested threads
+ for (size_t i = 0; i < mObservers.size(); i++) {
+ mObservers[i]->notify(statsBuffer);
+ }
+
+ // drop our reference
+ statsBuffer->release();
+ } else {
+ mDroppedReports++;
+ }
+ }
+
+ int getDroppedReports() { return mDroppedReports; }
+
+private:
+ static const size_t kBufferCount = 3;
+ static const size_t kBufferSize = static_cast<size_t>(FrameInfoIndex::NumIndexes);
+
+ std::vector< sp<FrameStatsObserver> > mObservers;
+
+ sp<BufferPool> mBufferPool;
+
+ int mDroppedReports = 0;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 6f8d627..cdd2da0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -505,6 +505,9 @@
mJankTracker.addFrame(*mCurrentFrameInfo);
mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
+ if (CC_UNLIKELY(mFrameStatsReporter.get() != nullptr)) {
+ mFrameStatsReporter->reportFrameStats(mCurrentFrameInfo->data());
+ }
GpuMemoryTracker::onFrameCompleted();
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 8e64cbb..270fb1f 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -20,6 +20,7 @@
#include "DamageAccumulator.h"
#include "FrameInfo.h"
#include "FrameInfoVisualizer.h"
+#include "FrameStatsReporter.h"
#include "IContextFactory.h"
#include "LayerUpdateQueue.h"
#include "RenderNode.h"
@@ -139,6 +140,31 @@
return mRenderThread.renderState();
}
+ void addFrameStatsObserver(FrameStatsObserver* observer) {
+ if (mFrameStatsReporter.get() == nullptr) {
+ mFrameStatsReporter.reset(new FrameStatsReporter());
+ }
+
+ mFrameStatsReporter->addObserver(observer);
+ }
+
+ void removeFrameStatsObserver(FrameStatsObserver* observer) {
+ if (mFrameStatsReporter.get() != nullptr) {
+ mFrameStatsReporter->removeObserver(observer);
+ if (!mFrameStatsReporter->hasObservers()) {
+ mFrameStatsReporter.reset(nullptr);
+ }
+ }
+ }
+
+ long getDroppedFrameReportCount() {
+ if (mFrameStatsReporter.get() != nullptr) {
+ return mFrameStatsReporter->getDroppedReports();
+ }
+
+ return 0;
+ }
+
private:
friend class RegisterFrameCallbackTask;
// TODO: Replace with something better for layer & other GL object
@@ -187,6 +213,7 @@
std::string mName;
JankTracker mJankTracker;
FrameInfoVisualizer mProfiler;
+ std::unique_ptr<FrameStatsReporter> mFrameStatsReporter;
std::set<RenderNode*> mPrefetechedLayers;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index db2a2c8..1d1b144 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -568,6 +568,54 @@
post(task);
}
+CREATE_BRIDGE2(addFrameStatsObserver, CanvasContext* context,
+ FrameStatsObserver* frameStatsObserver) {
+ args->context->addFrameStatsObserver(args->frameStatsObserver);
+ if (args->frameStatsObserver != nullptr) {
+ args->frameStatsObserver->decStrong(args->context);
+ }
+ return nullptr;
+}
+
+void RenderProxy::addFrameStatsObserver(FrameStatsObserver* observer) {
+ SETUP_TASK(addFrameStatsObserver);
+ args->context = mContext;
+ args->frameStatsObserver = observer;
+ if (observer != nullptr) {
+ observer->incStrong(mContext);
+ }
+ post(task);
+}
+
+CREATE_BRIDGE2(removeFrameStatsObserver, CanvasContext* context,
+ FrameStatsObserver* frameStatsObserver) {
+ args->context->removeFrameStatsObserver(args->frameStatsObserver);
+ if (args->frameStatsObserver != nullptr) {
+ args->frameStatsObserver->decStrong(args->context);
+ }
+ return nullptr;
+}
+
+void RenderProxy::removeFrameStatsObserver(FrameStatsObserver* observer) {
+ SETUP_TASK(removeFrameStatsObserver);
+ args->context = mContext;
+ args->frameStatsObserver = observer;
+ if (observer != nullptr) {
+ observer->incStrong(mContext);
+ }
+ post(task);
+}
+
+CREATE_BRIDGE1(getDroppedFrameReportCount, CanvasContext* context) {
+ return (void*) args->context->getDroppedFrameReportCount();
+}
+
+long RenderProxy::getDroppedFrameReportCount() {
+ SETUP_TASK(getDroppedFrameReportCount);
+ args->context = mContext;
+ return (long) postAndWait(task);
+}
+
void RenderProxy::post(RenderTask* task) {
mRenderThread.queue(task);
}
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 0f91b2a..4180d802 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -29,6 +29,7 @@
#include <utils/StrongPointer.h>
#include "../Caches.h"
+#include "../FrameStatsObserver.h"
#include "../IContextFactory.h"
#include "CanvasContext.h"
#include "DrawFrameTask.h"
@@ -112,6 +113,10 @@
ANDROID_API void drawRenderNode(RenderNode* node);
ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
+ ANDROID_API void addFrameStatsObserver(FrameStatsObserver* observer);
+ ANDROID_API void removeFrameStatsObserver(FrameStatsObserver* observer);
+ ANDROID_API long getDroppedFrameReportCount();
+
private:
RenderThread& mRenderThread;
CanvasContext* mContext;
diff --git a/libs/hwui/tests/unit/BufferPoolTests.cpp b/libs/hwui/tests/unit/BufferPoolTests.cpp
new file mode 100644
index 0000000..09bd302
--- /dev/null
+++ b/libs/hwui/tests/unit/BufferPoolTests.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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 <gtest/gtest.h>
+
+#include <BufferPool.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace uirenderer {
+
+TEST(BufferPool, acquireThenRelease) {
+ static const int numRuns = 5;
+
+ // 10 buffers of size 1
+ static const size_t bufferSize = 1;
+ static const size_t bufferCount = 10;
+ sp<BufferPool> pool = new BufferPool(bufferSize, bufferCount);
+
+ for (int run = 0; run < numRuns; run++) {
+ BufferPool::Buffer* acquiredBuffers[bufferCount];
+ for (size_t i = 0; i < bufferCount; i++) {
+ ASSERT_EQ(bufferCount - i, pool->getAvailableBufferCount());
+ acquiredBuffers[i] = pool->acquire();
+ ASSERT_NE(nullptr, acquiredBuffers[i]);
+ }
+
+ for (size_t i = 0; i < bufferCount; i++) {
+ ASSERT_EQ(i, pool->getAvailableBufferCount());
+ acquiredBuffers[i]->release();
+ acquiredBuffers[i] = nullptr;
+ }
+
+ ASSERT_EQ(bufferCount, pool->getAvailableBufferCount());
+ }
+}
+
+TEST(BufferPool, acquireReleaseInterleaved) {
+ static const int numRuns = 5;
+
+ // 10 buffers of size 1
+ static const size_t bufferSize = 1;
+ static const size_t bufferCount = 10;
+
+ sp<BufferPool> pool = new BufferPool(bufferSize, bufferCount);
+
+ for (int run = 0; run < numRuns; run++) {
+ BufferPool::Buffer* acquiredBuffers[bufferCount];
+
+ // acquire all
+ for (size_t i = 0; i < bufferCount; i++) {
+ ASSERT_EQ(bufferCount - i, pool->getAvailableBufferCount());
+ acquiredBuffers[i] = pool->acquire();
+ ASSERT_NE(nullptr, acquiredBuffers[i]);
+ }
+
+ // release half
+ for (size_t i = 0; i < bufferCount / 2; i++) {
+ ASSERT_EQ(i, pool->getAvailableBufferCount());
+ acquiredBuffers[i]->release();
+ acquiredBuffers[i] = nullptr;
+ }
+
+ const size_t expectedRemaining = bufferCount / 2;
+ ASSERT_EQ(expectedRemaining, pool->getAvailableBufferCount());
+
+ // acquire half
+ for (size_t i = 0; i < bufferCount / 2; i++) {
+ ASSERT_EQ(expectedRemaining - i, pool->getAvailableBufferCount());
+ acquiredBuffers[i] = pool->acquire();
+ }
+
+ // acquire one more, should fail
+ ASSERT_EQ(nullptr, pool->acquire());
+
+ // release all
+ for (size_t i = 0; i < bufferCount; i++) {
+ ASSERT_EQ(i, pool->getAvailableBufferCount());
+ acquiredBuffers[i]->release();
+ acquiredBuffers[i] = nullptr;
+ }
+
+ ASSERT_EQ(bufferCount, pool->getAvailableBufferCount());
+ }
+}
+
+};
+};