John Reck | fe5e7b7 | 2014-05-23 17:42:28 -0700 | [diff] [blame] | 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 |
| 43 | static const SkColor ELEMENT_COLORS[] = { 0xcf3e66cc, 0xcf8f00ff, 0xcfdc3912, 0xcfe69800 }; |
| 44 | static const SkColor CURRENT_FRAME_COLOR = 0xcf5faa4d; |
| 45 | static 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 | |
| 51 | namespace android { |
| 52 | namespace uirenderer { |
| 53 | |
| 54 | static int dpToPx(int dp, float density) { |
| 55 | return (int) (dp * density + 0.5f); |
| 56 | } |
| 57 | |
| 58 | DrawProfiler::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 | |
| 71 | DrawProfiler::~DrawProfiler() { |
| 72 | destroyData(); |
| 73 | } |
| 74 | |
| 75 | void 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 | |
| 84 | void 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 | |
| 90 | void 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 | |
| 97 | void 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 | |
| 104 | void 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 | |
John Reck | e4267ea | 2014-06-03 15:53:15 -0700 | [diff] [blame] | 112 | void DrawProfiler::unionDirty(SkRect* dirty) { |
John Reck | fe5e7b7 | 2014-05-23 17:42:28 -0700 | [diff] [blame] | 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 | |
| 121 | void 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 | |
| 132 | void 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 | |
| 147 | void DrawProfiler::destroyData() { |
| 148 | delete mData; |
| 149 | mData = NULL; |
| 150 | } |
| 151 | |
| 152 | void 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 | |
| 161 | void 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 | |
| 175 | void 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 | |
| 183 | void 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 | |
| 194 | void 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 | |
| 206 | DrawProfiler::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 | |
| 219 | bool 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 | |
| 233 | void 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 */ |