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 "BakedOpRenderer.h"
19#include "IProfileRenderer.h"
20#include "utils/Color.h"
21
22#include <cutils/compiler.h>
23#include <array>
24
25#define RETURN_IF_PROFILING_DISABLED() \
26    if (CC_LIKELY(mType == ProfileType::None)) return
27#define RETURN_IF_DISABLED() \
28    if (CC_LIKELY(mType == ProfileType::None && !mShowDirtyRegions)) return
29
30#define PROFILE_DRAW_WIDTH 3
31#define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2
32#define PROFILE_DRAW_DP_PER_MS 7
33
34namespace android {
35namespace uirenderer {
36
37// Must be NUM_ELEMENTS in size
38static const SkColor THRESHOLD_COLOR = Color::Green_500;
39static const SkColor BAR_FAST_MASK = 0x8FFFFFFF;
40static const SkColor BAR_JANKY_MASK = 0xDFFFFFFF;
41
42// We could get this from TimeLord and use the actual frame interval, but
43// this is good enough
44#define FRAME_THRESHOLD 16
45#define FRAME_THRESHOLD_NS 16000000
46
47struct BarSegment {
48    FrameInfoIndex start;
49    FrameInfoIndex end;
50    SkColor color;
51};
52
53static const std::array<BarSegment, 7> Bar{{
54        {FrameInfoIndex::IntendedVsync, FrameInfoIndex::HandleInputStart, Color::Teal_700},
55        {FrameInfoIndex::HandleInputStart, FrameInfoIndex::PerformTraversalsStart,
56         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) : mFrameSource(source) {
69    setDensity(1);
70}
71
72FrameInfoVisualizer::~FrameInfoVisualizer() {
73    destroyData();
74}
75
76void FrameInfoVisualizer::setDensity(float density) {
77    if (CC_UNLIKELY(mDensity != density)) {
78        mDensity = density;
79        mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
80        mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
81    }
82}
83
84void FrameInfoVisualizer::unionDirty(SkRect* dirty) {
85    RETURN_IF_DISABLED();
86    // Not worth worrying about minimizing the dirty region for debugging, so just
87    // dirty the entire viewport.
88    if (dirty) {
89        mDirtyRegion = *dirty;
90        dirty->setEmpty();
91    }
92}
93
94void FrameInfoVisualizer::draw(IProfileRenderer& renderer) {
95    RETURN_IF_DISABLED();
96
97    if (mShowDirtyRegions) {
98        mFlashToggle = !mFlashToggle;
99        if (mFlashToggle) {
100            SkPaint paint;
101            paint.setColor(0x7fff0000);
102            renderer.drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop, mDirtyRegion.fRight,
103                              mDirtyRegion.fBottom, paint);
104        }
105    }
106
107    if (mType == ProfileType::Bars) {
108        // Patch up the current frame to pretend we ended here. CanvasContext
109        // will overwrite these values with the real ones after we return.
110        // This is a bit nicer looking than the vague green bar, as we have
111        // valid data for almost all the stages and a very good idea of what
112        // the issue stage will look like, too
113        FrameInfo& info = mFrameSource.back();
114        info.markSwapBuffers();
115        info.markFrameCompleted();
116
117        initializeRects(renderer.getViewportHeight(), renderer.getViewportWidth());
118        drawGraph(renderer);
119        drawThreshold(renderer);
120    }
121}
122
123void FrameInfoVisualizer::createData() {
124    if (mFastRects.get()) return;
125
126    mFastRects.reset(new float[mFrameSource.capacity() * 4]);
127    mJankyRects.reset(new float[mFrameSource.capacity() * 4]);
128}
129
130void FrameInfoVisualizer::destroyData() {
131    mFastRects.reset(nullptr);
132    mJankyRects.reset(nullptr);
133}
134
135void FrameInfoVisualizer::initializeRects(const int baseline, const int width) {
136    // Target the 95% mark for the current frame
137    float right = width * .95;
138    float baseLineWidth = right / mFrameSource.capacity();
139    mNumFastRects = 0;
140    mNumJankyRects = 0;
141    int fast_i = 0, janky_i = 0;
142    // Set the bottom of all the shapes to the baseline
143    for (int fi = mFrameSource.size() - 1; fi >= 0; fi--) {
144        if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
145            continue;
146        }
147        float lineWidth = baseLineWidth;
148        float* rect;
149        int ri;
150        // Rects are LTRB
151        if (mFrameSource[fi].totalDuration() <= FRAME_THRESHOLD_NS) {
152            rect = mFastRects.get();
153            ri = fast_i;
154            fast_i += 4;
155            mNumFastRects++;
156        } else {
157            rect = mJankyRects.get();
158            ri = janky_i;
159            janky_i += 4;
160            mNumJankyRects++;
161            lineWidth *= 2;
162        }
163
164        rect[ri + 0] = right - lineWidth;
165        rect[ri + 1] = baseline;
166        rect[ri + 2] = right;
167        rect[ri + 3] = baseline;
168        right -= lineWidth;
169    }
170}
171
172void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex end) {
173    int fast_i = (mNumFastRects - 1) * 4;
174    int janky_i = (mNumJankyRects - 1) * 4;
175    ;
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(IProfileRenderer& 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(IProfileRenderer& renderer) {
213    SkPaint paint;
214    paint.setColor(THRESHOLD_COLOR);
215    float yLocation = renderer.getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit);
216    renderer.drawRect(0.0f, yLocation - mThresholdStroke / 2, renderer.getViewportWidth(),
217                      yLocation + mThresholdStroke / 2, paint);
218}
219
220bool FrameInfoVisualizer::consumeProperties() {
221    bool changed = false;
222    ProfileType newType = Properties::getProfileType();
223    if (newType != mType) {
224        mType = newType;
225        if (mType == ProfileType::None) {
226            destroyData();
227        } else {
228            createData();
229        }
230        changed = true;
231    }
232
233    bool showDirty = Properties::showDirtyRegions;
234    if (showDirty != mShowDirtyRegions) {
235        mShowDirtyRegions = showDirty;
236        changed = true;
237    }
238    return changed;
239}
240
241void FrameInfoVisualizer::dumpData(int fd) {
242    RETURN_IF_PROFILING_DISABLED();
243
244    // This method logs the last N frames (where N is <= mDataSize) since the
245    // last call to dumpData(). In other words if there's a dumpData(), draw frame,
246    // dumpData(), the last dumpData() should only log 1 frame.
247
248    dprintf(fd, "\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        dprintf(fd, "\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
263} /* namespace uirenderer */
264} /* namespace android */
265