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
This commit is contained in:
Eino-Ville Talvala
2014-05-29 17:17:07 -07:00
parent 20b716bfc6
commit e1f57d6f44
5 changed files with 743 additions and 5 deletions

View File

@@ -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.
*
* <p>Measures CPU and GPU processing duration for a set of operations, and dumps
* the results into a file.</p>
*
* <p>Rough usage:
* <pre>
* {@code
* <set up workload>
* <start long-running workload>
* mPerfMeasurement.startTimer();
* ...render a frame...
* mPerfMeasurement.stopTimer();
* <end workload>
* mPerfMeasurement.dumpPerformanceData("/sdcard/my_data.txt");
* }
* </pre>
* </p>
*
* <p>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.</p>
*/
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<Long> mCollectedGpuDurations = new ArrayList<>();
private ArrayList<Long> mCollectedCpuDurations = new ArrayList<>();
private ArrayList<Long> mCollectedTimestamps = new ArrayList<>();
/**
* Values for in-progress measurements (waiting for async GPU results)
*/
private Queue<Long> mTimestampQueue = new LinkedList<>();
private Queue<Long> 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.
*
* <p>Must be called within a valid GL context.</p>
*/
public static boolean isGlTimingSupported() {
return nativeQuerySupport();
}
/**
* Dump collected data to file, and clear the stored data.
*
* <p>
* Format is a simple csv-like text file with a header,
* followed by a 3-column list of values in nanoseconds:
* <pre>
* timestamp gpu_duration cpu_duration
* <long> <long> <long>
* <long> <long> <long>
* <long> <long> <long>
* ....
* </pre>
* </p>
*/
public void dumpPerformanceData(String path) {
try (BufferedWriter dump = new BufferedWriter(new FileWriter(path))) {
dump.write("timestamp gpu_duration cpu_duration\n");
for (int i = 0; i < mCollectedGpuDurations.size(); i++) {
dump.write(String.format("%d %d %d\n",
mCollectedTimestamps.get(i),
mCollectedGpuDurations.get(i),
mCollectedCpuDurations.get(i)));
}
mCollectedTimestamps.clear();
mCollectedGpuDurations.clear();
mCollectedCpuDurations.clear();
} catch (IOException e) {
Log.e(TAG, "Error writing data dump to " + path + ":" + e);
}
}
/**
* Start a GPU/CPU timing measurement.
*
* <p>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.</p>
*
* @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.
*
* <p>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.</p>
*
* @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.
*
* <p>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.</p>
*
* @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.
*
* <p>Not safe to call more than once.</p>
*/
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.
*
* <p>All GL commands between this method and the next {@link #nativeEndGlTimer} will be
* included in the timing.</p>
*
* <p>Must be called from the same thread as calls to {@link #nativeEndGlTimer} and
* {@link #nativeGetNextGlDuration}.</p>
*
* @throws IllegalStateException if a GL error occurs or start is called repeatedly.
*/
protected static native void nativeStartGlTimer(long contextHandle);
/**
* Finish a GL timing section.
*
* <p>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}.</p>
*
* <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
* {@link #nativeGetNextGlDuration}.</p>
*
* @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.
*
* <p>Must be called from the same thread as calls to {@link #nativeStartGlTimer} and
* {@link #nativeEndGlTimer}.</p>
*
* @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);
}

View File

@@ -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_<time>_<width1>x<height1>_...txt}
*/
private void dumpGlTiming() {
if (mPerfMeasurer == null) return;
File legacyStorageDir = new File(Environment.getExternalStorageDirectory(), "CameraLegacy");
if (!legacyStorageDir.exists()){
if (!legacyStorageDir.mkdirs()){
Log.e(TAG, "Failed to create directory for data dump");
return;
}
}
StringBuilder path = new StringBuilder(legacyStorageDir.getPath());
path.append(File.separator);
path.append("durations_");
Time now = new Time();
now.setToNow();
path.append(now.format2445());
path.append("_S");
for (EGLSurfaceHolder surface : mSurfaces) {
path.append(String.format("_%d_%d", surface.width, surface.height));
}
path.append("_C");
for (EGLSurfaceHolder surface : mConversionSurfaces) {
path.append(String.format("_%d_%d", surface.width, surface.height));
}
path.append(".txt");
mPerfMeasurer.dumpPerformanceData(path.toString());
}
private void setupGlTiming() {
if (PerfMeasurement.isGlTimingSupported()) {
Log.d(TAG, "Enabling GL performance measurement");
mPerfMeasurer = new PerfMeasurement();
} else {
Log.d(TAG, "GL performance measurement not supported on this device");
mPerfMeasurer = null;
}
}
private void beginGlTiming() {
if (mPerfMeasurer == null) return;
mPerfMeasurer.startTimer();
}
private void addGlTimestamp(long timestamp) {
if (mPerfMeasurer == null) return;
mPerfMeasurer.addTimestamp(timestamp);
}
private void endGlTiming() {
if (mPerfMeasurer == null) return;
mPerfMeasurer.stopTimer();
}
/**
* Return the surface texture to draw to - this is the texture use to when producing output
* surface buffers.
@@ -474,6 +547,11 @@ public class SurfaceTextureRenderer {
mConversionSurfaces.get(0).eglSurface);
initializeGLState();
mSurfaceTexture = new SurfaceTexture(getTextureId());
// Set up performance tracking if enabled
if (SystemProperties.getBoolean(LEGACY_PERF_PROPERTY, false)) {
setupGlTiming();
}
}
/**
@@ -494,8 +572,19 @@ public class SurfaceTextureRenderer {
}
checkGlError("before updateTexImage");
if (targetSurfaces == null) {
mSurfaceTexture.updateTexImage();
return;
}
beginGlTiming();
mSurfaceTexture.updateTexImage();
if (targetSurfaces == null) return;
long timestamp = mSurfaceTexture.getTimestamp();
addGlTimestamp(timestamp);
List<Long> targetSurfaceIds = LegacyCameraDevice.getSurfaceIds(targetSurfaces);
for (EGLSurfaceHolder holder : mSurfaces) {
if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) {
@@ -522,6 +611,8 @@ public class SurfaceTextureRenderer {
}
}
}
endGlTiming();
}
/**

View File

@@ -139,6 +139,7 @@ LOCAL_SRC_FILES:= \
android_hardware_Camera.cpp \
android_hardware_camera2_CameraMetadata.cpp \
android_hardware_camera2_legacy_LegacyCameraDevice.cpp \
android_hardware_camera2_legacy_PerfMeasurement.cpp \
android_hardware_camera2_DngCreator.cpp \
android_hardware_SensorManager.cpp \
android_hardware_SerialPort.cpp \

View File

@@ -79,6 +79,7 @@ extern int register_android_opengl_jni_GLES31Ext(JNIEnv* env);
extern int register_android_hardware_Camera(JNIEnv *env);
extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env);
extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env);
extern int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv *env);
extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
extern int register_android_hardware_SensorManager(JNIEnv *env);
extern int register_android_hardware_SerialPort(JNIEnv *env);
@@ -1314,6 +1315,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_camera2_CameraMetadata),
REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement),
REG_JNI(register_android_hardware_camera2_DngCreator),
REG_JNI(register_android_hardware_SensorManager),
REG_JNI(register_android_hardware_SerialPort),

View File

@@ -0,0 +1,335 @@
/*
* 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.
*/
#define LOG_TAG "Camera2-Legacy-PerfMeasurement-JNI"
#include <utils/Log.h>
#include <utils/Errors.h>
#include <utils/Trace.h>
#include <utils/Vector.h>
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <ui/GraphicBuffer.h>
#include <system/window.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
using namespace android;
// fully-qualified class name
#define PERF_MEASUREMENT_CLASS_NAME "android/hardware/camera2/legacy/PerfMeasurement"
/** GL utility methods copied from com_google_android_gles_jni_GLImpl.cpp */
// Check if the extension at the head of pExtensions is pExtension. Note that pExtensions is
// terminated by either 0 or space, while pExtension is terminated by 0.
static bool
extensionEqual(const GLubyte* pExtensions, const GLubyte* pExtension) {
while (true) {
char a = *pExtensions++;
char b = *pExtension++;
bool aEnd = a == '\0' || a == ' ';
bool bEnd = b == '\0';
if (aEnd || bEnd) {
return aEnd == bEnd;
}
if (a != b) {
return false;
}
}
}
static const GLubyte*
nextExtension(const GLubyte* pExtensions) {
while (true) {
char a = *pExtensions++;
if (a == '\0') {
return pExtensions-1;
} else if ( a == ' ') {
return pExtensions;
}
}
}
static bool
checkForExtension(const GLubyte* pExtensions, const GLubyte* pExtension) {
for (; *pExtensions != '\0'; pExtensions = nextExtension(pExtensions)) {
if (extensionEqual(pExtensions, pExtension)) {
return true;
}
}
return false;
}
/** End copied GL utility methods */
bool checkGlError(JNIEnv* env) {
int error;
if ((error = glGetError()) != GL_NO_ERROR) {
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"GLES20 error: 0x%d", error);
return true;
}
return false;
}
/**
* Asynchronous low-overhead GL performance measurement using
* http://www.khronos.org/registry/gles/extensions/EXT/EXT_disjoint_timer_query.txt
*
* Measures the duration of GPU processing for a set of GL commands, delivering
* the measurement asynchronously once processing completes.
*
* All calls must come from a single thread with a valid GL context active.
**/
class PerfMeasurementContext {
private:
Vector<GLuint> mTimingQueries;
size_t mTimingStartIndex;
size_t mTimingEndIndex;
size_t mTimingQueryIndex;
size_t mFreeQueries;
bool mInitDone;
public:
/**
* maxQueryCount should be a conservative estimate of how many query objects
* will be active at once, which is a function of the GPU's level of
* pipelining and the frequency of queries.
*/
PerfMeasurementContext(size_t maxQueryCount):
mTimingStartIndex(0),
mTimingEndIndex(0),
mTimingQueryIndex(0) {
mTimingQueries.resize(maxQueryCount);
mFreeQueries = maxQueryCount;
mInitDone = false;
}
int getMaxQueryCount() {
return mTimingQueries.size();
}
/**
* Start a measurement period using the next available query object.
* Returns INVALID_OPERATION if called multiple times in a row,
* and BAD_VALUE if no more query objects are available.
*/
int startGlTimer() {
// Lazy init of queries to avoid needing GL context during construction
if (!mInitDone) {
glGenQueriesEXT(mTimingQueries.size(), mTimingQueries.editArray());
mInitDone = true;
}
if (mTimingEndIndex != mTimingStartIndex) {
return INVALID_OPERATION;
}
if (mFreeQueries == 0) {
return BAD_VALUE;
}
glBeginQueryEXT(GL_TIME_ELAPSED_EXT, mTimingQueries[mTimingStartIndex]);
mTimingStartIndex = (mTimingStartIndex + 1) % mTimingQueries.size();
mFreeQueries--;
return OK;
}
/**
* Finish the current measurement period
* Returns INVALID_OPERATION if called before any startGLTimer calls
* or if called multiple times in a row.
*/
int stopGlTimer() {
size_t nextEndIndex = (mTimingEndIndex + 1) % mTimingQueries.size();
if (nextEndIndex != mTimingStartIndex) {
return INVALID_OPERATION;
}
glEndQueryEXT(GL_TIME_ELAPSED_EXT);
mTimingEndIndex = nextEndIndex;
return OK;
}
static const nsecs_t NO_DURATION_YET = -1L;
static const nsecs_t FAILED_MEASUREMENT = -2L;
/**
* Get the next available duration measurement.
*
* Returns NO_DURATION_YET if no new measurement is available,
* and FAILED_MEASUREMENT if an error occurred during the next
* measurement period.
*
* Otherwise returns a positive number of nanoseconds measuring the
* duration of the oldest completed query.
*/
nsecs_t getNextGlDuration() {
if (!mInitDone) {
// No start/stop called yet
return NO_DURATION_YET;
}
GLint available;
glGetQueryObjectivEXT(mTimingQueries[mTimingQueryIndex],
GL_QUERY_RESULT_AVAILABLE_EXT, &available);
if (!available) {
return NO_DURATION_YET;
}
GLint64 duration = FAILED_MEASUREMENT;
GLint disjointOccurred;
glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjointOccurred);
if (!disjointOccurred) {
glGetQueryObjecti64vEXT(mTimingQueries[mTimingQueryIndex],
GL_QUERY_RESULT_EXT,
&duration);
}
mTimingQueryIndex = (mTimingQueryIndex + 1) % mTimingQueries.size();
mFreeQueries++;
return static_cast<nsecs_t>(duration);
}
static bool isMeasurementSupported() {
const GLubyte* extensions = glGetString(GL_EXTENSIONS);
return checkForExtension(extensions,
reinterpret_cast<const GLubyte*>("GL_EXT_disjoint_timer_query"));
}
};
PerfMeasurementContext* getContext(jlong context) {
return reinterpret_cast<PerfMeasurementContext*>(context);
}
extern "C" {
static jlong PerfMeasurement_nativeCreateContext(JNIEnv* env, jobject thiz,
jint maxQueryCount) {
PerfMeasurementContext *context = new PerfMeasurementContext(maxQueryCount);
return reinterpret_cast<jlong>(context);
}
static void PerfMeasurement_nativeDeleteContext(JNIEnv* env, jobject thiz,
jlong contextHandle) {
PerfMeasurementContext *context = getContext(contextHandle);
delete(context);
}
static jboolean PerfMeasurement_nativeQuerySupport(JNIEnv* env, jobject thiz) {
bool supported = PerfMeasurementContext::isMeasurementSupported();
checkGlError(env);
return static_cast<jboolean>(supported);
}
static void PerfMeasurement_nativeStartGlTimer(JNIEnv* env, jobject thiz,
jlong contextHandle) {
PerfMeasurementContext *context = getContext(contextHandle);
status_t err = context->startGlTimer();
if (err != OK) {
switch (err) {
case INVALID_OPERATION:
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Mismatched start/end GL timing calls");
return;
case BAD_VALUE:
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Too many timing queries in progress, max %d",
context->getMaxQueryCount());
return;
default:
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Unknown error starting GL timing");
return;
}
}
checkGlError(env);
}
static void PerfMeasurement_nativeStopGlTimer(JNIEnv* env, jobject thiz,
jlong contextHandle) {
PerfMeasurementContext *context = getContext(contextHandle);
status_t err = context->stopGlTimer();
if (err != OK) {
switch (err) {
case INVALID_OPERATION:
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Mismatched start/end GL timing calls");
return;
default:
jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
"Unknown error ending GL timing");
return;
}
}
checkGlError(env);
}
static jlong PerfMeasurement_nativeGetNextGlDuration(JNIEnv* env,
jobject thiz, jlong contextHandle) {
PerfMeasurementContext *context = getContext(contextHandle);
nsecs_t duration = context->getNextGlDuration();
checkGlError(env);
return static_cast<jlong>(duration);
}
} // extern "C"
static JNINativeMethod gPerfMeasurementMethods[] = {
{ "nativeCreateContext",
"(I)J",
(jlong *)PerfMeasurement_nativeCreateContext },
{ "nativeDeleteContext",
"(J)V",
(void *)PerfMeasurement_nativeDeleteContext },
{ "nativeQuerySupport",
"()Z",
(jboolean *)PerfMeasurement_nativeQuerySupport },
{ "nativeStartGlTimer",
"(J)V",
(void *)PerfMeasurement_nativeStartGlTimer },
{ "nativeStopGlTimer",
"(J)V",
(void *)PerfMeasurement_nativeStopGlTimer },
{ "nativeGetNextGlDuration",
"(J)J",
(jlong *)PerfMeasurement_nativeGetNextGlDuration }
};
// Get all the required offsets in java class and register native functions
int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv* env)
{
// Register native functions
return AndroidRuntime::registerNativeMethods(env,
PERF_MEASUREMENT_CLASS_NAME,
gPerfMeasurementMethods,
NELEM(gPerfMeasurementMethods));
}