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 "FrameInfoVisualizer.h"
17
18#include "OpenGLRenderer.h"
19
20#include <cutils/compiler.h>
21#include <array>
22
23#define RETURN_IF_PROFILING_DISABLED() if (CC_LIKELY(mType == ProfileType::None)) return
24#define RETURN_IF_DISABLED() if (CC_LIKELY(mType == ProfileType::None && !mShowDirtyRegions)) return
25
26#define PROFILE_DRAW_WIDTH 3
27#define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2
28#define PROFILE_DRAW_DP_PER_MS 7
29
30// Must be NUM_ELEMENTS in size
31static const SkColor THRESHOLD_COLOR = 0xff5faa4d;
32static const SkColor BAR_FAST_ALPHA = 0x8F000000;
33static const SkColor BAR_JANKY_ALPHA = 0xDF000000;
34
35// We could get this from TimeLord and use the actual frame interval, but
36// this is good enough
37#define FRAME_THRESHOLD 16
38#define FRAME_THRESHOLD_NS 16000000
39
40namespace android {
41namespace uirenderer {
42
43struct BarSegment {
44    FrameInfoIndex start;
45    FrameInfoIndex end;
46    SkColor color;
47};
48
49static const std::array<BarSegment,7> Bar {{
50    { FrameInfoIndex::IntendedVsync, FrameInfoIndex::HandleInputStart, 0x00796B },
51    { FrameInfoIndex::HandleInputStart, FrameInfoIndex::PerformTraversalsStart, 0x388E3C },
52    { FrameInfoIndex::PerformTraversalsStart, FrameInfoIndex::DrawStart, 0x689F38},
53    { FrameInfoIndex::DrawStart, FrameInfoIndex::SyncStart, 0x2196F3},
54    { FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart, 0x4FC3F7},
55    { FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers, 0xF44336},
56    { FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted, 0xFF9800},
57}};
58
59static int dpToPx(int dp, float density) {
60    return (int) (dp * density + 0.5f);
61}
62
63FrameInfoVisualizer::FrameInfoVisualizer(FrameInfoSource& source)
64        : mFrameSource(source) {
65    setDensity(1);
66}
67
68FrameInfoVisualizer::~FrameInfoVisualizer() {
69    destroyData();
70}
71
72void FrameInfoVisualizer::setDensity(float density) {
73    if (CC_UNLIKELY(mDensity != density)) {
74        mDensity = density;
75        mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
76        mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
77    }
78}
79
80void FrameInfoVisualizer::unionDirty(SkRect* dirty) {
81    RETURN_IF_DISABLED();
82    // Not worth worrying about minimizing the dirty region for debugging, so just
83    // dirty the entire viewport.
84    if (dirty) {
85        mDirtyRegion = *dirty;
86        dirty->setEmpty();
87    }
88}
89
90void FrameInfoVisualizer::draw(OpenGLRenderer* canvas) {
91    RETURN_IF_DISABLED();
92
93    if (mShowDirtyRegions) {
94        mFlashToggle = !mFlashToggle;
95        if (mFlashToggle) {
96            SkPaint paint;
97            paint.setColor(0x7fff0000);
98            canvas->drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop,
99                    mDirtyRegion.fRight, mDirtyRegion.fBottom, &paint);
100        }
101    }
102
103    if (mType == ProfileType::Bars) {
104        // Patch up the current frame to pretend we ended here. CanvasContext
105        // will overwrite these values with the real ones after we return.
106        // This is a bit nicer looking than the vague green bar, as we have
107        // valid data for almost all the stages and a very good idea of what
108        // the issue stage will look like, too
109        FrameInfo& info = mFrameSource.back();
110        info.markSwapBuffers();
111        info.markFrameCompleted();
112
113        initializeRects(canvas->getViewportHeight(), canvas->getViewportWidth());
114        drawGraph(canvas);
115        drawThreshold(canvas);
116    }
117}
118
119void FrameInfoVisualizer::createData() {
120    if (mFastRects.get()) return;
121
122    mFastRects.reset(new float[mFrameSource.capacity() * 4]);
123    mJankyRects.reset(new float[mFrameSource.capacity() * 4]);
124}
125
126void FrameInfoVisualizer::destroyData() {
127    mFastRects.reset(nullptr);
128    mJankyRects.reset(nullptr);
129}
130
131void FrameInfoVisualizer::initializeRects(const int baseline, const int width) {
132    // Target the 95% mark for the current frame
133    float right = width * .95;
134    float baseLineWidth = right / mFrameSource.capacity();
135    mNumFastRects = 0;
136    mNumJankyRects = 0;
137    int fast_i = 0, janky_i = 0;
138    // Set the bottom of all the shapes to the baseline
139    for (int fi = mFrameSource.size() - 1; fi >= 0; fi--) {
140        if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
141            continue;
142        }
143        float lineWidth = baseLineWidth;
144        float* rect;
145        int ri;
146        // Rects are LTRB
147        if (mFrameSource[fi].totalDuration() <= FRAME_THRESHOLD_NS) {
148            rect = mFastRects.get();
149            ri = fast_i;
150            fast_i += 4;
151            mNumFastRects++;
152        } else {
153            rect = mJankyRects.get();
154            ri = janky_i;
155            janky_i += 4;
156            mNumJankyRects++;
157            lineWidth *= 2;
158        }
159
160        rect[ri + 0] = right - lineWidth;
161        rect[ri + 1] = baseline;
162        rect[ri + 2] = right;
163        rect[ri + 3] = baseline;
164        right -= lineWidth;
165    }
166}
167
168void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex end) {
169    int fast_i = (mNumFastRects - 1) * 4;
170    int janky_i = (mNumJankyRects - 1) * 4;;
171    for (size_t fi = 0; fi < mFrameSource.size(); fi++) {
172        if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
173            continue;
174        }
175
176        float* rect;
177        int ri;
178        // Rects are LTRB
179        if (mFrameSource[fi].totalDuration() <= FRAME_THRESHOLD_NS) {
180            rect = mFastRects.get();
181            ri = fast_i;
182            fast_i -= 4;
183        } else {
184            rect = mJankyRects.get();
185            ri = janky_i;
186            janky_i -= 4;
187        }
188
189        // Set the bottom to the old top (build upwards)
190        rect[ri + 3] = rect[ri + 1];
191        // Move the top up by the duration
192        rect[ri + 1] -= mVerticalUnit * durationMS(fi, start, end);
193    }
194}
195
196void FrameInfoVisualizer::drawGraph(OpenGLRenderer* canvas) {
197    SkPaint paint;
198    for (size_t i = 0; i < Bar.size(); i++) {
199        nextBarSegment(Bar[i].start, Bar[i].end);
200        paint.setColor(Bar[i].color | BAR_FAST_ALPHA);
201        canvas->drawRects(mFastRects.get(), mNumFastRects * 4, &paint);
202        paint.setColor(Bar[i].color | BAR_JANKY_ALPHA);
203        canvas->drawRects(mJankyRects.get(), mNumJankyRects * 4, &paint);
204    }
205}
206
207void FrameInfoVisualizer::drawThreshold(OpenGLRenderer* canvas) {
208    SkPaint paint;
209    paint.setColor(THRESHOLD_COLOR);
210    paint.setStrokeWidth(mThresholdStroke);
211
212    float pts[4];
213    pts[0] = 0.0f;
214    pts[1] = pts[3] = canvas->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit);
215    pts[2] = canvas->getViewportWidth();
216    canvas->drawLines(pts, 4, &paint);
217}
218
219bool FrameInfoVisualizer::consumeProperties() {
220    bool changed = false;
221    ProfileType newType = Properties::getProfileType();
222    if (newType != mType) {
223        mType = newType;
224        if (mType == ProfileType::None) {
225            destroyData();
226        } else {
227            createData();
228        }
229        changed = true;
230    }
231
232    bool showDirty = Properties::showDirtyRegions;
233    if (showDirty != mShowDirtyRegions) {
234        mShowDirtyRegions = showDirty;
235        changed = true;
236    }
237    return changed;
238}
239
240void FrameInfoVisualizer::dumpData(int fd) {
241    RETURN_IF_PROFILING_DISABLED();
242
243    // This method logs the last N frames (where N is <= mDataSize) since the
244    // last call to dumpData(). In other words if there's a dumpData(), draw frame,
245    // dumpData(), the last dumpData() should only log 1 frame.
246
247    FILE *file = fdopen(fd, "a");
248    fprintf(file, "\n\tDraw\tPrepare\tProcess\tExecute\n");
249
250    for (size_t i = 0; i < mFrameSource.size(); i++) {
251        if (mFrameSource[i][FrameInfoIndex::IntendedVsync] <= mLastFrameLogged) {
252            continue;
253        }
254        mLastFrameLogged = mFrameSource[i][FrameInfoIndex::IntendedVsync];
255        fprintf(file, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n",
256                durationMS(i, FrameInfoIndex::IntendedVsync, FrameInfoIndex::SyncStart),
257                durationMS(i, FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart),
258                durationMS(i, FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers),
259                durationMS(i, FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted));
260    }
261
262    fflush(file);
263}
264
265} /* namespace uirenderer */
266} /* namespace android */
267