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