- Trace图
- FrameTimeline Item
- VSync
- Fence
- work duration & ready duration
- FrameTimeline-App侧
- FrameTimeline-SF侧
- TimerDispatch
- 参考
本文搞懂Android卡顿检测之FrameTimeline,对很多概念进行了介绍,看懂trace图,从而可以熟悉Jank检测方法及Jank类别,进一步评估FrameTimeline的准确性及卡顿检测的局限性。(本文基于Android 13,针对硬件绘制模式)
Trace图
本文针对硬件绘制,软件绘制、SurfaceView的App侧没有VSyncId,绘制完直接提交给SurfaceFlinger合成,FrameTimeline目前对它们还是无能为力的,官方未来会支持。
从Trace图上找到FrameTimeline相关踪迹,App侧和Sf侧,同时包含两个时间,Expected Time和Actual Time。
我从自用的OPPO系手机上抓了一份滑动美团的trace,并使用perfetto-ui打开。
App端的Expected Time和Actual Time概览:
Sf端的Expected Time和Actual Time概览:
同时可以发现,App-18961627 与 sf-18961630 产生了一个关联,这是因为,sf-18961630 是一个DisplayFrame,它包含了App-18961627 这个SurfaceFrame。表示 App-18961627 这个Surface在 VSync=sf-18961630 时被推到屏幕显示,推到屏幕显示出来这件事也叫presented。
FrameTimeline Item
先明确一个Timeline的表示:
struct TimelineItem {
TimelineItem(const nsecs_t startTime = 0, const nsecs_t endTime = 0,
const nsecs_t presentTime = 0)
: startTime(startTime), endTime(endTime), presentTime(presentTime) {}
nsecs_t startTime;
nsecs_t endTime;
nsecs_t presentTime;
bool operator==(const TimelineItem& other) const {
return startTime == other.startTime && endTime == other.endTime &&
presentTime == other.presentTime;
}
bool operator!=(const TimelineItem& other) const { return !(*this == other); }
};
startTime :表示绘制/合成的开始时间点,对于App侧,表示doFrame的开始,对于sf侧,表示setTransactionState被调用,即sf被唤醒的时间;
endTime :表示绘制/合成的完成时间点,对于App侧,表示GPU完成了渲染,即acquireFence的signalTime,对于sf侧,表示commit完成的时间;
presentTime :表示显示的时间点,表示一帧被显示在屏幕上的时间,即presentFence的signalTime,这个就不区分App侧和sf侧了,毕竟Surface也是需要由sf合成并显示的。
VSync
VSync,都知道是屏幕的垂直同步信号。我们思考更深一点,VSync到来,意味着一帧图像将显示在屏幕上。这种与刷新直接相关的VSync,其实是硬件VSync(HW VSync)。
我们还知道,Android sf有一个模拟的软件VSync,它来自于对硬件VSync的预测,使用6个硬件VSync的采样点,就能推算出斜率和截距。当给定一个时间点time point的时候,可以估算出该时间之后最近的VSync,该VSync落在斜率和截距所代表的直线上,并将其作为软件VSync,在绝对时间上,它几乎等于对应硬件VSync。
所以,VSync有两个实际含义:
- 屏幕显示一帧图像;(HW VSync)
- 软件VSync驱动app去绘制,驱动sf去合成。
但软件VSync不是立即驱动app和sf工作,而是存在相位差,在app侧和sf侧分别偏移app phase和SF phase的时间,这两个phase可以通过dumpsys SurfaceFlinger直接看到。
该帧预期显示的VSync时间:在FrameTimeline中,我们需要找到一帧未来可能在哪个VSync上显示到屏幕上。在拥有三缓冲的Android显示系统中,一帧很可能在3个VSync周期后显示到屏幕上。
- 第1个VSync:CPU执行doFrame
- 第2个VSync:GPU执行渲染
- 第3个VSync:sf合成并送显
- 第4个VSync触发:正好可以在屏幕上显示
所以,该帧预期显示的VSync时间 = now + 3个VSync的周期。
对于120pfs/8ms一帧的刷新率,该帧预期显示的VSync时间 = now + 8x3 = now + 24ms。
对于60pfs/16.7ms一帧的刷新率,该帧预期显示的VSync时间 = now + 16.7x3 = now + 50ms。
Android模拟的软件VSync,其实就是这个“该帧预期显示的VSync时间”,也就是距今近似等于24ms和50ms之后的时间点。
(这一点非常重要,十几年的Android老牛也未必知道这个)Trace图上显示的,或者说驱动app绘制和sf合成的VSync-app/VSync-sf,其实是软件VSync的调整时间,也就是SurfaceFlinger代码中提到的wakeupTime。
Fence
Fence是同步用的,用于保护某些绘制/合成过程,分为:
- acquireFence:当对Surface queueBuffer到BufferQueue中后,只有signal后才能被消费端acquire,此时表示GPU已经完成渲染;
- releaseFence:dequeueBuffer后,singal触发,表示合成工作已经完成,可以被App拿去绘制了;
- presentFence:commit到Display后,signal触发,表示该帧已经被显示到屏幕上,表示一个Buffer最终显示出来了。
work duration & ready duration
FrameTimeline的时间是否满足预期,duration是一个基础,它来源于offset。
对于App侧,完成一帧的绘制需要work duration这么久,将绘制好的一帧的显示出来需要 ready duration这么久。work duration也代表了App绘制一帧的时长,是从doFrame开始到GPU完成绘制的时长(使用硬件加速的情况下)。ready duration表示sf将这一帧合成并显示到屏幕上的时长,是从sf被唤醒到presentFence signalTime的时长。
对于sf侧,就不存在绘制的时长了,主要是合成时长,直接使用work duration表示。表示sf将所有帧合成并显示到屏幕上的时长,是从sf被唤醒到presentFence signalTime的时长。sf侧的ready duration始终等于0。
在 adb shell dumpsys SurfaceFlinger
命令显示的dump信息中:
60.00fps: 3d01:39:51.482
app phase: 2400001 ns SF phase: -10933333 ns
app duration: 20000000 ns SF duration: 27600000 ns
early app phase: 2400001 ns early SF phase: -10933333 ns
early app duration: 20000000 ns early SF duration: 27600000 ns
GL early app phase: 2400001 ns GL early SF phase: -10933333 ns
GL early app duration: 20000000 ns GL early SF duration: 27600000 ns
HWC min duration: 17000000 ns
present offset: 0 ns VSYNC period: 16666667 ns
app侧的work duration == app duration。
app侧的ready duration == sf duration == sf侧的work duration。
sf中,根据offset计算duration:
namespace {
std::chrono::nanoseconds sfOffsetToDuration(nsecs_t sfOffset, nsecs_t vsyncDuration) {
return std::chrono::nanoseconds(vsyncDuration - sfOffset);
}
std::chrono::nanoseconds appOffsetToDuration(nsecs_t appOffset, nsecs_t sfOffset,
nsecs_t vsyncDuration) {
auto duration = vsyncDuration + (sfOffset - appOffset);
if (duration < vsyncDuration) {
duration += vsyncDuration;
}
return std::chrono::nanoseconds(duration);
}
} // namespace
盗了一张图,大意是对的,但在Android13上,还不完全对:
- offset不是与HW_VSYNC的相对时间,而是与软件VSYNC的相对时间;
- sf offset是一个负数,在VSYNC的前面,表示比VSYNC早。
再看上面的代码,sf duration 是 vsyncDuration - sfOffset
,这个值比VSYNC周期大。
同理,app duration = vsyncDuration - appOffset
,但还不行,因为sf偏移了,app侧偏要移少一点,才能让一帧图像正好在VSYNC到来前完成绘制,这样效率是最高的。
所以,app duration 变为 vsyncDuration - appOffset + sfOffset = vsyncDuration + (sfOffset - appOffset)
。如果小于一个vsyncDuration,多加一个,这样才能满足3个VSYNC完成绘制的三缓冲机制,才是最佳的绘制效率。
FrameTimeline-App侧
重点来了,App侧的Expected Time和Actual Time是怎么来的?
首先,它们是 FrameTimeline Item 开始时间和完成时间的组合,Actual Time 表示实际时间,Expected Time表示预测的时间。
对于上图中的token=18961627来说,Actual Timeline = 10.2ms,Expected Timeline = 20ms。
Actual Timeline
startTime等于doFrame开始的时间,下面是整个调用过程:
Choreographer.java
doFrame() --> do the registered Callback
ViewRootImpl.java
performDraw() --> draw()
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
CanvasContext::draw() of RenderThread in libhwui.so
native_window_set_frame_timeline_info()
Surface::dispatchSetFrameTimelineInfo()
BLASTBufferQueue.cpp --> BBQSurface --> setFrameTimelineInfo
BLASTBufferQueue::acquireNextBufferLocked()
t->setFrameTimelineInfo(mNextFrameTimelineInfoQueue.front()); // 设置到Transaction中
t->setApplyToken(mApplyToken).apply(false, true); //设置到SurfaceFlinger
SurfaceComposerClient.cpp --> SurfaceComposerClient::Transaction::apply()
set mFrameTimelineInfo to sf
SurfaceFlinger::commit()
setTransactionState()
// set the start time to SurfaceFrame in FrameTimeline
void SurfaceFrame::setActualStartTime(nsecs_t actualStartTime) {
std::scoped_lock lock(mMutex);
mActuals.startTime = actualStartTime;
}
endTime 是acquireFence 的SignalTime,表示GPU已经完成渲染。
Layer.cpp
1258 surfaceFrame->setAcquireFenceTime(acquireFenceTime); in addSurfaceFramePresentedForBuffer() // HWUI
1273 surfaceFrame->setAcquireFenceTime(postTime); in createSurfaceFrameForTransaction() // 软件绘制
Expected Timeline
关于“该帧预期显示的VSync时间”,请翻前文
endTime = 该帧预期显示的VSync时间 - ready duration.
startTime = 该帧预期显示的VSync时间 - work duration - ready duration.
预测时间生成:
预测时间分发:
FrameTimeline-SF侧
对于上图中的token=18961630来说,Actual Timeline = 27.3ms,Expected Timeline = 27.6ms。
Actual Timeline
startTime = commit(),FrameTimeline::DisplayFrame::onSfWakeUp,表示sf被客户端唤醒的时间,及开始合成的时间;
presentTime = presentFence 的signal时间;
Expected Timeline
跟踪代码:
831 void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime, Fps refreshRate) {
832 ATRACE_CALL();
833 std::scoped_lock lock(mMutex);
834 mCurrentDisplayFrame->onSfWakeUp(token, refreshRate,
835 mTokenManager.getPredictionsForToken(token), wakeUpTime);
836 }
{targetWakeupTime, readyTime, vsyncTime} 对应 {startTime, endTime, presentTime}
参考App侧的预测时间生成方式,由于sf侧readyDuration==0,所以:
- startTime = vsyncTime - workDuration
- endTime = vsyncTime
TimerDispatch
补充一下,这个对理解VSYNC-App/sf的生成会有很大的帮助:
VSync-app发生时设置一个VSync-sf的alarm:
同理,VSync-sf发生时设置一个VSync-app的alarm:
参考
http://www.aospxref.com/android-13.0.0_r3/xref/frameworks/native/services/surfaceflinger/