Android VSync 是如何触发 Chromium 绘制的

1. VSync 信号的起始

Chromium 在 Android 平台是通过 ExternaBeginFrameSourceAndroid 监听 Android 平台的 VSync 信号,在收到 VSync 信号后,通过 BeginFrameObserver 通知信号订阅者

ExternaBeginFrameSourceAndroid 构造流程

rust 复制代码
browser_view_renderer.Constructor
|-->UpdateBeginFrameSource
  |-->begin_frame_source_->SetParentSource(RootBeginFrameSourceWebView::GetInstance())
  |-->RootBeginFrameSourceWebView.GetInstance [ begin_frame_source_webview ]
    |-->begin_frame_source_ [ ExternalBeginFrameSourceAndroid.Constructor ]
    |-->external_begin_frame_source_android.Constructor
      |-->Create Java Object ( ExternalBeginFrameSourceAndroid )

VSync 信号通知流程

rust 复制代码
external_begin_frame_source_android.OnVSync
|-->OnVSyncImpl
  |-->OnBeginFrame
     |
     | ExternalBeginFrameSourceAndroid extends BeginFrameSource
     |
  |-->begin_frame_source.OnBeginFrame
    |-->FilterAndIssueBeginFrame
      |-->BeginFrameObserver.OnBeginFrame
         |
         | BeginFrameSourceWebView::BeginFrameObserver extends BeginFrameObserver
         |
      |-->BeginFrameSourceWebView::BeginFrameObserver.OnBeginFrame [ begin_frame_source_webview ]
        |-->BeginFrameSourceWebView.SendBeginFrame
          |-->OnBeginFrame
            |--> 通知 SynchronousCompositoHost.OnBeginFrame
          |-->AfterBeginFrame
            |--> 阻塞 UI 线程,等待 Render BeginFrame Response

如代码中所示,VSync信号有两个重步骤:

  • 通知 SynchronousCompositorHost VSync 信号,最终告知到 Render 进程的 SynchronousCompositorProxy 刷新一帧内容
  • 在 1 调用完成后,阻塞 UI 线程,直至 Render 绘制帧准备完成后接触阻塞

2. SynchronousCompositorHost 通知 Render 生成一帧内容

接 1 中 OnBeginFrame 省略的部分,从 BeginFrameSourceWebView.OnBeginFrame 开始

rust 复制代码
BeginFrameSourceWebView.SendBeginFrame
  |-->OnBeginFrame
      |
      | BeginFrameSourceWebView extends ExternalBeginFrameSource
      |
  |-->ExternalBeginFrameSource.OnBeginFrame
    |-->FilterAndIssueBeginFrame
      |-->BeginFrameObserver.OnBeginFrame
          |
          | SynchronousCompositorHost extends BeginFrameObserver
          |
      |-->SynchronousCompositorHost.OnBeginFrame
        |-->SendBeginFrame
          |-->① SynchronousCompositorSyncCallBridge.WaitAfterVSyncOnUIThread
            |-->SynchronousCompositorHost.AddBeginFrameCompletionCallback ---> SynchronousCompositorSyncCallBridge::BeginFrameCompleteOnUIThread
              |-->SynchronousCompositorClient.AddBeginFrameCompletionCallback
                  |
                  | BrowserViewRenderer extends SynchronousCompositorClient
                  |
              |-->BrowserViewRenderer.AddBeginFrameCompletionCallback
                |-->BeginFrameSourceWebView.AddBeginFrameCompletionCallback [ begin_frame_source_webview ]
                  |-->after_begin_frame_callbacks_.emplace_back(std::move(callback)) [ 记录 callback ]
           |-->② blink::mojom::SynchronousCompositor.BeginFrame
             |--> mojo 通知 Render 生成一帧内容

如代码流程所示,SynchronousCompositorHost.OnBeginFrame 主要有以下2个步骤

① 向 BeginFrameSourceWebView 注册回调,并保存在 after_begin_frame_callbacks_ 队列中

② 通过 blink::mojom::SynchronousCompositor.BeginFrame 通知 Render 进程刷新一帧内容

3. UI 线程阻塞等待 Render 帧内容准备完成

回到 BeginFrameSourceWebView.SendBeginFrame,方法内部主要有 2 个调用

rust 复制代码
BeginFrameSourceWebView.SendBeginFrame
  |-->OnBeginFrame --> 通知 Render 生成一帧内容
  |-->AfterBeginFrame
    |-->RootBeginFrameSourceWebView.AfterBeginFrame [ begin_frame_source_webview ]
      |-->after_begin_frame_callbacks_.clear()

通过 [2] 我们知道 OnBeginFrame 会调用到 SynchronousCompositorHost.OnBeginFrame,经过一系列逻辑调用最终会在 after_begin_frame_callbacks_ 中注册回调方法 SynchronousCompositorSyncCallBridge::BeginFrameCompleteOnUIThread,而 after_begin_frame_callbacks_ 成员变量如下

c 复制代码
std::vector<base::ScopedClosureRunner> after_begin_frame_callbacks_;
~ScopedClosureRunner() {
  RunAndReset();
}

after_begin_frame_callbacks_.clear 会销毁列表中的成员,并触发成员的析构方法,在析构中会调用我们所设置的函数指针,因此 after_begin_frame_callbacks_.clear() 会触发我们之前添加的回调方法

lua 复制代码
SynchronousCompositorSyncCallBridge.BeginFrameCompleteOnUIThread
  |-->begin_frame_condition_.Wait()

begin_frame_condition_ 信号量阻塞等待,直到唤醒,达到阻塞 UI 线程的效果

4. Render 进程一帧内容准备好后,通知 Browser,解除 UI 线程阻塞

回到 SynchronousCompositorHost.SendBeginFrame 方法,他会通知到 Render 刷新一帧内容

lua 复制代码
SynchronousCompositorHost.SendBeginFrame
  |-->compositor->BeginFrame ---------------------------------------|
                                                                    |
~~~~~~~~~~~~~~~~~~~~~~~~~Renderer~~~~~~~~~~~~~~~~~~~~~~~~~          | synchronous_compositor.mojom
                                                                    |
SynchronousCompositorProxy.BeginFrame <-----------------------------|

Render 收到 Browser BeginFrame 的调用

rust 复制代码
SynchronousCompositorProxy.BeginFrame
  |-->① layer_tree_frame_sink_->BeginFrame
    |-->LayerTreeFrameSink.BeginFrame
        |
        | extends LayerTreeFrameSink
        |
    |-->SynchronousLayerTreeFrameSink.BeginFrame
  |-->② SendBeginFrameResponse

SynchronousCompositorProxy.BeginFrame 2 个比较关键的步骤

① 刷新页面

② 页面刷新完成后,通知到Browser

刷新页面的具体逻辑稍后再分析,这里先看 SendBeginFrameResponse 将 VSync 整体的刷新流程对起来

SynchronousCompositorProxy 页面刷新完成后,通过 BeginFrameResponse 通知 Browser 进程内容帧已刷新完成

lua 复制代码
SynchronousCompositorProxy.SendBeginFrameResponse
  |-->control_host_->BeginFrameResponse ------------------------------------------------------|
                                                                                              |
~~~~~~~~~~~~~~~~~~~~~Browser~~~~~~~~~~~~~~~~~~~~~~                                            |
                                                                                              |
SynchronousCompositorControlHost.BeginFrameResponse [ synchronous_compositor_host.cc ] <------|

SynchronousCompositorControlHost.BeginFrameResponse 收到 Render 刷新完成的信号,经过一系列的调用,最终会唤醒信号量 begin_frame_condition_

ini 复制代码
SynchronousCompositorControlHost.BeginFrameResponse [ synchronous_compositor_host.cc ]
  |-->SynchronousCompositorSyncCallBridge.BeginFrameResponseOnIOThread
    |-->begin_frame_condition_.Signal();

5. 唤醒 UI 线程阻塞的信号量,并触发 View 绘制流程

begin_frame_condition_.Singal 唤醒 begin_frame_condition.Wait 的等待逻辑

lua 复制代码
SynchronousCompositorSyncCallBridge.BeginFrameCompleteOnUIThread
  |-->begin_frame_condition_.Wait [ 唤醒 ]
  |-->SynchronousCompositorHost.UpdateState
    |-->client_.PostInvalidate [ SynchronousCompositorClient ]
        |
        | extends SynchronousCompositorClient
        |
    |-->BrowserViewRenderer.PostInvalidate
      |-->client_.PostInvalidate [ BrowserViewRendererClient ]
          |
          | extends BrowserViewRendererClient
          |
      |-->aw_contents.PostInvalidate
        |-->AwContents.PostInvalidate

经过一系列的调用,最终通知到 AwContents.PostInvalidate,该方法会调用 WebView.nvalidate 方法,触发 WebView.onDraw 回调进行内容的绘制

6. WebView.onDraw 绘制 Render 已刷新好的内容

lua 复制代码
WebView.onDraw
  |-->AwContents.onDraw
    |-->aw_contents.onDraw
      |-->browser_view_renderer.OnDrawHardware
        |-->SynchronousCompositorHost.DemandDrawHwAsync
          |-->DemandDrawHw
            |-->GetSynchronousCompositor()->DemandDrawHw -------------|
                                                                      |
~~~~~~~~~~~~~~~~~~~~Renderer~~~~~~~~~~~~~~~~~~~~                      | synchronous_compositor.mojom
                                                                      |
SynchronousCompositorProxy.DemandDrawHw <-----------------------------|

SynchronousCompositorHost 通过 DemandDrawHw mojo调用从 Render 进程取回刷新好的内容帧

rust 复制代码
SynchronousCompositorProxy.DemandDrawHw
  |-->hardware_draw_reply_ = std::move(callback) [ 同步调用,通过此回调完成mojo方法的值返回 ]
  |-->SynchronousLayerTreeFrameSink.DemandDrawHw
    |-->InvokeComposite
      |-->client_.OnDraw [ LayerTreeFrameSinkClient ]
          |
          | extends LayerTreeFrameSinkClient
          |
      |-->LayerTreeHostImpl.OnDraw
        |-->client_.OnDrawForLayerTreeFrameSink [ LayerTreeHostImplClient ]
            |
            | extends LayerTreeHostImplClient
            |
        |-->ProxyImpl.OnDrawForLayerTreeFrameSink
          |-->scheduler.OnDrawForLayerTreeFrameSink
            |-->OnBeginImplFrameDeadline
              |-->ProcessScheduledActions
                |-->DrawIfPossible
                  |-->client_.ScheduledActionDrawIfPossible [ SchedulerClient ]
                      |
                      | extends SchedulerClient
                      |
                  |-->ProxyImpl.ScheduledActionDrawIfPossible
                    |-->DrawInternal
                      |-->LayerTreeHostImpl.DrawLayers
                        |-->layer_tree_frame_sink_.SubmitCompositorFrame [ LayerTreeFrameSink ]
                            |
                            | extends LayerTreeFrameSink
                            |
                        |-->SynchronousLayerTreeFrameSink.SubmitCompositorFrame
                          |-->sync_client_.SubmitCompositorFrame [ SynchronousLayerTreeFrameSinkClient ]
                              |
                              | extends SynchronousLayerTreeFrameSinkClient
                              |
                          |-->SynchronousCompositorProxy.SubmitCompositorFrame
                            |-->std::move(hardware_draw_reply_).Run ---> mojo方法值返回 ----------------| 
                                                                                                       |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Browser~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~      |
                                                                                                       |
SynchronousCompositorHost.DemandDrawHw                                                                 |
    |-->GetSynchronousCompositor()->DemandDrawHw <-------------------sync mojo call--------------------|
    |-->UpdateState [ 参考第5步的方法调用分解 ]

Render 将帧内容通过同步mojo方法调用返回给Browser进程,Browser进程完成同步mojo调用后,通过 UpdateState方法再次触发AwContents刷新View,触发WebView的绘制流程

7. [2]补充 SynchronousCompositorProxy.BeginFrame 刷新内容

在第2步中,SynchronousCompositorHost 有 2 个主要方法,以上步骤我们详细跟踪了 VSync → UI 阻塞 → UI 阻塞 → 刷新View 的调用流程,这里补充下 SynchronousCompositorHost 中通过 BeginFrame 通知 Render 刷新内容的过程

rust 复制代码
SynchronousCompositorProxy.BeginFrame
  |-->SynchronousLayerTreeFrameSink.BeginFrame
    |-->ExternalBeginFrameSource.OnBeginFrame [ begin_frame_source.cc ]
      |-->FilterAndIssueBeginFrame
        |-->BeginFrameObserver.OnBeginFrame
            |
            | extends BeginFrameObserver
            |
        |-->BeginFrameObserverBase.OnBeginFrame [ begin_frame_source.cc ]
          |-->OnBeginFrameDerivedImpl
              |
              | extends BeginFrameObserverBase
              |
          |-->Scheduler.OnBeginFrameDerivedImpl
            |-->BeginImplFrameSynchronous
              |-->BeginImplFrame
                |-->ProcessScheduledActions
                  |-->client_->ScheduledActionInvalidateLayerTreeFrameSink [ SchedulerClient ]
                      |
                      | extends SchedulerClient
                      |
                  |-->ProxyImpl.ScheduledActionInvalidateLayerTreeFrameSink
                    |-->LayerTreeHostImpl.InvalidateLayerTreeFrameSink
                      |-->layer_tree_frame_sink()->Invalidate [ LayerTreeFrameSink ]
                          |
                          | extends LayerTreeFrameSink
                          |
                      |-->SynchronousLayerTreeFrameSink.Invalidate
                        |-->sync_client_.Invalidate [ SynchronousLayerTreeFrameSinkClient ]
                            |
                            | extends SynchronousLayerTreeFrameSinkClient
                            |
                        |-->SynchronousCompositorProxy.Invalidate
                          |-->SendAsyncRendererStateIfNeeded
                            |-->host_.UpdateStae -----------------------------------------|
                                                                                          |
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Browser~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~               |
                                                                                          |
SynchronousCompositorHost.UpdateState <---------------------------------------------------|

Render 刷新内容后通过UpdateState通知到Browser,触发WebView绘制流程以便绘制新的内容

关系概览图

相关推荐
风清扬_jd36 分钟前
Chromium 硬件加速开关c++
java·前端·c++
谢尔登1 小时前
【React】事件机制
前端·javascript·react.js
2401_857622662 小时前
SpringBoot精华:打造高效美容院管理系统
java·前端·spring boot
etsuyou2 小时前
Koa学习
服务器·前端·学习
Easonmax2 小时前
【CSS3】css开篇基础(1)
前端·css
粥里有勺糖3 小时前
视野修炼-技术周刊第104期 | 下一代 JavaScript 工具链
前端·javascript·github
大鱼前端3 小时前
未来前端发展方向:深度探索与技术前瞻
前端
昨天;明天。今天。3 小时前
案例-博客页面简单实现
前端·javascript·css
天上掉下来个程小白3 小时前
请求响应-08.响应-案例
java·服务器·前端·springboot
前端络绎3 小时前
初识 DT-SDK:基于 Cesium 的二三维一体 WebGis 框架
前端