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