| /* |
| * 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 "IProfileRenderer.h" |
| #include "utils/Color.h" |
| #include "utils/TimeUtils.h" |
| |
| #include <cutils/compiler.h> |
| #include <array> |
| |
| #define RETURN_IF_PROFILING_DISABLED() \ |
| if (CC_LIKELY(mType == ProfileType::None)) return |
| #define RETURN_IF_DISABLED() \ |
| if (CC_LIKELY(mType == ProfileType::None && !mShowDirtyRegions)) return |
| |
| namespace android { |
| namespace uirenderer { |
| |
| static constexpr auto PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2; |
| static constexpr auto PROFILE_DRAW_DP_PER_MS = 7; |
| |
| struct Threshold { |
| SkColor color; |
| float percentFrametime; |
| }; |
| |
| static constexpr std::array<Threshold, 3> THRESHOLDS{ |
| Threshold{.color = Color::Green_500, .percentFrametime = 0.8f}, |
| Threshold{.color = Color::Lime_500, .percentFrametime = 1.0f}, |
| Threshold{.color = Color::Red_500, .percentFrametime = 1.5f}, |
| }; |
| static constexpr SkColor BAR_FAST_MASK = 0x8FFFFFFF; |
| static constexpr SkColor BAR_JANKY_MASK = 0xDFFFFFFF; |
| |
| struct BarSegment { |
| FrameInfoIndex start; |
| FrameInfoIndex end; |
| SkColor color; |
| }; |
| |
| static const std::array<BarSegment, 7> Bar{{ |
| {FrameInfoIndex::IntendedVsync, FrameInfoIndex::HandleInputStart, Color::Teal_700}, |
| {FrameInfoIndex::HandleInputStart, FrameInfoIndex::PerformTraversalsStart, |
| Color::Green_700}, |
| {FrameInfoIndex::PerformTraversalsStart, FrameInfoIndex::DrawStart, Color::LightGreen_700}, |
| {FrameInfoIndex::DrawStart, FrameInfoIndex::SyncStart, Color::Blue_500}, |
| {FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart, Color::LightBlue_300}, |
| {FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers, Color::Red_500}, |
| {FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted, Color::Orange_500}, |
| }}; |
| |
| static int dpToPx(int dp, float density) { |
| return (int)(dp * density + 0.5f); |
| } |
| |
| FrameInfoVisualizer::FrameInfoVisualizer(FrameInfoSource& source, nsecs_t frameInterval) |
| : mFrameSource(source), mFrameInterval(frameInterval) { |
| setDensity(1); |
| consumeProperties(); |
| } |
| |
| FrameInfoVisualizer::~FrameInfoVisualizer() { |
| destroyData(); |
| } |
| |
| void FrameInfoVisualizer::setDensity(float density) { |
| if (CC_UNLIKELY(mDensity != density)) { |
| mDensity = density; |
| // We want the vertical units to scale height relative to a baseline 16ms. |
| // This keeps the threshold lines consistent across varying refresh rates |
| mVerticalUnit = static_cast<int>(dpToPx(PROFILE_DRAW_DP_PER_MS, density) * (float)16_ms / |
| (float)mFrameInterval); |
| 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(IProfileRenderer& renderer) { |
| RETURN_IF_DISABLED(); |
| |
| if (mShowDirtyRegions) { |
| mFlashToggle = !mFlashToggle; |
| if (mFlashToggle) { |
| SkPaint paint; |
| paint.setColor(0x7fff0000); |
| renderer.drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop, mDirtyRegion.fRight, |
| mDirtyRegion.fBottom, paint); |
| } |
| } |
| |
| if (mType == ProfileType::Bars) { |
| // Patch up the current frame to pretend we ended here. CanvasContext |
| // will overwrite these values with the real ones after we return. |
| // This is a bit nicer looking than the vague green bar, as we have |
| // valid data for almost all the stages and a very good idea of what |
| // the issue stage will look like, too |
| FrameInfo& info = mFrameSource.back(); |
| info.markSwapBuffers(); |
| info.markFrameCompleted(); |
| |
| initializeRects(renderer.getViewportHeight(), renderer.getViewportWidth()); |
| drawGraph(renderer); |
| drawThreshold(renderer); |
| } |
| } |
| |
| void FrameInfoVisualizer::createData() { |
| if (mFastRects.get()) return; |
| |
| mFastRects.reset(new float[mFrameSource.capacity() * 4]); |
| mJankyRects.reset(new float[mFrameSource.capacity() * 4]); |
| } |
| |
| void FrameInfoVisualizer::destroyData() { |
| mFastRects.reset(nullptr); |
| mJankyRects.reset(nullptr); |
| } |
| |
| void FrameInfoVisualizer::initializeRects(const int baseline, const int width) { |
| // Target the 95% mark for the current frame |
| float right = width * .95; |
| float baseLineWidth = right / mFrameSource.capacity(); |
| mNumFastRects = 0; |
| mNumJankyRects = 0; |
| int fast_i = 0, janky_i = 0; |
| // Set the bottom of all the shapes to the baseline |
| for (int fi = mFrameSource.size() - 1; fi >= 0; fi--) { |
| if (mFrameSource[fi].getSkippedFrameReason()) { |
| continue; |
| } |
| float lineWidth = baseLineWidth; |
| float* rect; |
| int ri; |
| // Rects are LTRB |
| if (mFrameSource[fi].totalDuration() <= mFrameInterval) { |
| rect = mFastRects.get(); |
| ri = fast_i; |
| fast_i += 4; |
| mNumFastRects++; |
| } else { |
| rect = mJankyRects.get(); |
| ri = janky_i; |
| janky_i += 4; |
| mNumJankyRects++; |
| lineWidth *= 2; |
| } |
| |
| rect[ri + 0] = right - lineWidth; |
| rect[ri + 1] = baseline; |
| rect[ri + 2] = right; |
| rect[ri + 3] = baseline; |
| right -= lineWidth; |
| } |
| } |
| |
| void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex end) { |
| int fast_i = (mNumFastRects - 1) * 4; |
| int janky_i = (mNumJankyRects - 1) * 4; |
| |
| for (size_t fi = 0; fi < mFrameSource.size(); fi++) { |
| if (mFrameSource[fi].getSkippedFrameReason()) { |
| continue; |
| } |
| |
| float* rect; |
| int ri; |
| // Rects are LTRB |
| if (mFrameSource[fi].totalDuration() <= mFrameInterval) { |
| rect = mFastRects.get(); |
| ri = fast_i; |
| fast_i -= 4; |
| } else { |
| rect = mJankyRects.get(); |
| ri = janky_i; |
| janky_i -= 4; |
| } |
| |
| // Set the bottom to the old top (build upwards) |
| rect[ri + 3] = rect[ri + 1]; |
| // Move the top up by the duration |
| rect[ri + 1] -= mVerticalUnit * durationMS(fi, start, end); |
| } |
| } |
| |
| void FrameInfoVisualizer::drawGraph(IProfileRenderer& renderer) { |
| SkPaint paint; |
| for (size_t i = 0; i < Bar.size(); i++) { |
| nextBarSegment(Bar[i].start, Bar[i].end); |
| paint.setColor(Bar[i].color & BAR_FAST_MASK); |
| renderer.drawRects(mFastRects.get(), mNumFastRects * 4, paint); |
| paint.setColor(Bar[i].color & BAR_JANKY_MASK); |
| renderer.drawRects(mJankyRects.get(), mNumJankyRects * 4, paint); |
| } |
| } |
| |
| void FrameInfoVisualizer::drawThreshold(IProfileRenderer& renderer) { |
| SkPaint paint; |
| for (auto& t : THRESHOLDS) { |
| paint.setColor(t.color); |
| float yLocation = renderer.getViewportHeight() - |
| (ns2ms(mFrameInterval) * t.percentFrametime * mVerticalUnit); |
| renderer.drawRect(0.0f, yLocation - mThresholdStroke / 2, renderer.getViewportWidth(), |
| yLocation + mThresholdStroke / 2, 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) { |
| #ifdef __ANDROID__ |
| 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. |
| |
| dprintf(fd, "\n\tDraw\tPrepare\tProcess\tExecute\n"); |
| |
| for (size_t i = 0; i < mFrameSource.size(); i++) { |
| if (mFrameSource[i][FrameInfoIndex::IntendedVsync] <= mLastFrameLogged) { |
| continue; |
| } |
| mLastFrameLogged = mFrameSource[i][FrameInfoIndex::IntendedVsync]; |
| dprintf(fd, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n", |
| durationMS(i, FrameInfoIndex::IntendedVsync, FrameInfoIndex::SyncStart), |
| durationMS(i, FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart), |
| durationMS(i, FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers), |
| durationMS(i, FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted)); |
| } |
| #endif |
| } |
| |
| } /* namespace uirenderer */ |
| } /* namespace android */ |