Android RenderThread接收UI线程doFrame绘制指令,但放弃或跳过GPU合成,系统渲染系统发生空转

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(即没有执行 eglSwapBuffersvkQueueSubmit)。

出现这种"空转"现象,通常是由以下几种原因导致的。建议对照 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 帧附近,看看是否有 onDisplayChangedSurfaceDestroyedrelayoutWindow 等系统调用。

    • 看这 100ms 内,SurfaceFlinger 进程是否在做 setPowerModesetActiveConfig (切换刷新率) 或处理大事务。

原因三: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 内部:

  1. 看耗时

    • 如果这 10 个 DrawFrames 只有 0.x 毫秒 :绝对是原因一(脏区为空)原因二(Surface 无效)。RenderThread 发现没事干或干不了,直接退出了。

    • 如果这 10 个 DrawFrames 耗时依然有 几毫秒甚至十几毫秒,说明它确实在干活,但最后没提交。

  2. dequeueBuffereglSwapBuffers

    • 正常的 DrawFrames 内部会有 dequeueBuffer(向系统要一块画布)和 eglSwapBuffers / queueBuffer(画完交还给系统)。

    • 如果这 10 个帧里没有这两个方法,说明渲染在早期就被拦截了(无 Surface 或无脏区)。

    • 如果有 dequeueBuffer 但耗时极长,说明是 Buffer 耗尽(原因三)。

  3. SurfaceFlinger 进程在这 100ms 在干嘛

    • 如果 SurfaceFlinger 在这 100ms 内完全没有消费App的 Buffer(没有 latchBuffer),说明系统侧暂停了对App图库画面的接收(大概率在切换刷新率或做窗口动画同步)。

总结结论 : 这约 100ms 的"空转",是 RenderThread 的一种自我保护或被动挂起机制。最普遍的情况是:App触发了系统的刷新率切换(90Hz策略)或窗口过渡,导致App的 Surface 暂时不可用;或者是 UI 线程在跑一个没有实际视觉变化的空动画。100ms 后系统配置完成,677767 帧重新获得了有效的 Surface 和 Buffer,GPU 合成自然就恢复了。

推荐一个人工智能网站