Home | History | Annotate | Download | only in surfacecomposition
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package android.surfacecomposition;
     17 
     18 import java.util.Random;
     19 
     20 import android.content.Context;
     21 import android.graphics.Canvas;
     22 import android.graphics.Paint;
     23 import android.view.Surface;
     24 import android.view.SurfaceHolder;
     25 import android.view.SurfaceView;
     26 
     27 /**
     28  * This provides functionality to measure Surface update frame rate. The idea is to
     29  * constantly invalidates Surface in a separate thread. Lowest possible way is to
     30  * use SurfaceView which works with Surface. This gives a very small overhead
     31  * and very close to Android internals. Note, that lockCanvas is blocking
     32  * methods and it returns once SurfaceFlinger consumes previous buffer. This
     33  * gives the change to measure real performance of Surface compositor.
     34  */
     35 public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
     36     private final static long DURATION_TO_WARMUP_MS = 50;
     37     private final static long DURATION_TO_MEASURE_ROUGH_MS = 500;
     38     private final static long DURATION_TO_MEASURE_PRECISE_MS = 3000;
     39     private final static Random mRandom = new Random();
     40 
     41     private final Object mSurfaceLock = new Object();
     42     private Surface mSurface;
     43     private boolean mDrawNameOnReady = true;
     44     private boolean mSurfaceWasChanged = false;
     45     private String mName;
     46     private Canvas mCanvas;
     47 
     48     class ValidateThread extends Thread {
     49         private double mFPS = 0.0f;
     50         // Used to support early exit and prevent long computation.
     51         private double mBadFPS;
     52         private double mPerfectFPS;
     53 
     54         ValidateThread(double badFPS, double perfectFPS) {
     55             mBadFPS = badFPS;
     56             mPerfectFPS = perfectFPS;
     57         }
     58 
     59         public void run() {
     60             long startTime = System.currentTimeMillis();
     61             while (System.currentTimeMillis() - startTime < DURATION_TO_WARMUP_MS) {
     62                 invalidateSurface(false);
     63             }
     64 
     65             startTime = System.currentTimeMillis();
     66             long endTime;
     67             int frameCnt = 0;
     68             while (true) {
     69                 invalidateSurface(false);
     70                 endTime = System.currentTimeMillis();
     71                 ++frameCnt;
     72                 mFPS = (double)frameCnt * 1000.0 / (endTime - startTime);
     73                 if ((endTime - startTime) >= DURATION_TO_MEASURE_ROUGH_MS) {
     74                     // Test if result looks too bad or perfect and stop early.
     75                     if (mFPS <= mBadFPS || mFPS >= mPerfectFPS) {
     76                         break;
     77                     }
     78                 }
     79                 if ((endTime - startTime) >= DURATION_TO_MEASURE_PRECISE_MS) {
     80                     break;
     81                 }
     82             }
     83         }
     84 
     85         public double getFPS() {
     86             return mFPS;
     87         }
     88     }
     89 
     90     public CustomSurfaceView(Context context, String name) {
     91         super(context);
     92         mName = name;
     93         getHolder().addCallback(this);
     94     }
     95 
     96     public void setMode(int pixelFormat, boolean drawNameOnReady) {
     97         mDrawNameOnReady = drawNameOnReady;
     98         getHolder().setFormat(pixelFormat);
     99     }
    100 
    101     public void acquireCanvas() {
    102         synchronized (mSurfaceLock) {
    103             if (mCanvas != null) {
    104                 throw new RuntimeException("Surface canvas was already acquired.");
    105             }
    106             if (mSurface != null) {
    107                 mCanvas = mSurface.lockCanvas(null);
    108             }
    109         }
    110     }
    111 
    112     public void releaseCanvas() {
    113         synchronized (mSurfaceLock) {
    114             if (mCanvas != null) {
    115                 if (mSurface == null) {
    116                     throw new RuntimeException(
    117                             "Surface was destroyed but canvas was not released.");
    118                 }
    119                 mSurface.unlockCanvasAndPost(mCanvas);
    120                 mCanvas = null;
    121             }
    122         }
    123     }
    124 
    125     /**
    126      * Invalidate surface.
    127      */
    128     private void invalidateSurface(boolean drawSurfaceId) {
    129         synchronized (mSurfaceLock) {
    130             if (mSurface != null) {
    131                 Canvas canvas = mSurface.lockCanvas(null);
    132                 // Draw surface name for debug purpose only. This does not affect the test
    133                 // because it is drawn only during allocation.
    134                 if (drawSurfaceId) {
    135                     int textSize = canvas.getHeight() / 24;
    136                     Paint paint = new Paint();
    137                     paint.setTextSize(textSize);
    138                     int textWidth = (int)(paint.measureText(mName) + 0.5f);
    139                     int x = mRandom.nextInt(canvas.getWidth() - textWidth);
    140                     int y = textSize + mRandom.nextInt(canvas.getHeight() - textSize);
    141                     // Create effect of fog to visually control correctness of composition.
    142                     paint.setColor(0xFFFF8040);
    143                     canvas.drawARGB(32, 255, 255, 255);
    144                     canvas.drawText(mName, x, y, paint);
    145                 }
    146                 mSurface.unlockCanvasAndPost(canvas);
    147             }
    148         }
    149     }
    150 
    151     /**
    152      * Wait until surface is created and ready to use or return immediately if surface
    153      * already exists.
    154      */
    155     public void waitForSurfaceReady() {
    156         synchronized (mSurfaceLock) {
    157             if (mSurface == null) {
    158                 try {
    159                     mSurfaceLock.wait(5000);
    160                 } catch(InterruptedException e) {
    161                     e.printStackTrace();
    162                 }
    163             }
    164             if (mSurface == null)
    165                 throw new RuntimeException("Surface is not ready.");
    166             mSurfaceWasChanged = false;
    167         }
    168     }
    169 
    170     /**
    171      * Wait until surface is destroyed or return immediately if surface does not exist.
    172      */
    173     public void waitForSurfaceDestroyed() {
    174         synchronized (mSurfaceLock) {
    175             if (mSurface != null) {
    176                 try {
    177                     mSurfaceLock.wait(5000);
    178                 } catch(InterruptedException e) {
    179                     e.printStackTrace();
    180                 }
    181             }
    182             if (mSurface != null)
    183                 throw new RuntimeException("Surface still exists.");
    184             mSurfaceWasChanged = false;
    185         }
    186     }
    187 
    188     /**
    189      * Validate that surface has not been changed since waitForSurfaceReady or
    190      * waitForSurfaceDestroyed.
    191      */
    192     public void validateSurfaceNotChanged() {
    193         synchronized (mSurfaceLock) {
    194             if (mSurfaceWasChanged) {
    195                 throw new RuntimeException("Surface was changed during the test execution.");
    196             }
    197         }
    198     }
    199 
    200     public double measureFPS(double badFPS, double perfectFPS) {
    201         try {
    202             ValidateThread validateThread = new ValidateThread(badFPS, perfectFPS);
    203             validateThread.start();
    204             validateThread.join();
    205             return validateThread.getFPS();
    206         } catch (InterruptedException e) {
    207             throw new RuntimeException(e);
    208         }
    209     }
    210 
    211     @Override
    212     public void surfaceCreated(SurfaceHolder holder) {
    213         synchronized (mSurfaceLock) {
    214             mSurfaceWasChanged = true;
    215         }
    216     }
    217 
    218     @Override
    219     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    220         // This method is always called at least once, after surfaceCreated.
    221         synchronized (mSurfaceLock) {
    222             mSurface = holder.getSurface();
    223             // We only need to invalidate the surface for the compositor performance test so that
    224             // it gets included in the composition process. For allocation performance we
    225             // don't need to invalidate surface and this allows us to remove non-necessary
    226             // surface invalidation from the test.
    227             if (mDrawNameOnReady) {
    228                 invalidateSurface(true);
    229             }
    230             mSurfaceWasChanged = true;
    231             mSurfaceLock.notify();
    232         }
    233     }
    234 
    235     @Override
    236     public void surfaceDestroyed(SurfaceHolder holder) {
    237         synchronized (mSurfaceLock) {
    238             mSurface = null;
    239             mSurfaceWasChanged = true;
    240             mSurfaceLock.notify();
    241         }
    242     }
    243 }
    244