/* * 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 #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(SkRect* 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 */