DrawProfiler.cpp revision e4267ea4f20740c37c01bfb6aefcf61fddc4566a
1/*
2 * Copyright (C) 2014 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#include "DrawProfiler.h"
17
18#include <cutils/compiler.h>
19
20#include "OpenGLRenderer.h"
21#include "Properties.h"
22
23#define DEFAULT_MAX_FRAMES 128
24
25#define RETURN_IF_DISABLED() if (CC_LIKELY(mType == kNone)) return
26
27#define NANOS_TO_MILLIS_FLOAT(nanos) ((nanos) * 0.000001f)
28
29#define PROFILE_DRAW_WIDTH 3
30#define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2
31#define PROFILE_DRAW_DP_PER_MS 7
32
33// Number of floats we want to display from FrameTimingData
34// If this is changed make sure to update the indexes below
35#define NUM_ELEMENTS 4
36
37#define RECORD_INDEX 0
38#define PREPARE_INDEX 1
39#define PLAYBACK_INDEX 2
40#define SWAPBUFFERS_INDEX 3
41
42// Must be NUM_ELEMENTS in size
43static const SkColor ELEMENT_COLORS[] = { 0xcf3e66cc, 0xcf8f00ff, 0xcfdc3912, 0xcfe69800 };
44static const SkColor CURRENT_FRAME_COLOR = 0xcf5faa4d;
45static const SkColor THRESHOLD_COLOR = 0xff5faa4d;
46
47// We could get this from TimeLord and use the actual frame interval, but
48// this is good enough
49#define FRAME_THRESHOLD 16
50
51namespace android {
52namespace uirenderer {
53
54static int dpToPx(int dp, float density) {
55    return (int) (dp * density + 0.5f);
56}
57
58DrawProfiler::DrawProfiler()
59        : mType(kNone)
60        , mDensity(0)
61        , mData(NULL)
62        , mDataSize(0)
63        , mCurrentFrame(-1)
64        , mPreviousTime(0)
65        , mVerticalUnit(0)
66        , mHorizontalUnit(0)
67        , mThresholdStroke(0) {
68    setDensity(1);
69}
70
71DrawProfiler::~DrawProfiler() {
72    destroyData();
73}
74
75void DrawProfiler::setDensity(float density) {
76    if (CC_UNLIKELY(mDensity != density)) {
77        mDensity = density;
78        mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
79        mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density);
80        mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
81    }
82}
83
84void DrawProfiler::startFrame(nsecs_t recordDurationNanos) {
85    RETURN_IF_DISABLED();
86    mData[mCurrentFrame].record = NANOS_TO_MILLIS_FLOAT(recordDurationNanos);
87    mPreviousTime = systemTime(CLOCK_MONOTONIC);
88}
89
90void DrawProfiler::markPlaybackStart() {
91    RETURN_IF_DISABLED();
92    nsecs_t now = systemTime(CLOCK_MONOTONIC);
93    mData[mCurrentFrame].prepare = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime);
94    mPreviousTime = now;
95}
96
97void DrawProfiler::markPlaybackEnd() {
98    RETURN_IF_DISABLED();
99    nsecs_t now = systemTime(CLOCK_MONOTONIC);
100    mData[mCurrentFrame].playback = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime);
101    mPreviousTime = now;
102}
103
104void DrawProfiler::finishFrame() {
105    RETURN_IF_DISABLED();
106    nsecs_t now = systemTime(CLOCK_MONOTONIC);
107    mData[mCurrentFrame].swapBuffers = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime);
108    mPreviousTime = now;
109    mCurrentFrame = (mCurrentFrame + 1) % mDataSize;
110}
111
112void DrawProfiler::unionDirty(SkRect* dirty) {
113    RETURN_IF_DISABLED();
114    // Not worth worrying about minimizing the dirty region for debugging, so just
115    // dirty the entire viewport.
116    if (dirty) {
117        dirty->setEmpty();
118    }
119}
120
121void DrawProfiler::draw(OpenGLRenderer* canvas) {
122    if (CC_LIKELY(mType != kBars)) {
123        return;
124    }
125
126    prepareShapes(canvas->getViewportHeight());
127    drawGraph(canvas);
128    drawCurrentFrame(canvas);
129    drawThreshold(canvas);
130}
131
132void DrawProfiler::createData() {
133    if (mData) return;
134
135    mDataSize = property_get_int32(PROPERTY_PROFILE_MAXFRAMES, DEFAULT_MAX_FRAMES);
136    if (mDataSize <= 0) mDataSize = 1;
137    if (mDataSize > 4096) mDataSize = 4096; // Reasonable maximum
138    mData = (FrameTimingData*) calloc(mDataSize, sizeof(FrameTimingData));
139    mRects = new float*[NUM_ELEMENTS];
140    for (int i = 0; i < NUM_ELEMENTS; i++) {
141        // 4 floats per rect
142        mRects[i] = (float*) calloc(mDataSize, 4 * sizeof(float));
143    }
144    mCurrentFrame = 0;
145}
146
147void DrawProfiler::destroyData() {
148    delete mData;
149    mData = NULL;
150}
151
152void DrawProfiler::addRect(Rect& r, float data, float* shapeOutput) {
153    r.top = r.bottom - (data * mVerticalUnit);
154    shapeOutput[0] = r.left;
155    shapeOutput[1] = r.top;
156    shapeOutput[2] = r.right;
157    shapeOutput[3] = r.bottom;
158    r.bottom = r.top;
159}
160
161void DrawProfiler::prepareShapes(const int baseline) {
162    Rect r;
163    r.right = mHorizontalUnit;
164    for (int i = 0; i < mDataSize; i++) {
165        const int shapeIndex = i * 4;
166        r.bottom = baseline;
167        addRect(r, mData[i].record, mRects[RECORD_INDEX] + shapeIndex);
168        addRect(r, mData[i].prepare, mRects[PREPARE_INDEX] + shapeIndex);
169        addRect(r, mData[i].playback, mRects[PLAYBACK_INDEX] + shapeIndex);
170        addRect(r, mData[i].swapBuffers, mRects[SWAPBUFFERS_INDEX] + shapeIndex);
171        r.translate(mHorizontalUnit, 0);
172    }
173}
174
175void DrawProfiler::drawGraph(OpenGLRenderer* canvas) {
176    SkPaint paint;
177    for (int i = 0; i < NUM_ELEMENTS; i++) {
178        paint.setColor(ELEMENT_COLORS[i]);
179        canvas->drawRects(mRects[i], mDataSize * 4, &paint);
180    }
181}
182
183void DrawProfiler::drawCurrentFrame(OpenGLRenderer* canvas) {
184    // This draws a solid rect over the entirety of the current frame's shape
185    // To do so we use the bottom of mRects[0] and the top of mRects[NUM_ELEMENTS-1]
186    // which will therefore fully overlap the previously drawn rects
187    SkPaint paint;
188    paint.setColor(CURRENT_FRAME_COLOR);
189    const int i = mCurrentFrame * 4;
190    canvas->drawRect(mRects[0][i], mRects[NUM_ELEMENTS-1][i+1], mRects[0][i+2],
191            mRects[0][i+3], &paint);
192}
193
194void DrawProfiler::drawThreshold(OpenGLRenderer* canvas) {
195    SkPaint paint;
196    paint.setColor(THRESHOLD_COLOR);
197    paint.setStrokeWidth(mThresholdStroke);
198
199    float pts[4];
200    pts[0] = 0.0f;
201    pts[1] = pts[3] = canvas->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit);
202    pts[2] = canvas->getViewportWidth();
203    canvas->drawLines(pts, 4, &paint);
204}
205
206DrawProfiler::ProfileType DrawProfiler::loadRequestedProfileType() {
207    ProfileType type = kNone;
208    char buf[PROPERTY_VALUE_MAX] = {'\0',};
209    if (property_get(PROPERTY_PROFILE, buf, "") > 0) {
210        if (!strcmp(buf, PROPERTY_PROFILE_VISUALIZE_BARS)) {
211            type = kBars;
212        } else if (!strcmp(buf, "true")) {
213            type = kConsole;
214        }
215    }
216    return type;
217}
218
219bool DrawProfiler::loadSystemProperties() {
220    ProfileType newType = loadRequestedProfileType();
221    if (newType != mType) {
222        mType = newType;
223        if (mType == kNone) {
224            destroyData();
225        } else {
226            createData();
227        }
228        return true;
229    }
230    return false;
231}
232
233void DrawProfiler::dumpData(int fd) {
234    RETURN_IF_DISABLED();
235
236    // This method logs the last N frames (where N is <= mDataSize) since the
237    // last call to dumpData(). In other words if there's a dumpData(), draw frame,
238    // dumpData(), the last dumpData() should only log 1 frame.
239
240    const FrameTimingData emptyData = {0, 0, 0, 0};
241
242    FILE *file = fdopen(fd, "a");
243    fprintf(file, "\n\tDraw\tPrepare\tProcess\tExecute\n");
244
245    for (int frameOffset = 1; frameOffset <= mDataSize; frameOffset++) {
246        int i = (mCurrentFrame + frameOffset) % mDataSize;
247        if (!memcmp(mData + i, &emptyData, sizeof(FrameTimingData))) {
248            continue;
249        }
250        fprintf(file, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n",
251                mData[i].record, mData[i].prepare, mData[i].playback, mData[i].swapBuffers);
252    }
253    // reset the buffer
254    memset(mData, 0, sizeof(FrameTimingData) * mDataSize);
255    mCurrentFrame = 0;
256
257    fflush(file);
258}
259
260} /* namespace uirenderer */
261} /* namespace android */
262