How Android Webview DrawOnRT(M96版本)

背景

Android Webview是Android 中View的一个子类, 同样在UI线程接收input, 在RT(Render Thread) 渲染.以下是Android Webview在Android渲染流水线集成的图例:

RC: Renderer Compositor, 是chromium中Renderer模块的合成线程

  1. input: 传递事件给Renderer处理

  2. animate: 通知Renderer开始新的一帧(raf的回调等)

  3. Renderer返回是否需要draw

  4. onDraw: 如果第三步返回需要draw, 会在UI线程回调onDraw, 然后通知Renderer 生产新的Frame

  5. kModeDraw: 在RT渲染 Renderer所生成的Frame

流程

在上面的第五步, 即在RT渲染 Renderer所生成的Frame, 就会把Webview的内容与其它Android View(如有)的内容绘制在一起,然后上屏.

其中Android Webview在RT渲染Frame涉及的chromium中的模块:

  • interface: 提供接口由Android调用来渲染Webview

  • android_webview: chromium中适配android webview渲染的模块

  • viz: chromium中的通用渲染模块

  • sdk: 渲染时用到的第三方库

调用流程

AwDrawFnImpl

这个类是Webview渲染Frame的实现的一层很薄的封装, 基本就是调用RenderThreadManager来完成渲染. 通过C Wrapper 暴露给Android在渲染的时候调用.

RenderThreadManager(RTM)

// This class is used to pass data between UI thread and RenderThread.

RTM在传递Frame时的作用:

在UI线程的onDraw中通知Renderer绘制网页内容, 然后给RTM设置一个Frame Future. RT的DrawOnRT拿到这个Frame Future后, 会阻塞等待Frame Future 被resolved: Renderer 提交绘制好的Frame.

Renderer提交的Frame并不是一幅bitmap或者texture, 而是一系列的RenderPass, RenderPass里面又有不同的DrawQuad, DrawQuad才具体指定绘制方式, 例如光栅化后的纹理,或者一块单纯的颜色区域等.

RenderPass之间通过特殊的DrawQuad(RenderPassDrawQuad)串联起来, RenderPassDrawQuad表示某个区域的内容由对应的RenderPass来填充,除了最后的root RenderPass, 每个RenderPass都应该被别的RenderPass的RenderPassDrawQuad来引用.而root RenderPass表示最终的页面.

一个示例:

这其实对应了cc::RenderSurface的树形关系, 而DrawQuad则表示cc::Layer具体的绘制(光栅化后的纹理,eg.)

SubmitFrame

HardwareRendererViz拿到Frame后不是直接Draw,还要先SubmitFrame,即把Frame提交到它对应的Surface:

In Chromium, we can use surfaces for many of the embedding cases we have today:

  • embedding a blink-rendered tab in the browser's UI

  • embedding a plugin or video within a page

  • embedding an iframe rendered from one process into an iframe rendered in a

different process

一个web页面对应一个Surface, Surface会保存绘制web页面所产生的Frame.

Android Webview在绘制的时候,还会加入一个Root Surface, 负责将web页面的Surface正确渲染(clip, transform等):

web页面对应的Surface因此也称为Child Surface, 如果这个web页面还有video或者OOPIF, 那么它也会有Child Surface:

这也是需要先SubmitFrame的原因, 因为还需要等待其他Surface也SubmitFrame,然后再聚合Child Surfaces的Frame, 这个聚合的Frame才表示完整的页面.

每个Surface都有唯一的id, 并且由SurfaceManager负责创建与管理,SurfaceManager, Surface, Frame之间的关系:

每个Surface最多会有两个Frame, 一个是pending,一个是active.

Aggregate Frame

如上所述, Surface之间每个Surface都有自己的Frame,它们之间的关系类似于下图所示:

为了简化后续绘制的流程, 会先由SurfaceAggregator 把不同Surface的Frame聚合成一个AggregatedFrame后再由Display 来绘制.

Surface之间的关系并非保存在Surface的成员变量中, 而也是由Frame中的SurfaceDrawQuad来引用, 每个SurfaceDrawQuad使用SurfaceId来指定一个Surface, 类似于:

简单来说, Aggregate Frame就是要把SurfaceDrawQuad去掉, 把两个CompositorFrame合在一起

流程:

流程从root surface开始, 先经历一遍prewalk的递归, 把root surface涉及到的所有surface都处理成ResolvedFrame.然后第二次递归的Copyasses , 把ResolvedFrame都聚合成一个AggregatedFrame:

  • GetResolvedFrame

    递归遍历到的每个Surface的Frame都会被resolved, resolve主要做两件事

    1. 把Frame中的RenderPass的id(原来是child surface 内部的)映射成全局统一的

    2. 把Frame中使用到的Resource(有好几种类型的resource, 常见的是tile 纹理)导入到DisplayResourceProvider, 并把Resource的id替换为DisplayResourceProvider的local_id, 并删除DisplayResourceProvider中不再使用的Resource.

  • PrewalkSurface

    得到Surface的ResolvedFrame后就传入PrewalkSurface处理, PrewalkSurface先调用PrewalkRenderPass来递归找到child surface进行处理; 然后再递归处理那些非draw但是有copy request的child surface.

  • PrewalkRenderPass

    因为root renderpass会引用其它的renderpass, 所以在PrewalkSurface中只需要给PrewalkRenderPass传入ResolvedFrame的root renderpass.

    PrewalkRenderPass遍历当前传入的renderpass的DrawQuad:

    • 如果是SurfaceDrawQuad, 则先GetResolvedFrame, 然后递归PrewalkSurface处理Surface

    • 如果是RenderPassDrawQuad, 则递归PrewalkRenderPass遍历其中的DrawQuad

    举个例子, 经过PrewalkRenderPass, 如下的两个Surface的Frame都会被处理成ResolvedFrame.而如果这两个Surface也有引用别的Surface, 那么它们也会被递归PrewalkSurface处理.

  • CopyPasses

    CopyPasses从root surface的Resolved Frame开始, 对每一个RenderPass, 会拷贝一份到Aggregated Frame中

  • CopyQuadsToPass

    进一步的,除了拷贝RenderPass, 还要拷贝RenderPass中的quads.比较特殊的是对SurfaceDrawQuad会调用HandleSurfaceQuad 递归处理引用的Surface.

  • HandleSurfaceQuad

    在HandleSurfaceQuad中, Surface的Frame中的非root RenderPass会直接拷贝到Aggregated Frame中.

    但root RenderPass有两种处理情况:

    • merge: 如果满足条件, child surface的root RenderPass中的DrawQuads会合入原来SurfaceDrawQuad所在的RenderPass.(下图左下)

    • non-merge: child surface的root RenderPass也会拷贝一份到Aggregated Frame中, 然后原来的SurfaceDrawQuad会修改为RenderPassDrawQuad,然后引用拷贝的RenderPass.(下图右下)

  • 遍历方式的不同

    可以看到, PreWalk和CopyPasses两个阶段遍历的方式不太一样, 我觉得主要原因是PreWalk阶段关注的是处理Surface的Frame, 而CopyPasses则是需要拷贝每一个DrawQuad.

Python 复制代码
#PreWalk
R:
for quad in quad_list
    if Surface
        Do(Surface)
        goto R
    if RenderPass
        goto R
        
#CopyPasses
R:
for pass in render_pass_list
    for quad in quad_list
        if Surface
            goto R 
        Do2(quad)

DrawFrame

所有Surface的Frame聚合成Aggregated Frame之后就交给DirectRenderer来绘制

  • 遍历每个RenderPass, 非root RenderPass分别画在一个独立的backing(纹理)上,root RenderPass则是上屏的surface上

下图的示例中, 左边的RenderPass先绘制到一个backing中, 然后再绘制root RenderPass的RenderPassDrawQuad时, 再把backing的内容绘制到上屏的framebuffer中.

  • DrawTileDrawQuad

    这是最常见的DrawQuad, 用来绘制光栅化后的tile纹理, DrawQuad里会有与绘制相关的字段:

    1. ResourceId: tile纹理在DisplayResourceProvider中的id, 这是在ResolveFrame阶段导入DisplayResourceProvider获得的.

    2. tex_coord_rect: tile纹理要取样的范围

    3. rect: 要绘制的位置, 表示的大小是在DrawQuad自身的坐标空间

    4. content_device_transform: 从DrawQuad的坐标空间到RenderPass的坐标空间的转换矩阵, 可以通过改变这个矩阵, 影响tile绘制的地方. 如发生滑动或者其它类似情况时, layer不需要重新光栅化, 只需要修改content_device_transform.

    下图是两个DrawTileDrawQuad绘制到RenderPass backing的示意图:

  • CanSkipRenderPass

    Renderer每次提交的Frame都包含了页面绘制的完整的内容, 所以可能会有RenderPass与上一次绘制相比, 没有发生改变.因此RenderPass的backing是在下一次DrawFrame的时候才决定是否销毁:

    • 如果新的Frame里没有上一次的RenderPass,那就销毁对应的backing.

    • 而如果RenderPass的backing没有被销毁, 绘制内容也没发生变化, 那么就可能复用上一次的backing, 减少一次绘制

  • CanPassBeDrawnDirectly

    上面说RenderPass会先绘制到自己的backing, 然后在DrawRenderPassQuad时再把backing的内容绘制到target backing/framebuffer上. 其实也有特殊情况, 如果一个RenderPass特别简单, 也可能省去这个RenderPass的backing, 直接绘制到target framebuffer中.

    如下, 左边RenderPass里的DrawQuad直接绘制到RenderPass的target framebuffer上

    在bypass绘制时, 绘制的位置由被bypass的RenderPass里的DrawQuad与引用该RenderPass的RenderPassDrawQuad同时决定.

PlainText 复制代码
在原来的流程中, DrawQuad的最终位置可以认为分成两步来计算:
1.先计算在RenderPass的backing的位置
backing_rect = rect * content_device_transform1(from DrawQuad)
2.然后计算RenderPass在target backing的位置
final_rect = backing_rect * content_device_transform2(from RenderPassDrawQuad)

而bypass则少了backing_rect的中间阶段:
final_rect = rect * content_device_transform1 * content_device_transform2
  • SkiaRenderer

    DoDrawQuad实际的执行由DirectRenderer的子类来实现.

    在之前, 硬件加速下是用GLRenderer来绘制DrawQuad的, 最近切换成了SkiaRenderer, SkiaRenderer有如下的优点

    1. Skia底层可以使用OpenGL, 也可以使用Vulkan等其他API; 而GLRenderer只支持OpenGL.

    2. SkiaRenderer可以支持在Frame中提交PaintOP, 通过将PaintOP转成SkiaOP来进行绘制.

      1. 上面说DrawTileQuad会绘制tile的纹理, 其实也可能是PaintOP.

参考

  1. docs.google.com/document/d/...

  2. www.chromium.org/developers/...

  3. chromium.googlesource.com/chromium/sr...

  4. docs.google.com/presentatio...

相关推荐
powerfulzyh2 天前
Docker中运行的Chrome崩溃问题解决
chrome·docker·容器
代码的乐趣2 天前
支持selenium的chrome driver更新到136.0.7103.92
chrome·python·selenium
努力学习的小廉2 天前
深入了解linux系统—— 自定义shell
linux·运维·chrome
fenglllle3 天前
macOS 15.4.1 Chrome不能访问本地网络
chrome·macos
yousuotu3 天前
python如何提取Chrome中的保存的网站登录用户名密码?
java·chrome·python
颜淡慕潇4 天前
【Python】超全常用 conda 命令整理
chrome·python·conda
网硕互联的小客服4 天前
如何解决 Linux 系统文件描述符耗尽的问题
linux·运维·chrome
海尔辛4 天前
学习黑客正经版Bash 脚本入门教程
chrome·学习·bash
@PHARAOH4 天前
HOW - 在 Mac 上的 Chrome 浏览器中调试 Windows 场景下的前端页面
前端·chrome·macos
zybsjn5 天前
开发 Chrome 扩展中的侧边栏图标设置实录(Manifest V3)
前端·chrome