| /* |
| * 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 "core_jni_helpers.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 const 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)); |
| } |