|  | /* | 
|  | * Copyright (C) 2014 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  | #include "DrawProfiler.h" | 
|  |  | 
|  | #include <cutils/compiler.h> | 
|  |  | 
|  | #include "OpenGLRenderer.h" | 
|  | #include "Properties.h" | 
|  |  | 
|  | #define DEFAULT_MAX_FRAMES 128 | 
|  |  | 
|  | #define RETURN_IF_DISABLED() if (CC_LIKELY(mType == kNone)) return | 
|  |  | 
|  | #define NANOS_TO_MILLIS_FLOAT(nanos) ((nanos) * 0.000001f) | 
|  |  | 
|  | #define PROFILE_DRAW_WIDTH 3 | 
|  | #define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2 | 
|  | #define PROFILE_DRAW_DP_PER_MS 7 | 
|  |  | 
|  | // Number of floats we want to display from FrameTimingData | 
|  | // If this is changed make sure to update the indexes below | 
|  | #define NUM_ELEMENTS 4 | 
|  |  | 
|  | #define RECORD_INDEX 0 | 
|  | #define PREPARE_INDEX 1 | 
|  | #define PLAYBACK_INDEX 2 | 
|  | #define SWAPBUFFERS_INDEX 3 | 
|  |  | 
|  | // Must be NUM_ELEMENTS in size | 
|  | static const SkColor ELEMENT_COLORS[] = { 0xcf3e66cc, 0xcf8f00ff, 0xcfdc3912, 0xcfe69800 }; | 
|  | static const SkColor CURRENT_FRAME_COLOR = 0xcf5faa4d; | 
|  | static const SkColor THRESHOLD_COLOR = 0xff5faa4d; | 
|  |  | 
|  | // We could get this from TimeLord and use the actual frame interval, but | 
|  | // this is good enough | 
|  | #define FRAME_THRESHOLD 16 | 
|  |  | 
|  | namespace android { | 
|  | namespace uirenderer { | 
|  |  | 
|  | static int dpToPx(int dp, float density) { | 
|  | return (int) (dp * density + 0.5f); | 
|  | } | 
|  |  | 
|  | DrawProfiler::DrawProfiler() | 
|  | : mType(kNone) | 
|  | , mDensity(0) | 
|  | , mData(NULL) | 
|  | , mDataSize(0) | 
|  | , mCurrentFrame(-1) | 
|  | , mPreviousTime(0) | 
|  | , mVerticalUnit(0) | 
|  | , mHorizontalUnit(0) | 
|  | , mThresholdStroke(0) { | 
|  | setDensity(1); | 
|  | } | 
|  |  | 
|  | DrawProfiler::~DrawProfiler() { | 
|  | destroyData(); | 
|  | } | 
|  |  | 
|  | void DrawProfiler::setDensity(float density) { | 
|  | if (CC_UNLIKELY(mDensity != density)) { | 
|  | mDensity = density; | 
|  | mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density); | 
|  | mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density); | 
|  | mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DrawProfiler::startFrame(nsecs_t recordDurationNanos) { | 
|  | RETURN_IF_DISABLED(); | 
|  | mData[mCurrentFrame].record = NANOS_TO_MILLIS_FLOAT(recordDurationNanos); | 
|  | mPreviousTime = systemTime(CLOCK_MONOTONIC); | 
|  | } | 
|  |  | 
|  | void DrawProfiler::markPlaybackStart() { | 
|  | RETURN_IF_DISABLED(); | 
|  | nsecs_t now = systemTime(CLOCK_MONOTONIC); | 
|  | mData[mCurrentFrame].prepare = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime); | 
|  | mPreviousTime = now; | 
|  | } | 
|  |  | 
|  | void DrawProfiler::markPlaybackEnd() { | 
|  | RETURN_IF_DISABLED(); | 
|  | nsecs_t now = systemTime(CLOCK_MONOTONIC); | 
|  | mData[mCurrentFrame].playback = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime); | 
|  | mPreviousTime = now; | 
|  | } | 
|  |  | 
|  | void DrawProfiler::finishFrame() { | 
|  | RETURN_IF_DISABLED(); | 
|  | nsecs_t now = systemTime(CLOCK_MONOTONIC); | 
|  | mData[mCurrentFrame].swapBuffers = NANOS_TO_MILLIS_FLOAT(now - mPreviousTime); | 
|  | mPreviousTime = now; | 
|  | mCurrentFrame = (mCurrentFrame + 1) % mDataSize; | 
|  | } | 
|  |  | 
|  | void DrawProfiler::unionDirty(Rect* dirty) { | 
|  | RETURN_IF_DISABLED(); | 
|  | // Not worth worrying about minimizing the dirty region for debugging, so just | 
|  | // dirty the entire viewport. | 
|  | if (dirty) { | 
|  | dirty->setEmpty(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DrawProfiler::draw(OpenGLRenderer* canvas) { | 
|  | if (CC_LIKELY(mType != kBars)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | prepareShapes(canvas->getViewportHeight()); | 
|  | drawGraph(canvas); | 
|  | drawCurrentFrame(canvas); | 
|  | drawThreshold(canvas); | 
|  | } | 
|  |  | 
|  | void DrawProfiler::createData() { | 
|  | if (mData) return; | 
|  |  | 
|  | mDataSize = property_get_int32(PROPERTY_PROFILE_MAXFRAMES, DEFAULT_MAX_FRAMES); | 
|  | if (mDataSize <= 0) mDataSize = 1; | 
|  | if (mDataSize > 4096) mDataSize = 4096; // Reasonable maximum | 
|  | mData = (FrameTimingData*) calloc(mDataSize, sizeof(FrameTimingData)); | 
|  | mRects = new float*[NUM_ELEMENTS]; | 
|  | for (int i = 0; i < NUM_ELEMENTS; i++) { | 
|  | // 4 floats per rect | 
|  | mRects[i] = (float*) calloc(mDataSize, 4 * sizeof(float)); | 
|  | } | 
|  | mCurrentFrame = 0; | 
|  | } | 
|  |  | 
|  | void DrawProfiler::destroyData() { | 
|  | delete mData; | 
|  | mData = NULL; | 
|  | } | 
|  |  | 
|  | void DrawProfiler::addRect(Rect& r, float data, float* shapeOutput) { | 
|  | r.top = r.bottom - (data * mVerticalUnit); | 
|  | shapeOutput[0] = r.left; | 
|  | shapeOutput[1] = r.top; | 
|  | shapeOutput[2] = r.right; | 
|  | shapeOutput[3] = r.bottom; | 
|  | r.bottom = r.top; | 
|  | } | 
|  |  | 
|  | void DrawProfiler::prepareShapes(const int baseline) { | 
|  | Rect r; | 
|  | r.right = mHorizontalUnit; | 
|  | for (int i = 0; i < mDataSize; i++) { | 
|  | const int shapeIndex = i * 4; | 
|  | r.bottom = baseline; | 
|  | addRect(r, mData[i].record, mRects[RECORD_INDEX] + shapeIndex); | 
|  | addRect(r, mData[i].prepare, mRects[PREPARE_INDEX] + shapeIndex); | 
|  | addRect(r, mData[i].playback, mRects[PLAYBACK_INDEX] + shapeIndex); | 
|  | addRect(r, mData[i].swapBuffers, mRects[SWAPBUFFERS_INDEX] + shapeIndex); | 
|  | r.translate(mHorizontalUnit, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DrawProfiler::drawGraph(OpenGLRenderer* canvas) { | 
|  | SkPaint paint; | 
|  | for (int i = 0; i < NUM_ELEMENTS; i++) { | 
|  | paint.setColor(ELEMENT_COLORS[i]); | 
|  | canvas->drawRects(mRects[i], mDataSize * 4, &paint); | 
|  | } | 
|  | } | 
|  |  | 
|  | void DrawProfiler::drawCurrentFrame(OpenGLRenderer* canvas) { | 
|  | // This draws a solid rect over the entirety of the current frame's shape | 
|  | // To do so we use the bottom of mRects[0] and the top of mRects[NUM_ELEMENTS-1] | 
|  | // which will therefore fully overlap the previously drawn rects | 
|  | SkPaint paint; | 
|  | paint.setColor(CURRENT_FRAME_COLOR); | 
|  | const int i = mCurrentFrame * 4; | 
|  | canvas->drawRect(mRects[0][i], mRects[NUM_ELEMENTS-1][i+1], mRects[0][i+2], | 
|  | mRects[0][i+3], &paint); | 
|  | } | 
|  |  | 
|  | void DrawProfiler::drawThreshold(OpenGLRenderer* canvas) { | 
|  | SkPaint paint; | 
|  | paint.setColor(THRESHOLD_COLOR); | 
|  | paint.setStrokeWidth(mThresholdStroke); | 
|  |  | 
|  | float pts[4]; | 
|  | pts[0] = 0.0f; | 
|  | pts[1] = pts[3] = canvas->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit); | 
|  | pts[2] = canvas->getViewportWidth(); | 
|  | canvas->drawLines(pts, 4, &paint); | 
|  | } | 
|  |  | 
|  | DrawProfiler::ProfileType DrawProfiler::loadRequestedProfileType() { | 
|  | ProfileType type = kNone; | 
|  | char buf[PROPERTY_VALUE_MAX] = {'\0',}; | 
|  | if (property_get(PROPERTY_PROFILE, buf, "") > 0) { | 
|  | if (!strcmp(buf, PROPERTY_PROFILE_VISUALIZE_BARS)) { | 
|  | type = kBars; | 
|  | } else if (!strcmp(buf, "true")) { | 
|  | type = kConsole; | 
|  | } | 
|  | } | 
|  | return type; | 
|  | } | 
|  |  | 
|  | bool DrawProfiler::loadSystemProperties() { | 
|  | ProfileType newType = loadRequestedProfileType(); | 
|  | if (newType != mType) { | 
|  | mType = newType; | 
|  | if (mType == kNone) { | 
|  | destroyData(); | 
|  | } else { | 
|  | createData(); | 
|  | } | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void DrawProfiler::dumpData(int fd) { | 
|  | RETURN_IF_DISABLED(); | 
|  |  | 
|  | // This method logs the last N frames (where N is <= mDataSize) since the | 
|  | // last call to dumpData(). In other words if there's a dumpData(), draw frame, | 
|  | // dumpData(), the last dumpData() should only log 1 frame. | 
|  |  | 
|  | const FrameTimingData emptyData = {0, 0, 0, 0}; | 
|  |  | 
|  | FILE *file = fdopen(fd, "a"); | 
|  | fprintf(file, "\n\tDraw\tPrepare\tProcess\tExecute\n"); | 
|  |  | 
|  | for (int frameOffset = 1; frameOffset <= mDataSize; frameOffset++) { | 
|  | int i = (mCurrentFrame + frameOffset) % mDataSize; | 
|  | if (!memcmp(mData + i, &emptyData, sizeof(FrameTimingData))) { | 
|  | continue; | 
|  | } | 
|  | fprintf(file, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n", | 
|  | mData[i].record, mData[i].prepare, mData[i].playback, mData[i].swapBuffers); | 
|  | } | 
|  | // reset the buffer | 
|  | memset(mData, 0, sizeof(FrameTimingData) * mDataSize); | 
|  | mCurrentFrame = 0; | 
|  |  | 
|  | fflush(file); | 
|  | } | 
|  |  | 
|  | } /* namespace uirenderer */ | 
|  | } /* namespace android */ |