Android RenderThread接收UI线程doFrame绘制指令,但放弃或跳过GPU合成,系统渲染系统发生空转
摘要:Android系统渲染过程中出现10帧约100ms的空转现象,主要原因是RenderThread接收UI线程指令但未执行GPU合成。可能原因包括:1)脏区为空(无像素变化,耗时极短);2)Surface失效(如刷新率切换或窗口过渡时被标记无效);3)Buffer耗尽(BLASTSync阻塞导致BufferQueue用尽)。通过分析Trace中DrawFrames耗时、dequeueBuffer/eglSwapBuffers调用及SurfaceFlinger活动可定位具体原因。该现象是系统在刷新率切换或窗口过渡时的自我保护机制,配置完成后即恢复正常。
在 trace 上看到:UI 主线程正常 doFrame → RenderThread 正常执行 DrawFrames → 但没有触发 GPU 合成(没有 Trace GPU completion fence,也没有 waiting for GPU completion)。
这说明一个核心事实:在这约 100ms 的 10 帧里,RenderThread 虽然接收了 UI 线程的绘制指令,但在最后关头"主动放弃或跳过"了将画面提交给 GPU(即没有执行 eglSwapBuffers 或 vkQueueSubmit)。
出现这种"空转"现象,通常是由以下几种原因导致的。建议对照 trace 里的细节进行排查:
原因一:脏区为空 (Dirty Region is Empty) ------ 最常见
UI 主线程确实在 doFrame,但 RenderThread 计算后发现屏幕上没有任何实际的像素变化。
-
发生场景 :App可能有一个"不可见"或"透明"的属性动画(如
ValueAnimator)还在持续运行,它每帧都在触发 UI 线程的doFrame。但 RenderThread 经过syncFrameState和 DisplayList 比较后,发现需要重绘的矩形区域(Damage Rect)是(0, 0, 0, 0)。 -
系统行为 :为了省电,RenderThread 会直接跳过 GPU 渲染和 Buffer 交换,直接结束这一帧。因此不会向 GPU 发送指令,也不会产生
GPU completion fence。 -
Trace 验证:
-
观察这 10 个没有 GPU fence 的
DrawFrames,它们的耗时是不是极短?(通常小于 1ms 或 2ms) 。如果是,大概率是因为脏区为空被直接return掉了。 -
展开
DrawFrames,看里面是否有eglSwapBuffers或者queueBuffer。如果没有,证实了被跳过。
-
原因二:Surface 暂时失效或被剥夺 (Surface Invalid / Disconnected)
IDisplayManagerCallback::onDisplayEvent(显示状态/刷新率变化)以及 90Hz 刷新率,这 100ms 很可能发生了显示模式切换或窗口过渡。
-
发生场景:在滑动大图时,系统触发了刷新率切换(例如 60Hz 切换到 90Hz,或者反向),或者正在进行 Activity 共享元素过渡(Shared Element Transition)。
-
系统行为 :在这约 100ms 内,WindowManager 可能会暂时将图库的
Surface标记为无效,或者底层的BufferQueue正在重新配置。此时 UI 线程依然在doFrame,RenderThread 也试图DrawFrames,但在准备向 Surface 申请 Buffer (dequeueBuffer) 时失败,或者发现没有合法的 Surface,于是 RenderThread 只能"丢弃"这一帧。 -
Trace 验证:
-
在 677611 帧附近,看看是否有
onDisplayChanged、SurfaceDestroyed、relayoutWindow等系统调用。 -
看这 100ms 内,
SurfaceFlinger进程是否在做setPowerMode、setActiveConfig(切换刷新率) 或处理大事务。
-
原因三:Buffer 耗尽与 BLAST Sync 阻塞 (BufferQueue Exhaustion)
在 Android 12 及以上版本,窗口过渡大量使用了 BLASTBufferQueue。
-
发生场景 :如果这 100ms 正好触发了某种页面切换动画,WindowManager 可能会启动一个
SyncTransaction。 -
系统行为 :SurfaceFlinger 暂停消费图库的 Buffer(等待动画同步),导致App的 3 个 Buffer 很快被用光。当 RenderThread 在这 100ms 内试图继续
DrawFrames时,由于拿不到新的 Buffer (dequeueBuffer失败或非阻塞返回),它无法构建 GPU 指令,只能跳过 GPU 提交。直到 100ms 后同步结束,SurfaceFlinger 释放了 Buffer,677767 帧才恢复正常。
如何在 Trace 上实锤是哪种原因?
请在 Perfetto/Systrace 中放大这 100ms 的空白期,重点看这 10 个异常的 DrawFrames 内部:
-
看耗时:
-
如果这 10 个
DrawFrames只有 0.x 毫秒 :绝对是原因一(脏区为空)原因二(Surface 无效)。RenderThread 发现没事干或干不了,直接退出了。 -
如果这 10 个
DrawFrames耗时依然有 几毫秒甚至十几毫秒,说明它确实在干活,但最后没提交。
-
-
找
dequeueBuffer和eglSwapBuffers:-
正常的
DrawFrames内部会有dequeueBuffer(向系统要一块画布)和eglSwapBuffers/queueBuffer(画完交还给系统)。 -
如果这 10 个帧里没有这两个方法,说明渲染在早期就被拦截了(无 Surface 或无脏区)。
-
如果有
dequeueBuffer但耗时极长,说明是 Buffer 耗尽(原因三)。
-
-
看
SurfaceFlinger进程在这 100ms 在干嘛:- 如果 SurfaceFlinger 在这 100ms 内完全没有消费App的 Buffer(没有
latchBuffer),说明系统侧暂停了对App图库画面的接收(大概率在切换刷新率或做窗口动画同步)。
- 如果 SurfaceFlinger 在这 100ms 内完全没有消费App的 Buffer(没有
总结结论 : 这约 100ms 的"空转",是 RenderThread 的一种自我保护或被动挂起机制。最普遍的情况是:App触发了系统的刷新率切换(90Hz策略)或窗口过渡,导致App的 Surface 暂时不可用;或者是 UI 线程在跑一个没有实际视觉变化的空动画。100ms 后系统配置完成,677767 帧重新获得了有效的 Surface 和 Buffer,GPU 合成自然就恢复了。