/* * 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 "FrameInfoVisualizer.h" #include "OpenGLRenderer.h" #include #include #define RETURN_IF_PROFILING_DISABLED() if (CC_LIKELY(mType == ProfileType::None)) return #define RETURN_IF_DISABLED() if (CC_LIKELY(mType == ProfileType::None && !mShowDirtyRegions)) return #define PROFILE_DRAW_WIDTH 3 #define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2 #define PROFILE_DRAW_DP_PER_MS 7 // Must be NUM_ELEMENTS in size static const SkColor CURRENT_FRAME_COLOR = 0xcf5faa4d; static const SkColor THRESHOLD_COLOR = 0xff5faa4d; static const SkColor BAR_ALPHA = 0xCF000000; // 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 { struct BarSegment { FrameInfoIndex start; FrameInfoIndex end; SkColor color; }; static const std::array Bar {{ { FrameInfoIndex::kIntendedVsync, FrameInfoIndex::kVsync, 0x00695C }, { FrameInfoIndex::kVsync, FrameInfoIndex::kHandleInputStart, 0x00796B }, { FrameInfoIndex::kHandleInputStart, FrameInfoIndex::kAnimationStart, 0x00897B }, { FrameInfoIndex::kAnimationStart, FrameInfoIndex::kPerformTraversalsStart, 0x009688 }, { FrameInfoIndex::kPerformTraversalsStart, FrameInfoIndex::kDrawStart, 0x26A69A}, { FrameInfoIndex::kDrawStart, FrameInfoIndex::kSyncStart, 0x2196F3}, { FrameInfoIndex::kSyncStart, FrameInfoIndex::kIssueDrawCommandsStart, 0x4FC3F7}, { FrameInfoIndex::kIssueDrawCommandsStart, FrameInfoIndex::kSwapBuffers, 0xF44336}, { FrameInfoIndex::kSwapBuffers, FrameInfoIndex::kFrameCompleted, 0xFF9800}, }}; static int dpToPx(int dp, float density) { return (int) (dp * density + 0.5f); } FrameInfoVisualizer::FrameInfoVisualizer(FrameInfoSource& source) : mFrameSource(source) { setDensity(1); } FrameInfoVisualizer::~FrameInfoVisualizer() { destroyData(); } void FrameInfoVisualizer::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 FrameInfoVisualizer::unionDirty(SkRect* dirty) { RETURN_IF_DISABLED(); // Not worth worrying about minimizing the dirty region for debugging, so just // dirty the entire viewport. if (dirty) { mDirtyRegion = *dirty; dirty->setEmpty(); } } void FrameInfoVisualizer::draw(OpenGLRenderer* canvas) { RETURN_IF_DISABLED(); if (mShowDirtyRegions) { mFlashToggle = !mFlashToggle; if (mFlashToggle) { SkPaint paint; paint.setColor(0x7fff0000); canvas->drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop, mDirtyRegion.fRight, mDirtyRegion.fBottom, &paint); } } if (mType == ProfileType::Bars) { initializeRects(canvas->getViewportHeight()); drawGraph(canvas); drawCurrentFrame(canvas->getViewportHeight(), canvas); drawThreshold(canvas); } } void FrameInfoVisualizer::createData() { if (mRects.get()) return; mRects.reset(new float[mFrameSource.capacity() * 4]); } void FrameInfoVisualizer::destroyData() { mRects.reset(nullptr); } void FrameInfoVisualizer::initializeRects(const int baseline) { float left = 0; // Set the bottom of all the shapes to the baseline for (size_t i = 0; i < (mFrameSource.capacity() * 4); i += 4) { // Rects are LTRB mRects[i + 0] = left; mRects[i + 1] = baseline; left += mHorizontalUnit; mRects[i + 2] = left; mRects[i + 3] = baseline; } } void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex end) { for (size_t fi = 0, ri = 0; fi < mFrameSource.size(); fi++, ri += 4) { // TODO: Skipped frames will leave little holes in the graph, but this // is better than bogus and freaky lines, so... if (mFrameSource[fi][FrameInfoIndex::kFlags] & FrameInfoFlags::kSkippedFrame) { continue; } // Set the bottom to the old top (build upwards) mRects[ri + 3] = mRects[ri + 1]; // Move the top up by the duration mRects[ri + 1] -= mVerticalUnit * duration(fi, start, end); } } void FrameInfoVisualizer::drawGraph(OpenGLRenderer* canvas) { SkPaint paint; for (size_t i = 0; i < Bar.size(); i++) { paint.setColor(Bar[i].color | BAR_ALPHA); nextBarSegment(Bar[i].start, Bar[i].end); canvas->drawRects(mRects.get(), (mFrameSource.size() - 1) * 4, &paint); } } void FrameInfoVisualizer::drawCurrentFrame(const int baseline, 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); size_t fi = mFrameSource.size() - 1; size_t ri = fi * 4; float top = baseline - (mVerticalUnit * duration(fi, FrameInfoIndex::kIntendedVsync, FrameInfoIndex::kIssueDrawCommandsStart)); canvas->drawRect(mRects[ri], top, mRects[ri + 2], baseline, &paint); } void FrameInfoVisualizer::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); } bool FrameInfoVisualizer::consumeProperties() { bool changed = false; ProfileType newType = Properties::getProfileType(); if (newType != mType) { mType = newType; if (mType == ProfileType::None) { destroyData(); } else { createData(); } changed = true; } bool showDirty = Properties::showDirtyRegions; if (showDirty != mShowDirtyRegions) { mShowDirtyRegions = showDirty; changed = true; } return changed; } void FrameInfoVisualizer::dumpData(int fd) { RETURN_IF_PROFILING_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. FILE *file = fdopen(fd, "a"); fprintf(file, "\n\tDraw\tPrepare\tProcess\tExecute\n"); for (size_t i = 0; i < mFrameSource.size(); i++) { if (mFrameSource[i][FrameInfoIndex::kIntendedVsync] <= mLastFrameLogged) { continue; } mLastFrameLogged = mFrameSource[i][FrameInfoIndex::kIntendedVsync]; fprintf(file, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n", duration(i, FrameInfoIndex::kIntendedVsync, FrameInfoIndex::kSyncStart), duration(i, FrameInfoIndex::kSyncStart, FrameInfoIndex::kIssueDrawCommandsStart), duration(i, FrameInfoIndex::kIssueDrawCommandsStart, FrameInfoIndex::kSwapBuffers), duration(i, FrameInfoIndex::kSwapBuffers, FrameInfoIndex::kFrameCompleted)); } fflush(file); } } /* namespace uirenderer */ } /* namespace android */