| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.test.uibench; |
| |
| import android.app.Activity; |
| import android.content.Context; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.ColorFilter; |
| import android.graphics.Paint; |
| import android.graphics.PixelFormat; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Message; |
| import android.util.AttributeSet; |
| import android.view.FrameMetrics; |
| import android.view.View; |
| import android.view.Window; |
| import android.view.Window.OnFrameMetricsAvailableListener; |
| import android.view.animation.AnimationUtils; |
| import android.widget.TextView; |
| |
| public class RenderingJitter extends Activity { |
| private TextView mJitterReport; |
| private TextView mUiFrameTimeReport; |
| private TextView mRenderThreadTimeReport; |
| private TextView mTotalFrameTimeReport; |
| private TextView mMostlyTotalFrameTimeReport; |
| private PointGraphView mGraph; |
| |
| private static Handler sMetricsHandler; |
| static { |
| HandlerThread thread = new HandlerThread("frameMetricsListener"); |
| thread.start(); |
| sMetricsHandler = new Handler(thread.getLooper()); |
| } |
| |
| private Handler mUpdateHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case R.id.jitter_mma: |
| mJitterReport.setText((CharSequence) msg.obj); |
| break; |
| case R.id.totalish_mma: |
| mMostlyTotalFrameTimeReport.setText((CharSequence) msg.obj); |
| break; |
| case R.id.ui_frametime_mma: |
| mUiFrameTimeReport.setText((CharSequence) msg.obj); |
| break; |
| case R.id.rt_frametime_mma: |
| mRenderThreadTimeReport.setText((CharSequence) msg.obj); |
| break; |
| case R.id.total_mma: |
| mTotalFrameTimeReport.setText((CharSequence) msg.obj); |
| break; |
| case R.id.graph: |
| mGraph.addJitterSample(msg.arg1, msg.arg2); |
| break; |
| } |
| } |
| }; |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| setContentView(R.layout.rendering_jitter); |
| View content = findViewById(android.R.id.content); |
| content.setBackground(new AnimatedBackgroundDrawable()); |
| content.setKeepScreenOn(true); |
| mJitterReport = findViewById(R.id.jitter_mma); |
| mMostlyTotalFrameTimeReport = findViewById(R.id.totalish_mma); |
| mUiFrameTimeReport = findViewById(R.id.ui_frametime_mma); |
| mRenderThreadTimeReport = findViewById(R.id.rt_frametime_mma); |
| mTotalFrameTimeReport = findViewById(R.id.total_mma); |
| mGraph = findViewById(R.id.graph); |
| mJitterReport.setText("abcdefghijklmnopqrstuvwxyz"); |
| mMostlyTotalFrameTimeReport.setText("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); |
| mUiFrameTimeReport.setText("0123456789"); |
| mRenderThreadTimeReport.setText(",.!()[]{};"); |
| getWindow().addOnFrameMetricsAvailableListener(mMetricsListener, sMetricsHandler); |
| } |
| |
| public static final class PointGraphView extends View { |
| private static final float[] JITTER_LINES_MS = { |
| .5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 5.0f |
| }; |
| private static final String[] JITTER_LINES_LABELS = makeLabels(JITTER_LINES_MS); |
| private static final int[] JITTER_LINES_COLORS = new int[] { |
| 0xFF00E676, 0xFFFFF176, 0xFFFDD835, 0xFFFBC02D, 0xFFF9A825, |
| 0xFFF57F17, 0xFFDD2C00 |
| }; |
| private Paint mPaint = new Paint(); |
| private float[] mJitterYs = new float[JITTER_LINES_MS.length]; |
| private float mLabelWidth; |
| private float mLabelHeight; |
| private float mDensity; |
| private float mGraphScale; |
| private float mGraphMaxMs; |
| |
| private float[] mJitterPoints; |
| private float[] mJitterAvgPoints; |
| |
| public PointGraphView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| setWillNotDraw(false); |
| mDensity = context.getResources().getDisplayMetrics().density; |
| mPaint.setTextSize(dp(10)); |
| Rect textBounds = new Rect(); |
| mPaint.getTextBounds("8.8", 0, 3, textBounds); |
| mLabelWidth = textBounds.width() + dp(2); |
| mLabelHeight = textBounds.height(); |
| } |
| |
| public void addJitterSample(int jitterUs, int jitterUsAvg) { |
| for (int i = 1; i < mJitterPoints.length - 2; i += 2) { |
| mJitterPoints[i] = mJitterPoints[i + 2]; |
| mJitterAvgPoints[i] = mJitterAvgPoints[i + 2]; |
| } |
| mJitterPoints[mJitterPoints.length - 1] = |
| getHeight() - mGraphScale * (jitterUs / 1000.0f); |
| mJitterAvgPoints[mJitterAvgPoints.length - 1] = |
| getHeight() - mGraphScale * (jitterUsAvg / 1000.0f); |
| invalidate(); |
| } |
| |
| private float dp(float dp) { |
| return mDensity * dp; |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| canvas.drawColor(0x90000000); |
| int h = getHeight(); |
| int w = getWidth(); |
| mPaint.setColor(Color.WHITE); |
| mPaint.setStrokeWidth(dp(1)); |
| canvas.drawLine(mLabelWidth, 0, mLabelWidth, h, mPaint); |
| for (int i = 0; i < JITTER_LINES_LABELS.length; i++) { |
| canvas.drawText(JITTER_LINES_LABELS[i], |
| 0, (float) Math.floor(mJitterYs[i] + mLabelHeight * .5f), mPaint); |
| } |
| for (int i = 0; i < JITTER_LINES_LABELS.length; i++) { |
| mPaint.setColor(JITTER_LINES_COLORS[i]); |
| canvas.drawLine(mLabelWidth, mJitterYs[i], w, mJitterYs[i], mPaint); |
| } |
| mPaint.setStrokeWidth(dp(2)); |
| mPaint.setColor(Color.WHITE); |
| canvas.drawPoints(mJitterPoints, mPaint); |
| mPaint.setColor(0xFF2196F3); |
| canvas.drawPoints(mJitterAvgPoints, mPaint); |
| } |
| |
| @Override |
| protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
| super.onSizeChanged(w, h, oldw, oldh); |
| int graphWidth = (int) ((w - mLabelWidth - dp(1)) / mDensity); |
| float[] oldJitterPoints = mJitterPoints; |
| float[] oldJitterAvgPoints = mJitterAvgPoints; |
| mJitterPoints = new float[graphWidth * 2]; |
| mJitterAvgPoints = new float[graphWidth * 2]; |
| for (int i = 0; i < mJitterPoints.length; i += 2) { |
| mJitterPoints[i] = mLabelWidth + (i / 2 + 1) * mDensity; |
| mJitterAvgPoints[i] = mJitterPoints[i]; |
| } |
| if (oldJitterPoints != null) { |
| int newIndexShift = Math.max(mJitterPoints.length - oldJitterPoints.length, 0); |
| int oldIndexShift = oldJitterPoints.length - mJitterPoints.length; |
| for (int i = 1 + newIndexShift; i < mJitterPoints.length; i += 2) { |
| mJitterPoints[i] = oldJitterPoints[i + oldIndexShift]; |
| mJitterAvgPoints[i] = oldJitterAvgPoints[i + oldIndexShift]; |
| } |
| } |
| mGraphMaxMs = JITTER_LINES_MS[JITTER_LINES_MS.length - 1] + .5f; |
| mGraphScale = (h / mGraphMaxMs); |
| for (int i = 0; i < JITTER_LINES_MS.length; i++) { |
| mJitterYs[i] = (float) Math.floor(h - mGraphScale * JITTER_LINES_MS[i]); |
| } |
| } |
| |
| private static String[] makeLabels(float[] divisions) { |
| String[] ret = new String[divisions.length]; |
| for (int i = 0; i < divisions.length; i++) { |
| ret[i] = Float.toString(divisions[i]); |
| } |
| return ret; |
| } |
| } |
| |
| private final OnFrameMetricsAvailableListener mMetricsListener = new OnFrameMetricsAvailableListener() { |
| private final static double WEIGHT = 40; |
| private long mPreviousFrameTotal; |
| private double mJitterMma; |
| private double mUiFrametimeMma; |
| private double mRtFrametimeMma; |
| private double mTotalFrametimeMma; |
| private double mMostlyTotalFrametimeMma; |
| private boolean mNeedsFirstValues = true; |
| |
| @Override |
| public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, |
| int dropCountSinceLastInvocation) { |
| if (frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME) == 1) { |
| return; |
| } |
| |
| long uiDuration = frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION) |
| + frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION) |
| + frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) |
| + frameMetrics.getMetric(FrameMetrics.DRAW_DURATION); |
| long rtDuration = frameMetrics.getMetric(FrameMetrics.SYNC_DURATION) |
| + frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION); |
| long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION); |
| long jitter = Math.abs(totalDuration - mPreviousFrameTotal); |
| if (mNeedsFirstValues) { |
| mJitterMma = 0; |
| mUiFrametimeMma = uiDuration; |
| mRtFrametimeMma = rtDuration; |
| mTotalFrametimeMma = totalDuration; |
| mMostlyTotalFrametimeMma = uiDuration + rtDuration; |
| mNeedsFirstValues = false; |
| } else { |
| mJitterMma = add(mJitterMma, jitter); |
| mUiFrametimeMma = add(mUiFrametimeMma, uiDuration); |
| mRtFrametimeMma = add(mRtFrametimeMma, rtDuration); |
| mTotalFrametimeMma = add(mTotalFrametimeMma, totalDuration); |
| mMostlyTotalFrametimeMma = add(mMostlyTotalFrametimeMma, uiDuration + rtDuration); |
| } |
| mPreviousFrameTotal = totalDuration; |
| mUpdateHandler.obtainMessage(R.id.jitter_mma, |
| String.format("Jitter: %.3fms", toMs(mJitterMma))).sendToTarget(); |
| mUpdateHandler.obtainMessage(R.id.totalish_mma, |
| String.format("CPU-total duration: %.3fms", toMs(mMostlyTotalFrametimeMma))).sendToTarget(); |
| mUpdateHandler.obtainMessage(R.id.ui_frametime_mma, |
| String.format("UI duration: %.3fms", toMs(mUiFrametimeMma))).sendToTarget(); |
| mUpdateHandler.obtainMessage(R.id.rt_frametime_mma, |
| String.format("RT duration: %.3fms", toMs(mRtFrametimeMma))).sendToTarget(); |
| mUpdateHandler.obtainMessage(R.id.total_mma, |
| String.format("Total duration: %.3fms", toMs(mTotalFrametimeMma))).sendToTarget(); |
| mUpdateHandler.obtainMessage(R.id.graph, (int) (jitter / 1000), |
| (int) (mJitterMma / 1000)).sendToTarget(); |
| } |
| |
| double add(double previous, double today) { |
| return (((WEIGHT - 1) * previous) + today) / WEIGHT; |
| } |
| |
| double toMs(double val) { |
| return val / 1000000; |
| } |
| }; |
| |
| private static final class AnimatedBackgroundDrawable extends Drawable { |
| private static final int FROM_COLOR = 0xFF18FFFF; |
| private static final int TO_COLOR = 0xFF40C4FF; |
| private static final int DURATION = 1400; |
| |
| private final Paint mPaint; |
| private boolean mReverse; |
| private long mStartTime; |
| private int mColor; |
| |
| private boolean mReverseX; |
| private boolean mReverseY; |
| private float mX; |
| private float mY; |
| private float mRadius; |
| private float mMoveStep = 10.0f; |
| |
| public AnimatedBackgroundDrawable() { |
| mPaint = new Paint(); |
| mPaint.setColor(0xFFFFFF00); |
| mPaint.setAntiAlias(true); |
| } |
| |
| @Override |
| public void draw(Canvas canvas) { |
| stepColor(); |
| canvas.drawColor(mColor); |
| |
| mX += (mReverseX ? -mMoveStep : mMoveStep); |
| mY += (mReverseY ? -mMoveStep : mMoveStep); |
| clampXY(); |
| canvas.drawCircle(mX, mY, mRadius, mPaint); |
| |
| invalidateSelf(); |
| } |
| |
| private void clampXY() { |
| if (mX <= mRadius) { |
| mReverseX = false; |
| mX = mRadius; |
| } |
| if (mY <= mRadius) { |
| mReverseY = false; |
| mY = mRadius; |
| } |
| float maxX = getBounds().width() - mRadius; |
| if (mX >= maxX) { |
| mReverseX = true; |
| mX = maxX; |
| } |
| float maxY = getBounds().height() - mRadius; |
| if (mY >= maxY) { |
| mReverseY = true; |
| mY = maxY; |
| } |
| } |
| |
| @Override |
| protected void onBoundsChange(Rect bounds) { |
| super.onBoundsChange(bounds); |
| mMoveStep = Math.min(bounds.width(), bounds.height()) / 130.0f; |
| mRadius = Math.min(bounds.width(), bounds.height()) / 20.0f; |
| } |
| |
| @Override |
| public void setAlpha(int alpha) { |
| } |
| |
| @Override |
| public void setColorFilter(ColorFilter colorFilter) { |
| } |
| |
| @Override |
| public int getOpacity() { |
| return PixelFormat.OPAQUE; |
| } |
| |
| private void stepColor() { |
| if (mStartTime == 0) { |
| mStartTime = AnimationUtils.currentAnimationTimeMillis(); |
| } |
| float frac = (AnimationUtils.currentAnimationTimeMillis() - mStartTime) |
| / (float) DURATION; |
| if (frac > 1.0f) frac = 1.0f; |
| int dest = mReverse ? FROM_COLOR : TO_COLOR; |
| int src = mReverse ? TO_COLOR : FROM_COLOR; |
| int r = (int) (Color.red(src) + (Color.red(dest) - Color.red(src)) * frac); |
| int g = (int) (Color.green(src) + (Color.green(dest) - Color.green(src)) * frac); |
| int b = (int) (Color.blue(src) + (Color.blue(dest) - Color.blue(src)) * frac); |
| mColor = Color.rgb(r, g, b); |
| if (frac == 1.0f) { |
| mStartTime = 0; |
| mReverse = !mReverse; |
| } |
| } |
| } |
| } |