diff options
Diffstat (limited to 'tests/SurfaceComposition/src/android/surfacecomposition/CustomSurfaceView.java')
-rw-r--r-- | tests/SurfaceComposition/src/android/surfacecomposition/CustomSurfaceView.java | 243 |
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(); + } + } +} |