【性能优化】RenderThread各工作阶段梳理

【性能优化】知识点梳理-RenderThread各工作阶段逻辑梳理

一、为什么要梳理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的过程:

text 复制代码
VSYNC-sf 到达
↓
buffer 还没 ready(GPU 未完成)
↓
SF 放弃 latch
↓
这一帧不合成
↓
sleep ~16ms
↓
等下一帧 VSYNC

sf在发现buffer未ready时的决策问题,此时 SF 有两个选择:

  • 方案A:阻塞等 fence,等 GPU 完成 → 再合成
  • 方案B:直接跳过这一帧(实际采用),不等 → skip frame
    绝大多数情况下选择 B(非阻塞),选择是不卡 VSYNC 节奏

于是发生了截图的trace中看到的现象:

text 复制代码
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

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;
					}
     }
}
``
相关推荐
草莓熊Lotso2 小时前
MySQL 内置函数指南:日期、字符串、数学函数实战
android·java·linux·运维·数据库·c++·mysql
2401_895521343 小时前
mysql中general_log日志详解
android·数据库·mysql
zh_xuan3 小时前
Android compose 自定义主题
android·compose
一只程序熊3 小时前
uniapp 高德地图 打开选择地址报错,也没有展示出附近的位置
android·uni-app
贤泽3 小时前
Android View 触摸事件分发机制
android·aosp
zh_xuan3 小时前
Android compose 使用viewModel
android·compose
0pen13 小时前
我用 AI 写了一个 Android 群控工具,从零到可用只花了一个下午
android·人工智能
雾江流3 小时前
LSPosed 2.0.0 | 强大的安卓Root框架,支持XP模块
android·软件工程
小陈工11 小时前
FastAPI性能优化实战:从每秒100请求到1000的踩坑记录
python·性能优化·django·flask·numpy·pandas·fastapi