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...

相关推荐
东方隐侠安全团队-千里8 小时前
网安瞭望台第3期:俄黑客 TAG - 110组织与密码攻击手段分享
网络·chrome·web安全·网络安全
gqkmiss8 小时前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
秦老师Q13 小时前
「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用
前端·chrome·edge
滴水可藏海13 小时前
Chrome离线安装包下载
前端·chrome
Mr_Xuhhh18 小时前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
木古古181 天前
使用chrome 访问虚拟机Apache2 的默认页面,出现了ERR_ADDRESS_UNREACHABLE这个鸟问题
前端·chrome·apache
徐浪老师1 天前
深入实践 Shell 脚本编程:高效自动化操作指南
运维·chrome·自动化
Dklau-c2 天前
Linux下,修改环境变量的几种方法
linux·前端·chrome
Black蜡笔小新3 天前
无插件H5播放器EasyPlayer.js RTSP播放器chrome/edge等浏览器如何使用独立显卡
javascript·chrome·edge
gqkmiss3 天前
Chrome 浏览器 131 版本新特性
前端·chrome·浏览器·chrome 131