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