summaryrefslogtreecommitdiff
path: root/tests/SurfaceComposition/src/android/surfacecomposition/CustomSurfaceView.java
diff options
context:
space:
mode:
Diffstat (limited to 'tests/SurfaceComposition/src/android/surfacecomposition/CustomSurfaceView.java')
-rw-r--r--tests/SurfaceComposition/src/android/surfacecomposition/CustomSurfaceView.java243
1 files changed, 243 insertions, 0 deletions
diff --git a/tests/SurfaceComposition/src/android/surfacecomposition/CustomSurfaceView.java b/tests/SurfaceComposition/src/android/surfacecomposition/CustomSurfaceView.java
new file mode 100644
index 000000000000..0430662873cf
--- /dev/null
+++ b/tests/SurfaceComposition/src/android/surfacecomposition/CustomSurfaceView.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2015 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.surfacecomposition;
+
+import java.util.Random;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * This provides functionality to measure Surface update frame rate. The idea is to
+ * constantly invalidates Surface in a separate thread. Lowest possible way is to
+ * use SurfaceView which works with Surface. This gives a very small overhead
+ * and very close to Android internals. Note, that lockCanvas is blocking
+ * methods and it returns once SurfaceFlinger consumes previous buffer. This
+ * gives the change to measure real performance of Surface compositor.
+ */
+public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+ private final static long DURATION_TO_WARMUP_MS = 50;
+ private final static long DURATION_TO_MEASURE_ROUGH_MS = 500;
+ private final static long DURATION_TO_MEASURE_PRECISE_MS = 3000;
+ private final static Random mRandom = new Random();
+
+ private final Object mSurfaceLock = new Object();
+ private Surface mSurface;
+ private boolean mDrawNameOnReady = true;
+ private boolean mSurfaceWasChanged = false;
+ private String mName;
+ private Canvas mCanvas;
+
+ class ValidateThread extends Thread {
+ private double mFPS = 0.0f;
+ // Used to support early exit and prevent long computation.
+ private double mBadFPS;
+ private double mPerfectFPS;
+
+ ValidateThread(double badFPS, double perfectFPS) {
+ mBadFPS = badFPS;
+ mPerfectFPS = perfectFPS;
+ }
+
+ public void run() {
+ long startTime = System.currentTimeMillis();
+ while (System.currentTimeMillis() - startTime < DURATION_TO_WARMUP_MS) {
+ invalidateSurface(false);
+ }
+
+ startTime = System.currentTimeMillis();
+ long endTime;
+ int frameCnt = 0;
+ while (true) {
+ invalidateSurface(false);
+ endTime = System.currentTimeMillis();
+ ++frameCnt;
+ mFPS = (double)frameCnt * 1000.0 / (endTime - startTime);
+ if ((endTime - startTime) >= DURATION_TO_MEASURE_ROUGH_MS) {
+ // Test if result looks too bad or perfect and stop early.
+ if (mFPS <= mBadFPS || mFPS >= mPerfectFPS) {
+ break;
+ }
+ }
+ if ((endTime - startTime) >= DURATION_TO_MEASURE_PRECISE_MS) {
+ break;
+ }
+ }
+ }
+
+ public double getFPS() {
+ return mFPS;
+ }
+ }
+
+ public CustomSurfaceView(Context context, String name) {
+ super(context);
+ mName = name;
+ getHolder().addCallback(this);
+ }
+
+ public void setMode(int pixelFormat, boolean drawNameOnReady) {
+ mDrawNameOnReady = drawNameOnReady;
+ getHolder().setFormat(pixelFormat);
+ }
+
+ public void acquireCanvas() {
+ synchronized (mSurfaceLock) {
+ if (mCanvas != null) {
+ throw new RuntimeException("Surface canvas was already acquired.");
+ }
+ if (mSurface != null) {
+ mCanvas = mSurface.lockCanvas(null);
+ }
+ }
+ }
+
+ public void releaseCanvas() {
+ synchronized (mSurfaceLock) {
+ if (mCanvas != null) {
+ if (mSurface == null) {
+ throw new RuntimeException(
+ "Surface was destroyed but canvas was not released.");
+ }
+ mSurface.unlockCanvasAndPost(mCanvas);
+ mCanvas = null;
+ }
+ }
+ }
+
+ /**
+ * Invalidate surface.
+ */
+ private void invalidateSurface(boolean drawSurfaceId) {
+ synchronized (mSurfaceLock) {
+ if (mSurface != null) {
+ Canvas canvas = mSurface.lockCanvas(null);
+ // Draw surface name for debug purpose only. This does not affect the test
+ // because it is drawn only during allocation.
+ if (drawSurfaceId) {
+ int textSize = canvas.getHeight() / 24;
+ Paint paint = new Paint();
+ paint.setTextSize(textSize);
+ int textWidth = (int)(paint.measureText(mName) + 0.5f);
+ int x = mRandom.nextInt(canvas.getWidth() - textWidth);
+ int y = textSize + mRandom.nextInt(canvas.getHeight() - textSize);
+ // Create effect of fog to visually control correctness of composition.
+ paint.setColor(0xFFFF8040);
+ canvas.drawARGB(32, 255, 255, 255);
+ canvas.drawText(mName, x, y, paint);
+ }
+ mSurface.unlockCanvasAndPost(canvas);
+ }
+ }
+ }
+
+ /**
+ * Wait until surface is created and ready to use or return immediately if surface
+ * already exists.
+ */
+ public void waitForSurfaceReady() {
+ synchronized (mSurfaceLock) {
+ if (mSurface == null) {
+ try {
+ mSurfaceLock.wait(5000);
+ } catch(InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ if (mSurface == null)
+ throw new RuntimeException("Surface is not ready.");
+ mSurfaceWasChanged = false;
+ }
+ }
+
+ /**
+ * Wait until surface is destroyed or return immediately if surface does not exist.
+ */
+ public void waitForSurfaceDestroyed() {
+ synchronized (mSurfaceLock) {
+ if (mSurface != null) {
+ try {
+ mSurfaceLock.wait(5000);
+ } catch(InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ if (mSurface != null)
+ throw new RuntimeException("Surface still exists.");
+ mSurfaceWasChanged = false;
+ }
+ }
+
+ /**
+ * Validate that surface has not been changed since waitForSurfaceReady or
+ * waitForSurfaceDestroyed.
+ */
+ public void validateSurfaceNotChanged() {
+ synchronized (mSurfaceLock) {
+ if (mSurfaceWasChanged) {
+ throw new RuntimeException("Surface was changed during the test execution.");
+ }
+ }
+ }
+
+ public double measureFPS(double badFPS, double perfectFPS) {
+ try {
+ ValidateThread validateThread = new ValidateThread(badFPS, perfectFPS);
+ validateThread.start();
+ validateThread.join();
+ return validateThread.getFPS();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ synchronized (mSurfaceLock) {
+ mSurfaceWasChanged = true;
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ // This method is always called at least once, after surfaceCreated.
+ synchronized (mSurfaceLock) {
+ mSurface = holder.getSurface();
+ // We only need to invalidate the surface for the compositor performance test so that
+ // it gets included in the composition process. For allocation performance we
+ // don't need to invalidate surface and this allows us to remove non-necessary
+ // surface invalidation from the test.
+ if (mDrawNameOnReady) {
+ invalidateSurface(true);
+ }
+ mSurfaceWasChanged = true;
+ mSurfaceLock.notify();
+ }
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ synchronized (mSurfaceLock) {
+ mSurface = null;
+ mSurfaceWasChanged = true;
+ mSurfaceLock.notify();
+ }
+ }
+}