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
17package com.android.test.uibench;
18
19import android.app.Activity;
20import android.content.Context;
21import android.graphics.Canvas;
22import android.graphics.Color;
23import android.graphics.ColorFilter;
24import android.graphics.Paint;
25import android.graphics.PixelFormat;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.HandlerThread;
31import android.os.Message;
32import android.util.AttributeSet;
33import android.view.FrameMetrics;
34import android.view.View;
35import android.view.Window;
36import android.view.Window.OnFrameMetricsAvailableListener;
37import android.view.animation.AnimationUtils;
38import android.widget.TextView;
39
40public 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");
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
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
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
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
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}
367