1 /* 2 * Copyright (C) 2018 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.gameperformance; 17 18 import java.util.ArrayList; 19 import java.util.List; 20 21 import android.content.Context; 22 import android.graphics.Canvas; 23 import android.graphics.Paint; 24 import android.graphics.PixelFormat; 25 import android.os.Handler; 26 import android.os.HandlerThread; 27 import android.os.Trace; 28 import android.view.Surface; 29 import android.view.SurfaceHolder; 30 import android.view.SurfaceView; 31 32 /** 33 * Minimal SurfaceView that sends buffer on request. 34 */ 35 public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback { 36 // Tag for trace when buffer is requested. 37 public final static String LOCAL_REQUEST_BUFFER = "localRequestBuffer"; 38 // Tag for trace when buffer is posted. 39 public final static String LOCAL_POST_BUFFER = "localPostBuffer"; 40 41 private final Object mSurfaceLock = new Object(); 42 // Keeps frame times. Used to calculate fps. 43 private List<Long> mFrameTimes; 44 // Surface to send. 45 private Surface mSurface; 46 private Handler mHandler; 47 48 private Runnable mInvalidateSurfaceTask = new Runnable() { 49 @Override 50 public void run() { 51 synchronized (mSurfaceLock) { 52 if (mSurface == null) { 53 return; 54 } 55 invalidateSurface(true, true); 56 mHandler.post(this); 57 } 58 } 59 }; 60 CustomSurfaceView(Context context)61 public CustomSurfaceView(Context context) { 62 super(context); 63 mFrameTimes = new ArrayList<Long>(); 64 getHolder().addCallback(this); 65 getHolder().setFormat(PixelFormat.OPAQUE); 66 67 HandlerThread thread = new HandlerThread("SurfaceInvalidator"); 68 thread.start(); 69 mHandler = new Handler(thread.getLooper()); 70 } 71 72 /** 73 * Resets frame times in order to calculate fps for different test pass. 74 */ resetFrameTimes()75 public void resetFrameTimes() { 76 synchronized (mSurfaceLock) { 77 mFrameTimes.clear(); 78 } 79 } 80 81 /** 82 * Returns current fps based on collected frame times. 83 */ getFps()84 public double getFps() { 85 synchronized (mSurfaceLock) { 86 if (mFrameTimes.size() < 2) { 87 return 0.0f; 88 } 89 return 1000.0 * mFrameTimes.size() / 90 (mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0)); 91 } 92 } 93 94 /** 95 * Invalidates surface. 96 * @param traceCalls set to true in case we need register trace calls. Not used for warm-up. 97 * @param drawFps perform drawing current fps on surface to have some payload on surface. 98 */ invalidateSurface(boolean traceCalls, boolean drawFps)99 public void invalidateSurface(boolean traceCalls, boolean drawFps) { 100 synchronized (mSurfaceLock) { 101 if (mSurface == null) { 102 throw new IllegalStateException("Surface is not ready"); 103 } 104 if (traceCalls) { 105 Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, LOCAL_REQUEST_BUFFER); 106 } 107 Canvas canvas = mSurface.lockHardwareCanvas(); 108 if (traceCalls) { 109 Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); 110 } 111 112 if (drawFps) { 113 int textSize = canvas.getHeight() / 24; 114 Paint paint = new Paint(); 115 paint.setTextSize(textSize); 116 paint.setColor(0xFFFF8040); 117 canvas.drawARGB(92, 255, 255, 255); 118 canvas.drawText("FPS: " + String.format("%.2f", getFps()), 10, 300, paint); 119 } 120 121 if (traceCalls) { 122 Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, LOCAL_POST_BUFFER); 123 } 124 mSurface.unlockCanvasAndPost(canvas); 125 if (traceCalls) { 126 Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); 127 } 128 129 mFrameTimes.add(System.currentTimeMillis()); 130 } 131 } 132 133 /** 134 * Wait until surface is created and ready to use or return immediately if surface 135 * already exists. 136 */ waitForSurfaceReady()137 public void waitForSurfaceReady() { 138 synchronized (mSurfaceLock) { 139 if (mSurface == null) { 140 try { 141 mSurfaceLock.wait(5000); 142 } catch(InterruptedException e) { 143 e.printStackTrace(); 144 } 145 } 146 if (mSurface == null) 147 throw new IllegalStateException("Surface is not ready."); 148 } 149 } 150 151 /** 152 * Waits until surface is destroyed or return immediately if surface does not exist. 153 */ waitForSurfaceDestroyed()154 public void waitForSurfaceDestroyed() { 155 synchronized (mSurfaceLock) { 156 if (mSurface != null) { 157 try { 158 mSurfaceLock.wait(5000); 159 } catch(InterruptedException e) { 160 } 161 } 162 if (mSurface != null) 163 throw new IllegalStateException("Surface still exists."); 164 } 165 } 166 167 168 @Override surfaceCreated(SurfaceHolder holder)169 public void surfaceCreated(SurfaceHolder holder) { 170 } 171 172 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)173 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 174 // This method is always called at least once, after surfaceCreated. 175 synchronized (mSurfaceLock) { 176 mSurface = holder.getSurface(); 177 mSurfaceLock.notify(); 178 mHandler.post(mInvalidateSurfaceTask); 179 } 180 } 181 182 @Override surfaceDestroyed(SurfaceHolder holder)183 public void surfaceDestroyed(SurfaceHolder holder) { 184 synchronized (mSurfaceLock) { 185 mHandler.removeCallbacks(mInvalidateSurfaceTask); 186 mSurface = null; 187 mSurfaceLock.notify(); 188 } 189 } 190 } 191