diff options
author | 2015-12-15 15:21:31 -0800 | |
---|---|---|
committer | 2016-01-22 12:58:51 -0800 | |
commit | 06f5bc70a667a02b14e31d3f53f91d3661e30666 (patch) | |
tree | 3de8a009d084a71906aa26506b209fea01ef4471 | |
parent | 0ed21de72134a2b55648104f517e44a2deff17dd (diff) |
expose hwui frame stats through FrameStatsObserver
Change-Id: I88884bafc8e2f6d7f67a36d3609490e83cf8afd5
-rw-r--r-- | core/java/android/view/FrameStatsObserver.java | 122 | ||||
-rw-r--r-- | core/java/android/view/ThreadedRenderer.java | 35 | ||||
-rw-r--r-- | core/java/android/view/View.java | 31 | ||||
-rw-r--r-- | core/java/android/view/Window.java | 35 | ||||
-rw-r--r-- | core/jni/android_view_ThreadedRenderer.cpp | 156 | ||||
-rw-r--r-- | libs/hwui/Android.mk | 3 | ||||
-rw-r--r-- | libs/hwui/BufferPool.h | 176 | ||||
-rw-r--r-- | libs/hwui/FrameInfo.h | 4 | ||||
-rw-r--r-- | libs/hwui/FrameStatsObserver.h | 32 | ||||
-rw-r--r-- | libs/hwui/FrameStatsReporter.h | 91 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.cpp | 3 | ||||
-rw-r--r-- | libs/hwui/renderthread/CanvasContext.h | 27 | ||||
-rw-r--r-- | libs/hwui/renderthread/RenderProxy.cpp | 48 | ||||
-rw-r--r-- | libs/hwui/renderthread/RenderProxy.h | 5 | ||||
-rw-r--r-- | libs/hwui/tests/unit/BufferPoolTests.cpp | 101 |
15 files changed, 868 insertions, 1 deletions
diff --git a/core/java/android/view/FrameStatsObserver.java b/core/java/android/view/FrameStatsObserver.java new file mode 100644 index 000000000000..0add6072e827 --- /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 0e4bc84a4db2..78a63a6f88e1 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -24,7 +24,9 @@ import android.graphics.Bitmap; 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.Surface.OutOfResourcesException; 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 @@ public final class ThreadedRenderer { 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 @@ public final class ThreadedRenderer { } } + 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 @@ public final class ThreadedRenderer { 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 b5b0baa00e80..1b8549b12f18 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -109,6 +109,7 @@ import com.android.internal.view.menu.MenuBuilder; 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 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * 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 dfe0cc74670b..ee70891e4dae 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -34,6 +34,7 @@ import android.graphics.drawable.Drawable; 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 @@ public abstract class Window { 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 5aa6a739751c..edced5616b66 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 @@ namespace android { 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 @@ private: 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 @@ static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env, } // ---------------------------------------------------------------------------- +// 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 @@ static const JNINativeMethod gMethods[] = { { "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 8ba6318983fc..483ccf714b51 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -235,7 +235,8 @@ LOCAL_SRC_FILES += \ 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 000000000000..9bda2333329d --- /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 f8013ab6b6c4..0baca391be79 100644 --- a/libs/hwui/FrameInfo.h +++ b/libs/hwui/FrameInfo.h @@ -118,6 +118,10 @@ public: 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 000000000000..7abc9f143a0b --- /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 000000000000..b8a9432d6507 --- /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 6f8d62757437..cdd2da0d8c2f 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -505,6 +505,9 @@ void CanvasContext::draw() { 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 8e64cbbcc130..270fb1fead83 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 @@ public: 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 @@ private: 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 db2a2c8b0e55..1d1b144bd47e 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -568,6 +568,54 @@ void RenderProxy::serializeDisplayListTree() { 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 0f91b2afe170..4180d8020179 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 @@ public: 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 000000000000..09bd302333d3 --- /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()); + } +} + +}; +}; |