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