e4267ea4f2
* Now with more native! * Less matrix math thanks to bulk-property-update support! * Zero JNI on the View.damageInParent() path! * Fully aware of RT-driven animators! * Likely full of new and exciting bugs! * But it also fixes at least 1 existing invalidate bug! Change-Id: Ie0773f85a60850ff2668370c58defef2e8aa079f
262 lines
7.9 KiB
C++
262 lines
7.9 KiB
C++
/*
|
|
* 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 */
|