C++程序  |  262行  |  7.88 KB

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