/* * 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 #include #include #include #include "jni.h" #include "JNIHelp.h" #include "core_jni_helpers.h" #include #include #include #include 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 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(duration); } static bool isMeasurementSupported() { const GLubyte* extensions = glGetString(GL_EXTENSIONS); return checkForExtension(extensions, reinterpret_cast("GL_EXT_disjoint_timer_query")); } }; PerfMeasurementContext* getContext(jlong context) { return reinterpret_cast(context); } extern "C" { static jlong PerfMeasurement_nativeCreateContext(JNIEnv* env, jobject thiz, jint maxQueryCount) { PerfMeasurementContext *context = new PerfMeasurementContext(maxQueryCount); return reinterpret_cast(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(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(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 RegisterMethodsOrDie(env, PERF_MEASUREMENT_CLASS_NAME, gPerfMeasurementMethods, NELEM(gPerfMeasurementMethods)); }