Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 214a1372 authored by Andres Morales's avatar Andres Morales Committed by Android (Google) Code Review
Browse files

Merge "expose hwui frame stats through FrameStatsObserver"

parents 36c297f4 06f5bc70
Loading
Loading
Loading
Loading
+122 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+35 −0
Original line number Diff line number Diff line
@@ -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);
}
+31 −0
Original line number Diff line number Diff line
@@ -110,6 +110,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,
        getListenerInfo().mOnCreateContextMenuListener = l;
    }
    /**
     * 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
+35 −0
Original line number Diff line number Diff line
@@ -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;
+156 −0
Original line number Diff line number Diff line
@@ -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);
@@ -467,6 +570,42 @@ static void android_view_ThreadedRenderer_setContentDrawBounds(JNIEnv* env,
    proxy->setContentDrawBounds(left, top, right, bottom);
}

// ----------------------------------------------------------------------------
// 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));
}

Loading