该类的作用是提供一些gfxinfo的信息还开发者选项中的gpu呈现模式分析和显示gpu视图更新选项
该类提供的接口只有下面五个
typedef RingBuffer<FrameInfo, 120> FrameInfoSource;
class FrameInfoVisualizer {
public:
FrameInfoVisualizer(FrameInfoSource& source);
~FrameInfoVisualizer();
bool consumeProperties();
void setDensity(float density);
void unionDirty(SkRect* dirty);
void draw(ContentRenderer* renderer);
void dumpData(int fd);
该类在CanvasContext创建的时候构造
RingBuffer120> mFrames
CanvasContext::CanvasContext(RenderThread& thread, bool translucent,
RenderNode* rootRenderNode, IContextFactory* contextFactory)
: mRenderThread(thread)
, mEglManager(thread.eglManager())
, mOpaque(!translucent)
, mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord()))
, mJankTracker(thread.mainDisplayInfo())
, mProfiler(mFrames)
, mContentDrawBounds(0, 0, 0, 0) {
mRenderNodes.emplace_back(rootRenderNode);
mRenderThread.renderState().registerCanvasContext(this);
mProfiler.setDensity(mRenderThread.mainDisplayInfo().density);
}
mFrames数据源为能记录120帧的数组.
#define PROFILE_DRAW_WIDTH 3
#define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2
#define PROFILE_DRAW_DP_PER_MS 7
void FrameInfoVisualizer::setDensity(float density) {
if (CC_UNLIKELY(mDensity != density)) {
mDensity = density;
mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
}
}
既然是用于gfxinfo就从dumpData入手吧
CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread,
int fd, int dumpFlags) {
args->context->profiler().dumpData(args->fd);
if (args->dumpFlags & DumpFlags::FrameStats) {
args->context->dumpFrames(args->fd);
}
if (args->dumpFlags & DumpFlags::Reset) {
args->context->resetFrameStats();
}
if (args->dumpFlags & DumpFlags::JankStats) {
args->thread->jankTracker().dump(args->fd);
}
return nullptr;
}
args->context->profiler().dumpData(args->fd);用于本次要分析的函数,后边分别是用于reset frameinfo和dump jankTracker数据 上一篇文章有分析
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::IntendedVsync] <= mLastFrameLogged) {
continue;
}
mLastFrameLogged = mFrameSource[i][FrameInfoIndex::IntendedVsync];
fprintf(file, "\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));
}
fflush(file);
}
非常简单,只是输出了自上次打印后最近的一些帧的信息,其中包括四部分
1main thread 收到的vsync时间到处开始处理vsync时间
2 render thread收到同步的时间
3 render thread绘制的时间
4 swapbuffer的时间
另外FrameInfoVisualizer还有一个作用就是在调试模式的时候绘制出绘制的耗时,在开发者选项中打开
#define RETURN_IF_DISABLED() if (CC_LIKELY(mType == ProfileType::None && !mShowDirtyRegions)) return
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();
}
}
如上在gpu呈现模式上设置在屏幕上显示条形图后就会在屏幕上绘制呈现数据,另外还会加载dirtyRegion(变成整个window),这个主要是用于发现问题,so just dirty the entire viewport.
然后在绘制的时候就会多绘制这一部分(debug.hwui.profile=visual_bars),主要是通过void FrameInfoVisualizer::draw(ContentRenderer* renderer)方法完成的(这么做会不会影响后draw时常有待考察)
#define RETURN_IF_DISABLED() if (CC_LIKELY(mType == ProfileType::None && !mShowDirtyRegions)) return
void FrameInfoVisualizer::draw(ContentRenderer* 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);
}
}
没有开调试模式直接返回,另外还有mShowDirtyRegions表示调试模式的显示gpu视图更新有没有打开(debug.hwui.show_dirty_regions=true),也就是要重绘的区域,所以做好不要把这个变量和visual_bars同时打开.
如果显示gpu视图更新区域打开就把该区域绘制成0x7fff0000的颜色
后面就是处理visual_bars的逻辑
注意这里还没有进行swapbuffer,只是简单的生成了swapbuffers和drawcompeted的时间,所以这两个值也是没有参考意义的
initializeRects用于计算线宽的函数
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][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
continue;
}
float lineWidth = baseLineWidth;
float* rect;
int ri;
// Rects are LTRB
if (mFrameSource[fi].totalDuration() <= FRAME_THRESHOLD_NS) {
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;
}
}
这里就是计算表示每一帧矩形的x坐标位置,从又向作计算位置,一个矩形用四个float数值表示.其中left和right被计算出来,top和bottom都是baseline.
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},
}};
void FrameInfoVisualizer::drawGraph(ContentRenderer* 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);
}
}
drawGraph用于真正绘制表示每一帧的矩形,每一段代表的数据指标和颜色由Bar的数据结构表示.这里做以说明,有7列数据,从下向上拼接矩形.这里说明含义和颜色
Fast代表低于16ms的一帧,Janky代表高于16ms的一帧
1 main thrad vsync的时间到处理事件花费的时间
Fast Jank:
2 main thread处理事件花费的时间
3 main thread处理视图变化花费的时间
Fast: Jank:
4 main thread渲染花费的时间
Fast: Jank:
5render thread 同步main thread花费的时间
Fast: Jank:
6render thread绘制花费的时间
Fast: Jank:
7 swapbuffer花费的时间(虽然这个数据无效也解释下)
Fast: Jank:
下一篇将分析GpuMemoryTracker