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