@TOC
一、为什么要梳理RenderThread各工作阶段的逻辑?
对于Android原声的UI绘制流程,主线程我们再熟悉不过了,因此分析丢帧问题也比较好定位原因,从而进行优化,比如:常见的卡binder调用、布局负责层级过于负责导致measure/layout/draw过程耗时,都能从trace明显看到。
1、RenderThread线程渲染耗时无明确指向性**
RenderThread一般出现耗时,要么系统层面逻辑引起,要么就是绘制工作过大,导致GPU渲染时间超时。
这两个问题对于应用来说,只能尝试优化后者,无非就是减少过度绘制层数、精简动画效果、精简一些模糊、渐变、阴影的效果。优化后代码,再进行测试验证,整个优化链路来说,并没有像主线程那么清晰有明确指向。比如:绘制超时了,具体是啥效果比较耗费渲染性能导致的?或者说具体是对应到应用层的哪个方法调用引起的?这都是没有明确指向的。
2、RenderThread渲染的逻辑代码,并不是在java层实现,而是转到了native层需和底层渲染接口打交道,阅读起来难度较大
这个其实也好理解,毕竟要和比较底层的渲染接口通信了,应用层的渲染指令,转成android图形渲染底层的指令,这些都是比较底层的实现了,用java来实现,性能完全不够看,甚至都没法直接访问到。
3、需要和sf进程关联分析,而应用层不了解sf工作原理
图片引用自:
Android Systrace 基础知识 - Vsync 解读
二、RenderThread工作的5个阶段
- 1、syncFrameState: 主线程和渲染线程同步数据
- 2、dequeueBuffer: 从SurfaceFlinger维护的BufferQueue队列中获取一个可用的buffer
- 3、flush commands:CPU侧构建GPU指令,把GPU指令提交到到gpu的指令队列,何时开始真正的GPU渲染,由其本身的调度逻辑决定,这里是异步逻辑,CPU不再等其完成指令的执行。
- 4、eglSwapBuffersWithDamageKHR: 执行buffer的交换
- 5、queueBuffer: 把已经提交过gpu指令的buffer,提交给BufferQueue中,这时候BufferQueue的buffer数量+1
三、sf合成buffer时如何保证GPU已经将其内容渲染完成?
RenderThread执行渲染的几步中flush commands是把GPU指令传递给GPU,通知GPU需要开始绘制buffer内容,注意这里GPU的pipeline真正何时开始,是由gpu自己的command队列决定的,并不是同步就一定开始的,可能会在传递指令后延迟一定时间开始。
但是这个步骤并不是同步阻塞式的等GPU绘制指令完成的,把指令传递给GPU后,就继续执行buffer交换(eglSwapBuffersWithDamageKHR)、重新把buffer推到sf的BufferQueue中了。所以从这个逻辑中,就能发现有个问题,就是sf在收到vsync-sf信号时,acquireBuffer操作需要做合并时,还需要等待这个buffer上的gpu绘制工作完成(注意:这里实际上android系统为了保持vsync-sf信号的节奏不被打乱,是才采用了跳帧的方式,也就是等下一个vsync-sf信号达到时执行合成再看buffer有没有ready再决定是否逻辑合成逻辑),如果遇到要做合并时还未绘制完成,就会在trace的应用进程的GPU tid轨道上的上产生“waiting for GPU completion id”这样的trace tag:
注意:这里GPU 15043这个线程,代表的并不是GPU的执行绘制线程,仅代表的是CPU等GPU绘制逻辑完成的过程。
sf合成时需要等GPU完成的过程->也就是sf的fence同步机制。
分析以上trace的过程:
VSYNC-sf 到达
↓
buffer 还没 ready(GPU 未完成)
↓
SF 放弃 latch
↓
这一帧不合成
↓
sleep ~16ms
↓
等下一帧 VSYNC
sf在发现buffer未ready时的决策问题,此时 SF 有两个选择:
- 方案A:阻塞等 fence,等 GPU 完成 → 再合成
- 方案B:直接跳过这一帧(实际采用),不等 → skip frame 绝大多数情况下选择 B(非阻塞),选择是不卡 VSYNC 节奏
于是发生了截图的trace中看到的现象:
VSYNC-sf 到达
↓
buffer 还没 ready(GPU 未完成)
↓
SF 放弃 latch
↓
这一帧不合成
↓
sleep ~16ms
↓
等下一帧 VSYNC
fence机制的理解
在 Android 中,App 提交(Queue)Buffer 给 SF 时,通常会携带一个 Acquire Fence。这个 Fence 代表了 GPU 对该 Buffer 的渲染进度。当 VSYNC-sf 到达时,SF 必须检查这个 Fence 是否已经 signal(即 GPU 是否已经真正把画面画完了)。
重新放大了看上面的trace截图,可以看到sf的commit 385807这次任务,从trace打印也能看到fence 是unsignaled状态!
对应的源码路径
源码捷选如下:frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheck(
const TransactionHandler::TransactionFlushState& flushState) {
const bool fenceSignaled = !acquireFenceAvailable ||
s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
if (!fenceSignaled) {
// check fence status
const bool allowLatchUnsignaled =
shouldLatchUnsignaled(s, transaction.states.size(),
flushState.firstTransaction) &&
layer->isSimpleBufferUpdate(s);
if (allowLatchUnsignaled) {
SFTRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
layer->name.c_str());
ready = TransactionReadiness::NotReadyUnsignaled;
} else {
ready = TransactionReadiness::NotReady;
auto& listener = s.bufferData->releaseBufferListener;
if (listener &&
(flushState.queueProcessTime - transaction.postTime) >
std::chrono::nanoseconds(4s).count()) {
mTransactionHandler
.onTransactionQueueStalled(transaction.id,
{.pid = layer->ownerPid.val(),
.layerId = layer->id,
.layerName = layer->name,
.bufferId = s.bufferData->getId(),
.frameNumber =
s.bufferData->frameNumber});
}
// trace中打印fence是非signaled就是从这里打印出来的!!
SFTRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
return TraverseBuffersReturnValues::STOP_TRAVERSAL;
}
}
}
``