From e1f57d6f44909a66c7ab0af33dbc5289287e823a Mon Sep 17 00:00:00 2001
From: Eino-Ville Talvala
Date: Thu, 29 May 2014 17:17:07 -0700
Subject: [PATCH] Camera2: Add CPU/GPU overhead measurement to legacy mode
Dumps GL and CPU processing duration and frame timestamps to a file,
whenever the device is closed or the stream configuration is changed.
- Add PerfMeasurement class to legacy mode
- Wire up minimal usage to SurfaceTextureRenderer
Change-Id: Ic9d74ca26f706780b746175aa615c7aae4ae52e7
---
.../camera2/legacy/PerfMeasurement.java | 309 ++++++++++++++++
.../legacy/SurfaceTextureRenderer.java | 101 +++++-
core/jni/Android.mk | 1 +
core/jni/AndroidRuntime.cpp | 2 +
...ardware_camera2_legacy_PerfMeasurement.cpp | 335 ++++++++++++++++++
5 files changed, 743 insertions(+), 5 deletions(-)
create mode 100644 core/java/android/hardware/camera2/legacy/PerfMeasurement.java
create mode 100644 core/jni/android_hardware_camera2_legacy_PerfMeasurement.cpp
diff --git a/core/java/android/hardware/camera2/legacy/PerfMeasurement.java b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
new file mode 100644
index 0000000000000..b930ec26f02e0
--- /dev/null
+++ b/core/java/android/hardware/camera2/legacy/PerfMeasurement.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2014 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.hardware.camera2.legacy;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * GPU and CPU performance measurement for the legacy implementation.
+ *
+ *
Measures CPU and GPU processing duration for a set of operations, and dumps
+ * the results into a file.
All calls to this object must be made within the same thread, and the same GL context.
+ * PerfMeasurement cannot be used outside of a GL context. The only exception is
+ * dumpPerformanceData, which can be called outside of a valid GL context.
+ */
+class PerfMeasurement {
+ private static final String TAG = "PerfMeasurement";
+
+ public static final int DEFAULT_MAX_QUERIES = 3;
+
+ private final long mNativeContext;
+
+ private int mCompletedQueryCount = 0;
+
+ /**
+ * Values for completed measurements
+ */
+ private ArrayList mCollectedGpuDurations = new ArrayList<>();
+ private ArrayList mCollectedCpuDurations = new ArrayList<>();
+ private ArrayList mCollectedTimestamps = new ArrayList<>();
+
+ /**
+ * Values for in-progress measurements (waiting for async GPU results)
+ */
+ private Queue mTimestampQueue = new LinkedList<>();
+ private Queue mCpuDurationsQueue = new LinkedList<>();
+
+ private long mStartTimeNs;
+
+ /**
+ * The value returned by {@link #nativeGetNextGlDuration} if no new timing
+ * measurement is available since the last call.
+ */
+ private static final long NO_DURATION_YET = -1l;
+
+ /**
+ * The value returned by {@link #nativeGetNextGlDuration} if timing failed for
+ * the next timing interval
+ */
+ private static final long FAILED_TIMING = -2l;
+
+ /**
+ * Create a performance measurement object with a maximum of {@value #DEFAULT_MAX_QUERIES}
+ * in-progess queries.
+ */
+ public PerfMeasurement() {
+ mNativeContext = nativeCreateContext(DEFAULT_MAX_QUERIES);
+ }
+
+ /**
+ * Create a performance measurement object with maxQueries as the maximum number of
+ * in-progress queries.
+ *
+ * @param maxQueries maximum in-progress queries, must be larger than 0.
+ * @throws IllegalArgumentException if maxQueries is less than 1.
+ */
+ public PerfMeasurement(int maxQueries) {
+ if (maxQueries < 1) throw new IllegalArgumentException("maxQueries is less than 1");
+ mNativeContext = nativeCreateContext(maxQueries);
+ }
+
+ /**
+ * Returns true if the Gl timing methods will work, false otherwise.
+ *
+ *
Must be called within a valid GL context.
+ */
+ public static boolean isGlTimingSupported() {
+ return nativeQuerySupport();
+ }
+
+ /**
+ * Dump collected data to file, and clear the stored data.
+ *
+ *
+ * Format is a simple csv-like text file with a header,
+ * followed by a 3-column list of values in nanoseconds:
+ *
Call before starting a rendering pass. Only one timing measurement can be active at once,
+ * so {@link #stopTimer} must be called before the next call to this method.
+ *
+ * @throws IllegalStateException if the maximum number of queries are in progress already,
+ * or the method is called multiple times in a row, or there is
+ * a GPU error.
+ */
+ public void startTimer() {
+ nativeStartGlTimer(mNativeContext);
+ mStartTimeNs = SystemClock.elapsedRealtimeNanos();
+ }
+
+ /**
+ * Finish a GPU/CPU timing measurement.
+ *
+ *
Call after finishing all the drawing for a rendering pass. Only one timing measurement can
+ * be active at once, so {@link #startTimer} must be called before the next call to this
+ * method.
+ *
+ * @throws IllegalStateException if no GL timer is currently started, or there is a GPU
+ * error.
+ */
+ public void stopTimer() {
+ // Complete CPU timing
+ long endTimeNs = SystemClock.elapsedRealtimeNanos();
+ mCpuDurationsQueue.add(endTimeNs - mStartTimeNs);
+ // Complete GL timing
+ nativeStopGlTimer(mNativeContext);
+
+ // Poll to see if GL timing results have arrived; if so
+ // store the results for a frame
+ long duration = getNextGlDuration();
+ if (duration > 0) {
+ mCollectedGpuDurations.add(duration);
+ mCollectedTimestamps.add(mTimestampQueue.isEmpty() ?
+ NO_DURATION_YET : mTimestampQueue.poll());
+ mCollectedCpuDurations.add(mCpuDurationsQueue.isEmpty() ?
+ NO_DURATION_YET : mCpuDurationsQueue.poll());
+ }
+ if (duration == FAILED_TIMING) {
+ // Discard timestamp and CPU measurement since GPU measurement failed
+ if (!mTimestampQueue.isEmpty()) {
+ mTimestampQueue.poll();
+ }
+ if (!mCpuDurationsQueue.isEmpty()) {
+ mCpuDurationsQueue.poll();
+ }
+ }
+ }
+
+ /**
+ * Add a timestamp to a timing measurement. These are queued up and matched to completed
+ * workload measurements as they become available.
+ */
+ public void addTimestamp(long timestamp) {
+ mTimestampQueue.add(timestamp);
+ }
+
+ /**
+ * Get the next available GPU timing measurement.
+ *
+ *
Since the GPU works asynchronously, the results of a single start/stopGlTimer measurement
+ * will only be available some time after the {@link #stopTimer} call is made. Poll this method
+ * until the result becomes available. If multiple start/endTimer measurements are made in a
+ * row, the results will be available in FIFO order.
+ *
+ * @return The measured duration of the GPU workload for the next pending query, or
+ * {@link #NO_DURATION_YET} if no queries are pending or the next pending query has not
+ * yet finished, or {@link #FAILED_TIMING} if the GPU was unable to complete the
+ * measurement.
+ *
+ * @throws IllegalStateException If there is a GPU error.
+ *
+ */
+ private long getNextGlDuration() {
+ long duration = nativeGetNextGlDuration(mNativeContext);
+ if (duration > 0) {
+ mCompletedQueryCount++;
+ }
+ return duration;
+ }
+
+ /**
+ * Returns the number of measurements so far that returned a valid duration
+ * measurement.
+ */
+ public int getCompletedQueryCount() {
+ return mCompletedQueryCount;
+ }
+
+ @Override
+ protected void finalize() {
+ nativeDeleteContext(mNativeContext);
+ }
+
+ /**
+ * Create a native performance measurement context.
+ *
+ * @param maxQueryCount maximum in-progress queries; must be >= 1.
+ */
+ private static native long nativeCreateContext(int maxQueryCount);
+
+ /**
+ * Delete the native context.
+ *
+ *
Not safe to call more than once.
+ */
+ private static native void nativeDeleteContext(long contextHandle);
+
+ /**
+ * Query whether the relevant Gl extensions are available for Gl timing
+ */
+ private static native boolean nativeQuerySupport();
+
+ /**
+ * Start a GL timing section.
+ *
+ *
All GL commands between this method and the next {@link #nativeEndGlTimer} will be
+ * included in the timing.
+ *
+ *
Must be called from the same thread as calls to {@link #nativeEndGlTimer} and
+ * {@link #nativeGetNextGlDuration}.
+ *
+ * @throws IllegalStateException if a GL error occurs or start is called repeatedly.
+ */
+ protected static native void nativeStartGlTimer(long contextHandle);
+
+ /**
+ * Finish a GL timing section.
+ *
+ *
Some time after this call returns, the time the GPU took to
+ * execute all work submitted between the latest {@link #nativeStartGlTimer} and
+ * this call, will become available from calling {@link #nativeGetNextGlDuration}.
+ *
+ *
Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
+ * {@link #nativeGetNextGlDuration}.
+ *
+ * @throws IllegalStateException if a GL error occurs or stop is called before start
+ */
+ protected static native void nativeStopGlTimer(long contextHandle);
+
+ /**
+ * Get the next available GL duration measurement, in nanoseconds.
+ *
+ *
Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
+ * {@link #nativeEndGlTimer}.
+ *
+ * @return the next GL duration measurement, or {@link #NO_DURATION_YET} if
+ * no new measurement is available, or {@link #FAILED_TIMING} if timing
+ * failed for the next duration measurement.
+ * @throws IllegalStateException if a GL error occurs
+ */
+ protected static native long nativeGetNextGlDuration(long contextHandle);
+
+
+}
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index 9969fd2369d4f..daa64c05e8b35 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -17,6 +17,7 @@ package android.hardware.camera2.legacy;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
+import android.os.Environment;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
@@ -25,10 +26,13 @@ import android.opengl.EGLSurface;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;
+import android.text.format.Time;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
+import android.os.SystemProperties;
+import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@@ -126,6 +130,9 @@ public class SurfaceTextureRenderer {
private int maPositionHandle;
private int maTextureHandle;
+ private PerfMeasurement mPerfMeasurer = null;
+ private static final String LEGACY_PERF_PROPERTY = "persist.camera.legacy_perf";
+
public SurfaceTextureRenderer() {
mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length *
FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
@@ -219,7 +226,6 @@ public class SurfaceTextureRenderer {
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*offset*/ 0, /*count*/ 4);
checkGlError("glDrawArrays");
- GLES20.glFinish();
}
/**
@@ -327,9 +333,16 @@ public class SurfaceTextureRenderer {
EGL14.EGL_NONE
};
for (EGLSurfaceHolder holder : surfaces) {
- holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs, holder.surface,
- surfaceAttribs, 0);
- checkEglError("eglCreateWindowSurface");
+ try {
+ Size size = LegacyCameraDevice.getSurfaceSize(holder.surface);
+ holder.width = size.getWidth();
+ holder.height = size.getHeight();
+ holder.eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs,
+ holder.surface, surfaceAttribs, /*offset*/ 0);
+ checkEglError("eglCreateWindowSurface");
+ } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) {
+ Log.w(TAG, "Surface abandoned, skipping...", e);
+ }
}
}
@@ -367,6 +380,7 @@ public class SurfaceTextureRenderer {
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
EGL14.EGL_NO_CONTEXT);
+ dumpGlTiming();
if (mSurfaces != null) {
for (EGLSurfaceHolder holder : mSurfaces) {
if (holder.eglSurface != null) {
@@ -417,6 +431,65 @@ public class SurfaceTextureRenderer {
}
}
+ /**
+ * Save a measurement dump to disk, in
+ * {@code /sdcard/CameraLegacy/durations_