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
31 static const SkColor THRESHOLD_COLOR = 0xff5faa4d;
32 static const SkColor BAR_FAST_ALPHA = 0x8F000000;
33 static 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
40 namespace android {
41 namespace uirenderer {
42
43 struct BarSegment {
44 FrameInfoIndex start;
45 FrameInfoIndex end;
46 SkColor color;
47 };
48
49 static 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
dpToPx(int dp,float density)59 static int dpToPx(int dp, float density) {
60 return (int) (dp * density + 0.5f);
61 }
62
FrameInfoVisualizer(FrameInfoSource & source)63 FrameInfoVisualizer::FrameInfoVisualizer(FrameInfoSource& source)
64 : mFrameSource(source) {
65 setDensity(1);
66 }
67
~FrameInfoVisualizer()68 FrameInfoVisualizer::~FrameInfoVisualizer() {
69 destroyData();
70 }
71
setDensity(float density)72 void 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
unionDirty(SkRect * dirty)80 void 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
draw(OpenGLRenderer * canvas)90 void 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
createData()119 void 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
destroyData()126 void FrameInfoVisualizer::destroyData() {
127 mFastRects.reset(nullptr);
128 mJankyRects.reset(nullptr);
129 }
130
initializeRects(const int baseline,const int width)131 void 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
nextBarSegment(FrameInfoIndex start,FrameInfoIndex end)168 void 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
drawGraph(OpenGLRenderer * canvas)196 void 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
drawThreshold(OpenGLRenderer * canvas)207 void 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
consumeProperties()219 bool 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
dumpData(int fd)240 void 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