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 "DrawProfiler.h" 17 18 #include <cutils/compiler.h> 19 20 #include "OpenGLRenderer.h" 21 #include "Properties.h" 22 23 #define DEFAULT_MAX_FRAMES 128 24 25 #define RETURN_IF_PROFILING_DISABLED() if (CC_LIKELY(mType == kNone)) return 26 #define RETURN_IF_DISABLED() if (CC_LIKELY(mType == kNone && !mShowDirtyRegions)) return 27 28 #define NANOS_TO_MILLIS_FLOAT(nanos) ((nanos) * 0.000001f) 29 30 #define PROFILE_DRAW_WIDTH 3 31 #define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2 32 #define PROFILE_DRAW_DP_PER_MS 7 33 34 // Number of floats we want to display from FrameTimingData 35 // If this is changed make sure to update the indexes below 36 #define NUM_ELEMENTS 4 37 38 #define RECORD_INDEX 0 39 #define PREPARE_INDEX 1 40 #define PLAYBACK_INDEX 2 41 #define SWAPBUFFERS_INDEX 3 42 43 // Must be NUM_ELEMENTS in size 44 static const SkColor ELEMENT_COLORS[] = { 0xcf3e66cc, 0xcf8f00ff, 0xcfdc3912, 0xcfe69800 }; 45 static const SkColor CURRENT_FRAME_COLOR = 0xcf5faa4d; 46 static const SkColor THRESHOLD_COLOR = 0xff5faa4d; 47 48 // We could get this from TimeLord and use the actual frame interval, but 49 // this is good enough 50 #define FRAME_THRESHOLD 16 51 52 namespace android { 53 namespace uirenderer { 54 55 static int dpToPx(int dp, float density) { 56 return (int) (dp * density + 0.5f); 57 } 58 59 DrawProfiler::DrawProfiler() 60 : mType(kNone) 61 , mDensity(0) 62 , mData(NULL) 63 , mDataSize(0) 64 , mCurrentFrame(-1) 65 , mPreviousTime(0) 66 , mVerticalUnit(0) 67 , mHorizontalUnit(0) 68 , mThresholdStroke(0) 69 , mShowDirtyRegions(false) 70 , mFlashToggle(false) { 71 setDensity(1); 72 } 73 74 DrawProfiler::~DrawProfiler() { 75 destroyData(); 76 } 77 78 void DrawProfiler::setDensity(float density) { 79 if (CC_UNLIKELY(mDensity != density)) { 80 mDensity = density; 81 mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density); 82 mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density); 83 mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); 84 } 85 } 86 87 void DrawProfiler::startFrame(nsecs_t recordDurationNanos) { 88 RETURN_IF_PROFILING_DISABLED(); 89 mData[mCurrentFrame].record = NANOS_TO_MILLIS_FLOAT(recordDurationNanos); 90 mPreviousTime = systemTime(CLOCK_MONOTONIC); 91 } 92 93 void DrawProfiler::markPlaybackStart() { 94 RETURN_IF_PROFILING_DISABLED(); 95 nsecs_t now = systemTime(CLOCK_MONOTONIC); 96 mData[mCurrentFrame].prepare = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime); 97 mPreviousTime = now; 98 } 99 100 void DrawProfiler::markPlaybackEnd() { 101 RETURN_IF_PROFILING_DISABLED(); 102 nsecs_t now = systemTime(CLOCK_MONOTONIC); 103 mData[mCurrentFrame].playback = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime); 104 mPreviousTime = now; 105 } 106 107 void DrawProfiler::finishFrame() { 108 RETURN_IF_PROFILING_DISABLED(); 109 nsecs_t now = systemTime(CLOCK_MONOTONIC); 110 mData[mCurrentFrame].swapBuffers = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime); 111 mPreviousTime = now; 112 mCurrentFrame = (mCurrentFrame + 1) % mDataSize; 113 } 114 115 void DrawProfiler::unionDirty(SkRect* dirty) { 116 RETURN_IF_DISABLED(); 117 // Not worth worrying about minimizing the dirty region for debugging, so just 118 // dirty the entire viewport. 119 if (dirty) { 120 mDirtyRegion = *dirty; 121 dirty->setEmpty(); 122 } 123 } 124 125 void DrawProfiler::draw(OpenGLRenderer* canvas) { 126 RETURN_IF_DISABLED(); 127 128 if (mShowDirtyRegions) { 129 mFlashToggle = !mFlashToggle; 130 if (mFlashToggle) { 131 SkPaint paint; 132 paint.setColor(0x7fff0000); 133 canvas->drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop, 134 mDirtyRegion.fRight, mDirtyRegion.fBottom, &paint); 135 } 136 } 137 138 if (mType == kBars) { 139 prepareShapes(canvas->getViewportHeight()); 140 drawGraph(canvas); 141 drawCurrentFrame(canvas); 142 drawThreshold(canvas); 143 } 144 } 145 146 void DrawProfiler::createData() { 147 if (mData) return; 148 149 mDataSize = property_get_int32(PROPERTY_PROFILE_MAXFRAMES, DEFAULT_MAX_FRAMES); 150 if (mDataSize <= 0) mDataSize = 1; 151 if (mDataSize > 4096) mDataSize = 4096; // Reasonable maximum 152 mData = (FrameTimingData*) calloc(mDataSize, sizeof(FrameTimingData)); 153 mRects = new float*[NUM_ELEMENTS]; 154 for (int i = 0; i < NUM_ELEMENTS; i++) { 155 // 4 floats per rect 156 mRects[i] = (float*) calloc(mDataSize, 4 * sizeof(float)); 157 } 158 mCurrentFrame = 0; 159 } 160 161 void DrawProfiler::destroyData() { 162 delete mData; 163 mData = NULL; 164 } 165 166 void DrawProfiler::addRect(Rect& r, float data, float* shapeOutput) { 167 r.top = r.bottom - (data * mVerticalUnit); 168 shapeOutput[0] = r.left; 169 shapeOutput[1] = r.top; 170 shapeOutput[2] = r.right; 171 shapeOutput[3] = r.bottom; 172 r.bottom = r.top; 173 } 174 175 void DrawProfiler::prepareShapes(const int baseline) { 176 Rect r; 177 r.right = mHorizontalUnit; 178 for (int i = 0; i < mDataSize; i++) { 179 const int shapeIndex = i * 4; 180 r.bottom = baseline; 181 addRect(r, mData[i].record, mRects[RECORD_INDEX] + shapeIndex); 182 addRect(r, mData[i].prepare, mRects[PREPARE_INDEX] + shapeIndex); 183 addRect(r, mData[i].playback, mRects[PLAYBACK_INDEX] + shapeIndex); 184 addRect(r, mData[i].swapBuffers, mRects[SWAPBUFFERS_INDEX] + shapeIndex); 185 r.translate(mHorizontalUnit, 0); 186 } 187 } 188 189 void DrawProfiler::drawGraph(OpenGLRenderer* canvas) { 190 SkPaint paint; 191 for (int i = 0; i < NUM_ELEMENTS; i++) { 192 paint.setColor(ELEMENT_COLORS[i]); 193 canvas->drawRects(mRects[i], mDataSize * 4, &paint); 194 } 195 } 196 197 void DrawProfiler::drawCurrentFrame(OpenGLRenderer* canvas) { 198 // This draws a solid rect over the entirety of the current frame's shape 199 // To do so we use the bottom of mRects[0] and the top of mRects[NUM_ELEMENTS-1] 200 // which will therefore fully overlap the previously drawn rects 201 SkPaint paint; 202 paint.setColor(CURRENT_FRAME_COLOR); 203 const int i = mCurrentFrame * 4; 204 canvas->drawRect(mRects[0][i], mRects[NUM_ELEMENTS-1][i+1], mRects[0][i+2], 205 mRects[0][i+3], &paint); 206 } 207 208 void DrawProfiler::drawThreshold(OpenGLRenderer* canvas) { 209 SkPaint paint; 210 paint.setColor(THRESHOLD_COLOR); 211 paint.setStrokeWidth(mThresholdStroke); 212 213 float pts[4]; 214 pts[0] = 0.0f; 215 pts[1] = pts[3] = canvas->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit); 216 pts[2] = canvas->getViewportWidth(); 217 canvas->drawLines(pts, 4, &paint); 218 } 219 220 DrawProfiler::ProfileType DrawProfiler::loadRequestedProfileType() { 221 ProfileType type = kNone; 222 char buf[PROPERTY_VALUE_MAX] = {'\0',}; 223 if (property_get(PROPERTY_PROFILE, buf, "") > 0) { 224 if (!strcmp(buf, PROPERTY_PROFILE_VISUALIZE_BARS)) { 225 type = kBars; 226 } else if (!strcmp(buf, "true")) { 227 type = kConsole; 228 } 229 } 230 return type; 231 } 232 233 bool DrawProfiler::loadSystemProperties() { 234 bool changed = false; 235 ProfileType newType = loadRequestedProfileType(); 236 if (newType != mType) { 237 mType = newType; 238 if (mType == kNone) { 239 destroyData(); 240 } else { 241 createData(); 242 } 243 changed = true; 244 } 245 bool showDirty = property_get_bool(PROPERTY_DEBUG_SHOW_DIRTY_REGIONS, false); 246 if (showDirty != mShowDirtyRegions) { 247 mShowDirtyRegions = showDirty; 248 changed = true; 249 } 250 return changed; 251 } 252 253 void DrawProfiler::dumpData(int fd) { 254 RETURN_IF_PROFILING_DISABLED(); 255 256 // This method logs the last N frames (where N is <= mDataSize) since the 257 // last call to dumpData(). In other words if there's a dumpData(), draw frame, 258 // dumpData(), the last dumpData() should only log 1 frame. 259 260 const FrameTimingData emptyData = {0, 0, 0, 0}; 261 262 FILE *file = fdopen(fd, "a"); 263 fprintf(file, "\n\tDraw\tPrepare\tProcess\tExecute\n"); 264 265 for (int frameOffset = 1; frameOffset <= mDataSize; frameOffset++) { 266 int i = (mCurrentFrame + frameOffset) % mDataSize; 267 if (!memcmp(mData + i, &emptyData, sizeof(FrameTimingData))) { 268 continue; 269 } 270 fprintf(file, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n", 271 mData[i].record, mData[i].prepare, mData[i].playback, mData[i].swapBuffers); 272 } 273 // reset the buffer 274 memset(mData, 0, sizeof(FrameTimingData) * mDataSize); 275 mCurrentFrame = 0; 276 277 fflush(file); 278 } 279 280 } /* namespace uirenderer */ 281 } /* namespace android */ 282