背景
Android Webview是Android 中View的一个子类, 同样在UI线程接收input, 在RT(Render Thread) 渲染.以下是Android Webview在Android渲染流水线集成的图例:
RC: Renderer Compositor, 是chromium中Renderer模块的合成线程
-
input: 传递事件给Renderer处理
-
animate: 通知Renderer开始新的一帧(raf的回调等)
-
Renderer返回是否需要draw
-
onDraw: 如果第三步返回需要draw, 会在UI线程回调onDraw, 然后通知Renderer 生产新的Frame
-
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主要做两件事
-
把Frame中的RenderPass的id(原来是child surface 内部的)映射成全局统一的
-
把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里会有与绘制相关的字段:
-
ResourceId: tile纹理在DisplayResourceProvider中的id, 这是在ResolveFrame阶段导入DisplayResourceProvider获得的.
-
tex_coord_rect: tile纹理要取样的范围
-
rect: 要绘制的位置, 表示的大小是在DrawQuad自身的坐标空间
-
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有如下的优点
-
Skia底层可以使用OpenGL, 也可以使用Vulkan等其他API; 而GLRenderer只支持OpenGL.
-
SkiaRenderer可以支持在Frame中提交PaintOP, 通过将PaintOP转成SkiaOP来进行绘制.
- 上面说DrawTileQuad会绘制tile的纹理, 其实也可能是PaintOP.
-
参考