summaryrefslogtreecommitdiffstats
path: root/libs/hwui/DrawProfiler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/hwui/DrawProfiler.cpp')
-rw-r--r--libs/hwui/DrawProfiler.cpp261
1 files changed, 261 insertions, 0 deletions
diff --git a/libs/hwui/DrawProfiler.cpp b/libs/hwui/DrawProfiler.cpp
new file mode 100644
index 0000000..971a66e
--- /dev/null
+++ b/libs/hwui/DrawProfiler.cpp
@@ -0,0 +1,261 @@
+/*
+ * 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 */