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