1 /* 2 * Copyright (C) 2016 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 17 package com.android.test.uibench; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.graphics.Canvas; 22 import android.graphics.Color; 23 import android.graphics.ColorFilter; 24 import android.graphics.Paint; 25 import android.graphics.PixelFormat; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Drawable; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.HandlerThread; 31 import android.os.Message; 32 import android.util.AttributeSet; 33 import android.view.FrameMetrics; 34 import android.view.View; 35 import android.view.Window; 36 import android.view.Window.OnFrameMetricsAvailableListener; 37 import android.view.animation.AnimationUtils; 38 import android.widget.TextView; 39 40 public class RenderingJitter extends Activity { 41 private TextView mJitterReport; 42 private TextView mUiFrameTimeReport; 43 private TextView mRenderThreadTimeReport; 44 private TextView mTotalFrameTimeReport; 45 private TextView mMostlyTotalFrameTimeReport; 46 private PointGraphView mGraph; 47 48 private static Handler sMetricsHandler; 49 static { 50 HandlerThread thread = new HandlerThread("frameMetricsListener"); thread.start()51 thread.start(); 52 sMetricsHandler = new Handler(thread.getLooper()); 53 } 54 55 private Handler mUpdateHandler = new Handler() { 56 @Override 57 public void handleMessage(Message msg) { 58 switch (msg.what) { 59 case R.id.jitter_mma: 60 mJitterReport.setText((CharSequence) msg.obj); 61 break; 62 case R.id.totalish_mma: 63 mMostlyTotalFrameTimeReport.setText((CharSequence) msg.obj); 64 break; 65 case R.id.ui_frametime_mma: 66 mUiFrameTimeReport.setText((CharSequence) msg.obj); 67 break; 68 case R.id.rt_frametime_mma: 69 mRenderThreadTimeReport.setText((CharSequence) msg.obj); 70 break; 71 case R.id.total_mma: 72 mTotalFrameTimeReport.setText((CharSequence) msg.obj); 73 break; 74 case R.id.graph: 75 mGraph.addJitterSample(msg.arg1, msg.arg2); 76 break; 77 } 78 } 79 }; 80 81 @Override onCreate(Bundle savedInstanceState)82 protected void onCreate(Bundle savedInstanceState) { 83 super.onCreate(savedInstanceState); 84 setContentView(R.layout.rendering_jitter); 85 View content = findViewById(android.R.id.content); 86 content.setBackground(new AnimatedBackgroundDrawable()); 87 content.setKeepScreenOn(true); 88 mJitterReport = findViewById(R.id.jitter_mma); 89 mMostlyTotalFrameTimeReport = findViewById(R.id.totalish_mma); 90 mUiFrameTimeReport = findViewById(R.id.ui_frametime_mma); 91 mRenderThreadTimeReport = findViewById(R.id.rt_frametime_mma); 92 mTotalFrameTimeReport = findViewById(R.id.total_mma); 93 mGraph = findViewById(R.id.graph); 94 mJitterReport.setText("abcdefghijklmnopqrstuvwxyz"); 95 mMostlyTotalFrameTimeReport.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 96 mUiFrameTimeReport.setText("0123456789"); 97 mRenderThreadTimeReport.setText(",.!()[]{};"); 98 getWindow().addOnFrameMetricsAvailableListener(mMetricsListener, sMetricsHandler); 99 } 100 101 public static final class PointGraphView extends View { 102 private static final float[] JITTER_LINES_MS = { 103 .5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 5.0f 104 }; 105 private static final String[] JITTER_LINES_LABELS = makeLabels(JITTER_LINES_MS); 106 private static final int[] JITTER_LINES_COLORS = new int[] { 107 0xFF00E676, 0xFFFFF176, 0xFFFDD835, 0xFFFBC02D, 0xFFF9A825, 108 0xFFF57F17, 0xFFDD2C00 109 }; 110 private Paint mPaint = new Paint(); 111 private float[] mJitterYs = new float[JITTER_LINES_MS.length]; 112 private float mLabelWidth; 113 private float mLabelHeight; 114 private float mDensity; 115 private float mGraphScale; 116 private float mGraphMaxMs; 117 118 private float[] mJitterPoints; 119 private float[] mJitterAvgPoints; 120 PointGraphView(Context context, AttributeSet attrs)121 public PointGraphView(Context context, AttributeSet attrs) { 122 super(context, attrs); 123 setWillNotDraw(false); 124 mDensity = context.getResources().getDisplayMetrics().density; 125 mPaint.setTextSize(dp(10)); 126 Rect textBounds = new Rect(); 127 mPaint.getTextBounds("8.8", 0, 3, textBounds); 128 mLabelWidth = textBounds.width() + dp(2); 129 mLabelHeight = textBounds.height(); 130 } 131 addJitterSample(int jitterUs, int jitterUsAvg)132 public void addJitterSample(int jitterUs, int jitterUsAvg) { 133 for (int i = 1; i < mJitterPoints.length - 2; i += 2) { 134 mJitterPoints[i] = mJitterPoints[i + 2]; 135 mJitterAvgPoints[i] = mJitterAvgPoints[i + 2]; 136 } 137 mJitterPoints[mJitterPoints.length - 1] = 138 getHeight() - mGraphScale * (jitterUs / 1000.0f); 139 mJitterAvgPoints[mJitterAvgPoints.length - 1] = 140 getHeight() - mGraphScale * (jitterUsAvg / 1000.0f); 141 invalidate(); 142 } 143 dp(float dp)144 private float dp(float dp) { 145 return mDensity * dp; 146 } 147 148 @Override onDraw(Canvas canvas)149 protected void onDraw(Canvas canvas) { 150 canvas.drawColor(0x90000000); 151 int h = getHeight(); 152 int w = getWidth(); 153 mPaint.setColor(Color.WHITE); 154 mPaint.setStrokeWidth(dp(1)); 155 canvas.drawLine(mLabelWidth, 0, mLabelWidth, h, mPaint); 156 for (int i = 0; i < JITTER_LINES_LABELS.length; i++) { 157 canvas.drawText(JITTER_LINES_LABELS[i], 158 0, (float) Math.floor(mJitterYs[i] + mLabelHeight * .5f), mPaint); 159 } 160 for (int i = 0; i < JITTER_LINES_LABELS.length; i++) { 161 mPaint.setColor(JITTER_LINES_COLORS[i]); 162 canvas.drawLine(mLabelWidth, mJitterYs[i], w, mJitterYs[i], mPaint); 163 } 164 mPaint.setStrokeWidth(dp(2)); 165 mPaint.setColor(Color.WHITE); 166 canvas.drawPoints(mJitterPoints, mPaint); 167 mPaint.setColor(0xFF2196F3); 168 canvas.drawPoints(mJitterAvgPoints, mPaint); 169 } 170 171 @Override onSizeChanged(int w, int h, int oldw, int oldh)172 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 173 super.onSizeChanged(w, h, oldw, oldh); 174 int graphWidth = (int) ((w - mLabelWidth - dp(1)) / mDensity); 175 float[] oldJitterPoints = mJitterPoints; 176 float[] oldJitterAvgPoints = mJitterAvgPoints; 177 mJitterPoints = new float[graphWidth * 2]; 178 mJitterAvgPoints = new float[graphWidth * 2]; 179 for (int i = 0; i < mJitterPoints.length; i += 2) { 180 mJitterPoints[i] = mLabelWidth + (i / 2 + 1) * mDensity; 181 mJitterAvgPoints[i] = mJitterPoints[i]; 182 } 183 if (oldJitterPoints != null) { 184 int newIndexShift = Math.max(mJitterPoints.length - oldJitterPoints.length, 0); 185 int oldIndexShift = oldJitterPoints.length - mJitterPoints.length; 186 for (int i = 1 + newIndexShift; i < mJitterPoints.length; i += 2) { 187 mJitterPoints[i] = oldJitterPoints[i + oldIndexShift]; 188 mJitterAvgPoints[i] = oldJitterAvgPoints[i + oldIndexShift]; 189 } 190 } 191 mGraphMaxMs = JITTER_LINES_MS[JITTER_LINES_MS.length - 1] + .5f; 192 mGraphScale = (h / mGraphMaxMs); 193 for (int i = 0; i < JITTER_LINES_MS.length; i++) { 194 mJitterYs[i] = (float) Math.floor(h - mGraphScale * JITTER_LINES_MS[i]); 195 } 196 } 197 makeLabels(float[] divisions)198 private static String[] makeLabels(float[] divisions) { 199 String[] ret = new String[divisions.length]; 200 for (int i = 0; i < divisions.length; i++) { 201 ret[i] = Float.toString(divisions[i]); 202 } 203 return ret; 204 } 205 } 206 207 private final OnFrameMetricsAvailableListener mMetricsListener = new OnFrameMetricsAvailableListener() { 208 private final static double WEIGHT = 40; 209 private long mPreviousFrameTotal; 210 private double mJitterMma; 211 private double mUiFrametimeMma; 212 private double mRtFrametimeMma; 213 private double mTotalFrametimeMma; 214 private double mMostlyTotalFrametimeMma; 215 private boolean mNeedsFirstValues = true; 216 217 @Override 218 public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, 219 int dropCountSinceLastInvocation) { 220 if (frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1) { 221 return; 222 } 223 224 long uiDuration = frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION) 225 + frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION) 226 + frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) 227 + frameMetrics.getMetric(FrameMetrics.DRAW_DURATION); 228 long rtDuration = frameMetrics.getMetric(FrameMetrics.SYNC_DURATION) 229 + frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION); 230 long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION); 231 long jitter = Math.abs(totalDuration - mPreviousFrameTotal); 232 if (mNeedsFirstValues) { 233 mJitterMma = 0; 234 mUiFrametimeMma = uiDuration; 235 mRtFrametimeMma = rtDuration; 236 mTotalFrametimeMma = totalDuration; 237 mMostlyTotalFrametimeMma = uiDuration + rtDuration; 238 mNeedsFirstValues = false; 239 } else { 240 mJitterMma = add(mJitterMma, jitter); 241 mUiFrametimeMma = add(mUiFrametimeMma, uiDuration); 242 mRtFrametimeMma = add(mRtFrametimeMma, rtDuration); 243 mTotalFrametimeMma = add(mTotalFrametimeMma, totalDuration); 244 mMostlyTotalFrametimeMma = add(mMostlyTotalFrametimeMma, uiDuration + rtDuration); 245 } 246 mPreviousFrameTotal = totalDuration; 247 mUpdateHandler.obtainMessage(R.id.jitter_mma, 248 String.format("Jitter: %.3fms", toMs(mJitterMma))).sendToTarget(); 249 mUpdateHandler.obtainMessage(R.id.totalish_mma, 250 String.format("CPU-total duration: %.3fms", toMs(mMostlyTotalFrametimeMma))).sendToTarget(); 251 mUpdateHandler.obtainMessage(R.id.ui_frametime_mma, 252 String.format("UI duration: %.3fms", toMs(mUiFrametimeMma))).sendToTarget(); 253 mUpdateHandler.obtainMessage(R.id.rt_frametime_mma, 254 String.format("RT duration: %.3fms", toMs(mRtFrametimeMma))).sendToTarget(); 255 mUpdateHandler.obtainMessage(R.id.total_mma, 256 String.format("Total duration: %.3fms", toMs(mTotalFrametimeMma))).sendToTarget(); 257 mUpdateHandler.obtainMessage(R.id.graph, (int) (jitter / 1000), 258 (int) (mJitterMma / 1000)).sendToTarget(); 259 } 260 261 double add(double previous, double today) { 262 return (((WEIGHT - 1) * previous) + today) / WEIGHT; 263 } 264 265 double toMs(double val) { 266 return val / 1000000; 267 } 268 }; 269 270 private static final class AnimatedBackgroundDrawable extends Drawable { 271 private static final int FROM_COLOR = 0xFF18FFFF; 272 private static final int TO_COLOR = 0xFF40C4FF; 273 private static final int DURATION = 1400; 274 275 private final Paint mPaint; 276 private boolean mReverse; 277 private long mStartTime; 278 private int mColor; 279 280 private boolean mReverseX; 281 private boolean mReverseY; 282 private float mX; 283 private float mY; 284 private float mRadius; 285 private float mMoveStep = 10.0f; 286 AnimatedBackgroundDrawable()287 public AnimatedBackgroundDrawable() { 288 mPaint = new Paint(); 289 mPaint.setColor(0xFFFFFF00); 290 mPaint.setAntiAlias(true); 291 } 292 293 @Override draw(Canvas canvas)294 public void draw(Canvas canvas) { 295 stepColor(); 296 canvas.drawColor(mColor); 297 298 mX += (mReverseX ? -mMoveStep : mMoveStep); 299 mY += (mReverseY ? -mMoveStep : mMoveStep); 300 clampXY(); 301 canvas.drawCircle(mX, mY, mRadius, mPaint); 302 303 invalidateSelf(); 304 } 305 clampXY()306 private void clampXY() { 307 if (mX <= mRadius) { 308 mReverseX = false; 309 mX = mRadius; 310 } 311 if (mY <= mRadius) { 312 mReverseY = false; 313 mY = mRadius; 314 } 315 float maxX = getBounds().width() - mRadius; 316 if (mX >= maxX) { 317 mReverseX = true; 318 mX = maxX; 319 } 320 float maxY = getBounds().height() - mRadius; 321 if (mY >= maxY) { 322 mReverseY = true; 323 mY = maxY; 324 } 325 } 326 327 @Override onBoundsChange(Rect bounds)328 protected void onBoundsChange(Rect bounds) { 329 super.onBoundsChange(bounds); 330 mMoveStep = Math.min(bounds.width(), bounds.height()) / 130.0f; 331 mRadius = Math.min(bounds.width(), bounds.height()) / 20.0f; 332 } 333 334 @Override setAlpha(int alpha)335 public void setAlpha(int alpha) { 336 } 337 338 @Override setColorFilter(ColorFilter colorFilter)339 public void setColorFilter(ColorFilter colorFilter) { 340 } 341 342 @Override getOpacity()343 public int getOpacity() { 344 return PixelFormat.OPAQUE; 345 } 346 stepColor()347 private void stepColor() { 348 if (mStartTime == 0) { 349 mStartTime = AnimationUtils.currentAnimationTimeMillis(); 350 } 351 float frac = (AnimationUtils.currentAnimationTimeMillis() - mStartTime) 352 / (float) DURATION; 353 if (frac > 1.0f) frac = 1.0f; 354 int dest = mReverse ? FROM_COLOR : TO_COLOR; 355 int src = mReverse ? TO_COLOR : FROM_COLOR; 356 int r = (int) (Color.red(src) + (Color.red(dest) - Color.red(src)) * frac); 357 int g = (int) (Color.green(src) + (Color.green(dest) - Color.green(src)) * frac); 358 int b = (int) (Color.blue(src) + (Color.blue(dest) - Color.blue(src)) * frac); 359 mColor = Color.rgb(r, g, b); 360 if (frac == 1.0f) { 361 mStartTime = 0; 362 mReverse = !mReverse; 363 } 364 } 365 } 366 } 367