blob: 0430662873cff6b7a9dfebaf3804145ba047d49c [file] [log] [blame]
Yury Khmel9dbde7b2015-08-31 17:51:42 +09001/*
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 */
16package android.surfacecomposition;
17
18import java.util.Random;
19
20import android.content.Context;
21import android.graphics.Canvas;
22import android.graphics.Paint;
23import android.view.Surface;
24import android.view.SurfaceHolder;
25import 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 */
35public 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}