John Reck | 03a1edf | 2016-10-06 14:46:19 -0700 | [diff] [blame] | 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; |
John Reck | e692f31 | 2016-10-06 17:39:49 -0700 | [diff] [blame] | 20 | import android.content.Context; |
John Reck | 03a1edf | 2016-10-06 14:46:19 -0700 | [diff] [blame] | 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; |
John Reck | e692f31 | 2016-10-06 17:39:49 -0700 | [diff] [blame] | 32 | import android.util.AttributeSet; |
John Reck | 03a1edf | 2016-10-06 14:46:19 -0700 | [diff] [blame] | 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; |
John Reck | e692f31 | 2016-10-06 17:39:49 -0700 | [diff] [blame] | 46 | private PointGraphView mGraph; |
John Reck | 03a1edf | 2016-10-06 14:46:19 -0700 | [diff] [blame] | 47 | |
| 48 | private static Handler sMetricsHandler; |
| 49 | static { |
| 50 | HandlerThread thread = new HandlerThread("frameMetricsListener"); |
| 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; |
John Reck | e692f31 | 2016-10-06 17:39:49 -0700 | [diff] [blame] | 74 | case R.id.graph: |
| 75 | mGraph.addJitterSample(msg.arg1, msg.arg2); |
| 76 | break; |
John Reck | 03a1edf | 2016-10-06 14:46:19 -0700 | [diff] [blame] | 77 | } |
| 78 | } |
| 79 | }; |
| 80 | |
| 81 | @Override |
| 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); |
Alan Viverette | 51efddb | 2017-04-05 10:00:01 -0400 | [diff] [blame] | 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); |
John Reck | 03a1edf | 2016-10-06 14:46:19 -0700 | [diff] [blame] | 94 | mJitterReport.setText("abcdefghijklmnopqrstuvwxyz"); |
| 95 | mMostlyTotalFrameTimeReport.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); |
| 96 | mUiFrameTimeReport.setText("0123456789"); |
| 97 | mRenderThreadTimeReport.setText(",.!()[]{};"); |
| 98 | getWindow().addOnFrameMetricsAvailableListener(mMetricsListener, sMetricsHandler); |
| 99 | } |
| 100 | |
John Reck | e692f31 | 2016-10-06 17:39:49 -0700 | [diff] [blame] | 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 | |
| 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 | |
| 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 | |
| 144 | private float dp(float dp) { |
| 145 | return mDensity * dp; |
| 146 | } |
| 147 | |
| 148 | @Override |
| 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 |
| 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 | |
| 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 | |
John Reck | 03a1edf | 2016-10-06 14:46:19 -0700 | [diff] [blame] | 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(); |
John Reck | e692f31 | 2016-10-06 17:39:49 -0700 | [diff] [blame] | 257 | mUpdateHandler.obtainMessage(R.id.graph, (int) (jitter / 1000), |
| 258 | (int) (mJitterMma / 1000)).sendToTarget(); |
John Reck | 03a1edf | 2016-10-06 14:46:19 -0700 | [diff] [blame] | 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 | |
| 287 | public AnimatedBackgroundDrawable() { |
| 288 | mPaint = new Paint(); |
| 289 | mPaint.setColor(0xFFFFFF00); |
| 290 | mPaint.setAntiAlias(true); |
| 291 | } |
| 292 | |
| 293 | @Override |
| 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 | |
| 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 |
| 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 |
| 335 | public void setAlpha(int alpha) { |
| 336 | } |
| 337 | |
| 338 | @Override |
| 339 | public void setColorFilter(ColorFilter colorFilter) { |
| 340 | } |
| 341 | |
| 342 | @Override |
| 343 | public int getOpacity() { |
| 344 | return PixelFormat.OPAQUE; |
| 345 | } |
| 346 | |
| 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 | } |