How canvas works

Published: by Creative Commons Licence

  • Categories:

Update:
2022.12.8: 添加了关于 DrDC 的内容。

前置依赖: cc,viz,blink

blink 中实现了2种 canvas,分别是 blink::HTMLCanvasElement 和 blink::OffscreenCanvas ,前者对应 html/dom 中的 canvas,后者对应 js 中的 OffscrenCanvas。

html canvas 有两种模式,一种是常规模式,这种模式下 canvas 的绘制时机受 viz/cc 的调度,和网页上的其他 dom 绘制的时机一致。另一种是低延迟模式 desynchronized = true,此时 canvas 的绘制会脱离 dom,它会作为一个独立的 viz client 使用 CanvasResourceDispatcher 来自主向 viz 提交要显示的画面(MAC 下还不支持低延迟模式 crbug.com/945835)。

OffscreenCanvas 可以脱离 dom 存在,原理类似 html canvas 的低延迟模式,也是作为一个独立的 viz client 存在,可以自主向 viz 提交要显示的画面。不同的是它可以跑在 worker 线程中,从而避免阻塞 blink 线程(线程名 CrRenderMain,cc 的绘制线程),而 html canvas 的低延迟模式只能跑在 blink 线程。

要在 canvas 上绘制内容,需要先获取绘制 context,最常用的就是 2d context,它在 html canvas 和 OffscreenCanvas 下有不同的实现, 分别为 blink::CanvasRenderingContext2D 和 blink::OffscreenCanvasRenderingContext2D,区别可以理解为后者只支持低延迟渲染模式,而前者不仅支持低延迟渲染模式,同时支持常规 canvas 渲染模式。

除了 2d context,以下这些 context 在两种 canvas 中都可以使用:

webgl - WebGLRenderingContext: 使用 webgl 接口绘制
webgl2 - WebGL2RenderingContext: 使用 webgl2 接口绘制
bitmaprenderer - ImageBitmapRenderingContext: 专门用来在 canvas 上绘制 ImageBitmap
webgpu - GPUCanvasContext: 使用 webgpu 接口进行 webgpu 计算

简单起见,这里重点介绍 html canvas + 2d context 场景下常规渲染链路和低延迟渲染链路。

网页渲染流程简介

由于 canvas 是网页内容的一部分,很难在不了解网页渲染流程的情况下单独理解 canvas 的渲染,因此这里先介绍下网页渲染的一般流程。

网页的渲染链路非常长,由于这里的重点是 canvas,因此只做简单介绍,不会过多展开,后续会有专门的文章介绍。

下面是网页渲染的全链路流程简图 blink-1000:

blink-1000

上图是 chromium 110 版本,Android 平台的典型链路(启用了 DrDC),在旧版本以及当前的 Linux 平台上 Raster 线程和上屏的线程还在同一个线程中(没有启用 DrDC)。更多关于 DrDC 的讨论见这里

上图是站在 1000 米高空望向浏览器内核的俯视图,称为 blink-1000,隐藏了大量的细节,下面是站在 500 米高空看浏览器内核的俯视图,称为 blink-500,放在这里做为下期的预告: blink-500

下面简单介绍整个流程:

  • vsync: 浏览器一帧的渲染从 vsync 信号开始,它会通知 render 进程中的 cc compositor 线程(或者叫 cc impl 线程)开始新的一帧;
  • BeginFrame: cc compositor 线程紧接着通知 cc render 线程进行内容的绘制;
  • DOM: 此时 blink 开始工作,它会先解析 html 生成 DOM 树;
  • Javascript: 此时如果注册有 requestAnimationFrame 回调或者交互事件回调,则会在此时执行(桩点1);
  • Styles + Layout: 然后计算每个节点的样式以及对每个节点进行布局排版;
  • Paint: 之后开始绘制,不同类别的 DOM 元素采用不同的绘制方法(桩点2),绘制完成之后进行合成,最终产出 cc::Layer 树,然后 blink 通知 cc compositor 线程绘制完成;
  • Commit: cc compositor 会从 cc::Layer 树构建自己的 cc::LayerImpl 树;
  • Tiles: 然后根据网页视口的范围/页面的缩放比例将 cc::LayerImpl 进行分块(Tiles);
    • Raster Tasks: 这些分块会被送往 worker 线程进行 raster;
    • Raster: worker 会把raster任务序列化到 commandbuffer, 并通知 CrGpuMain 线程进行真正的 raster 。
  • CompositorFrame: 回到 cc compositor 线程,他在分发完 raster 任务之后会根据 cc::LayerImpl 树构建 viz::CompositorFrame 对象,该对象表示一帧绘制内容(并不一定是整个网页,参考后面的canvas低延迟模式介绍),它会被提交(submit)到 viz compsoitor 线程中进行合成;
  • viz Composite: viz compositor 把多个 CF 合成为完整的页面(桩点3),然后提交到 compositor gpu 线程中;
  • Display: compositor gpu 调用 GL 进行真正的绘制以及上屏。

我在上面的流程中埋了3个桩点,这三个桩点就是 canvas 渲染涉及到的三个重要节点。下面会把 canvas 的不同流程插入到这些节点中去。

Canvas 类图

为了讲清楚 canvas 的实现原理,方便下文的描述,这里先看下 Canvas 相关的类图:

canvas

可以对照下文的流程介绍来理解上图,这里不再单独展开。

桩点1: 向 Canvas 中绘制内容

向 canvas 中绘制内容分为三步,第一步获取用于绘制的 context,第二步调用 context 的绘制 API 进行绘制,第三步完成绘制,提交结果。

获取用于绘制的 Context

开发者通过 canvas.getContext("XXX") 来获取 context 对象,这个 js api 会通过 blink::HTMLCanvasElement::GetCanvasRenderingContext 方法来获取 context。每种类型的 context 都有对应的 Factory 工厂类,所有这些类都注册在一个静态字典中,创建的时候根据 context 类型找到对应的工厂类,然后使用工厂类就可以直接创建 context 对象了。核心逻辑如下:

CanvasRenderingContext* HTMLCanvasElement::GetCanvasRenderingContextInternal(
    const String& type,
    const CanvasContextCreationAttributesCore& attributes) {
  CanvasRenderingContext::CanvasRenderingAPI rendering_api =
      CanvasRenderingContext::RenderingAPIFromId(type, GetExecutionContext());

  // 根据 type 获取对应的工厂方法
  CanvasRenderingContextFactory* factory =
      GetRenderingContextFactory(static_cast<int>(rendering_api));

  // 创建 context
  context_ = factory->Create(this, recomputed_attributes);

  // 如果创建的时候设置了 desynchronized = true,则创建 CanvasResourceDispatcher 对象。
  // 只要有这个对象就表示开启了低延迟模式,这个对象用于将 canvas 的绘制内容到封装到 CompositorFrame 中并提交。
  if (context_->CreationAttributes().desynchronized) {

    SetNeedsUnbufferedInputEvents(true);
    frame_dispatcher_ = std::make_unique<CanvasResourceDispatcher>();

  }

  return context_.Get();
}

可以看到,如果用户在调用 getContext 的时候设置了 desynchronized = true, 则会开启低延迟模式。

context = canvas.getContext("2d", {
  desynchronized: true
});

js 中的 context 对象对应 C++ 中的 blink::CanvasRenderingContext 对象。不同类型的 js context 分别对应 blink::CanvasRenderingContext 的不同子类,对应关系如下:

2d - blink::CanvasRenderingContext2D
webgl - blink::WebGLRenderingContext
webgl2 - blink::WebGL2RenderingContext
bitmaprenderer - blink::ImageBitmapRenderingContext
webgpu - blink::GPUCanvasContext

向 Canvas 中绘制内容

js 调用 context.drawXXX 方法向 canvas 中绘制内容时,会调用到 C++ blink::CanvasRenderingContext 中对应的方法,对于 2d context, 则对应 blink::CanvasRenderingContext2D。它内部定义了所有 2d context 可以使用的 API,这些 API 分布于三个具有继承关系的类中:

  • blink::CanvasPath: 提供 path 相关绘制 API;
  • blink::BaseRenderingContext2D: 提供除了文字绘制之外的 2D API;
  • blink::CanvasRenderingContext2D: 提供文字绘制相关 API;
class CanvasRenderingContext2D final
    : public CanvasRenderingContext,
      public BaseRenderingContext2D : public CanvasPath {

  // from CanvasPath
  void moveTo(double double_x, double double_y);
  void lineTo(double double_x, double double_y);
  void quadraticCurveTo(double double_cpx,
                        double double_cpy,
                        double double_x,
                        double double_y);
  ...

  // from BaseRenderingContext2D
  void fill(const String& winding = "nonzero");
  void fill(Path2D*, const String& winding = "nonzero");
  void stroke();
  void stroke(Path2D*);
  void clip(const String& winding = "nonzero");
  void clip(Path2D*, const String& winding = "nonzero");
  void scale(double sx, double sy);
  void rotate(double angle_in_radians);
  ...

  void fillText(const String& text, double x, double y, double max_width);
  void strokeText(const String& text, double x, double y);
  void strokeText(const String& text, double x, double y, double max_width);
  TextMetrics* measureText(const String& text);
  void drawFormattedText(FormattedText* formatted_text,
                         double x,
                         double y,
                         ExceptionState&);
  ...
};

所有的绘制操作都通过 cc::PaintCanvas 记录到 blink::CanvasResourceProvider 中。 cc::PaintCanvas 有个子类 cc::RecordPaintCanvas,专门用来把 2d 绘制操作记录到 cc::DisplayItemList 中,它只记录绘制操作而不会进行真正的绘制。

cc 提供了一个 cc::PaintRecorder 类,专门用来录制绘制操作,相关类图如下:

cc-paint-recorder

cc::PaintRecorder 就像一个录像机,不过录制的是 2d 绘制操作,当调用 beginRecording() 开始录制的时候它会返回一个 cc::RecordPaintCanvas 对象,它提供了所有的 2d api,外部使用该对象提供的 api 进行绘制,从而录制绘制命令。绘制完成之后外部调用 finishRecordingAsPicture() 方法获取录制的结果,录制的内容被序列化存储在 cc::PaintRecord/cc::PaintOpBuffer 中。

blink::CanvasResourceProvider 内部即使用 cc::PaintRecorder 来录制绘制指令。

在绘制时从 cc::PaintRecorder 中获取 cc::RecordPaintCanas

#0  cc::PaintRecorderBase::getRecordingCanvas (this=0x55d116f135c0) at ../../cc/paint/paint_recorder.h:36
#1  0x00007f60aa32340c in blink::CanvasResourceProvider::Canvas (this=0x55d116f4f290, needs_will_draw=false) at ../../third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc:1311
#2  0x00007f60aa304239 in blink::Canvas2DLayerBridge::GetPaintCanvas (this=0x55d116f064f0) at ../../third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc:362
#3  0x00007f60a24339af in blink::CanvasRenderingContext2D::GetPaintCanvasForDraw (this=0x23e50058b288, dirty_rect=..., draw_type=blink::CanvasPerformanceMonitor::DrawType::kPath)
    at ../../third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc:450
#4  0x00007f60a240c6f5 in blink::BaseRenderingContext2D::DrawInternal<(blink::BaseRenderingContext2D::OverdrawOp)0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_1>(blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0 const&, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_1 const&, SkRect const&, blink::CanvasRenderingContext2DState::PaintType, blink::CanvasRenderingContext2DState::ImageType, SkIRect const&, blink::CanvasPerformanceMonitor::DrawType) (
    this=0x23e50058b2f8, draw_func=..., draw_covers_clip_bounds=..., bounds=..., paint_type=blink::CanvasRenderingContext2DState::kStrokePaintType, image_type=blink::CanvasRenderingContext2DState::kNoImage, clip_bounds=...,
    draw_type=blink::CanvasPerformanceMonitor::DrawType::kPath) at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h:726
#5  0x00007f60a2405c7c in blink::BaseRenderingContext2D::Draw<(blink::BaseRenderingContext2D::OverdrawOp)0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_1>(blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0 const&, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_1 const&, SkRect const&, blink::CanvasRenderingContext2DState::PaintType, blink::CanvasRenderingContext2DState::ImageType, blink::CanvasPerformanceMonitor::DrawType) (this=0x23e50058b2f8, draw_func=...,
    draw_covers_clip_bounds=..., bounds=..., paint_type=blink::CanvasRenderingContext2DState::kStrokePaintType, image_type=blink::CanvasRenderingContext2DState::kNoImage, draw_type=blink::CanvasPerformanceMonitor::DrawType::kPath)
    at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h:763
#6  0x00007f60a2405951 in blink::BaseRenderingContext2D::DrawPathInternal (this=0x23e50058b2f8, path=..., paint_type=blink::CanvasRenderingContext2DState::kStrokePaintType, fill_type=SkPathFillType::kWinding,
    use_paint_cache=cc::UsePaintCache::kDisabled) at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc:1029
#7  0x00007f60a2405ec7 in blink::BaseRenderingContext2D::stroke (this=0x23e50058b2f8) at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc:1077
#8  0x00007f60a178a36a in blink::(anonymous namespace)::v8_canvas_rendering_context_2d::StrokeOperationOverload1 (info=...) at gen/third_party/blink/renderer/bindings/modules/v8/v8_canvas_rendering_context_2d.cc:4466
#9  0x00007f60a176c3db in blink::(anonymous namespace)::v8_canvas_rendering_context_2d::StrokeOperationCallback (info=...) at gen/third_party/blink/renderer/bindings/modules/v8/v8_canvas_rendering_context_2d.cc:4506

获取之后进行绘制:

#0  blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0::operator()(cc::PaintCanvas*, cc::PaintFlags const*) const (
    this=0x7ffd4ecc0e80, c=0x55d116f61750, flags=0x23e50058bc80) at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc:1032
#1  0x00007f60a240c708 in blink::BaseRenderingContext2D::DrawInternal<(blink::BaseRenderingContext2D::OverdrawOp)0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPat
hFillType, cc::UsePaintCache)::$_0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_1>(blink::BaseRenderingContext2D::DrawPathInter
nal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0 const&, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType,
SkPathFillType, cc::UsePaintCache)::$_1 const&, SkRect const&, blink::CanvasRenderingContext2DState::PaintType, blink::CanvasRenderingContext2DState::ImageType, SkIRect const&, blink::CanvasPerformanceMonitor::DrawType) (
    this=0x23e50058b2f8, draw_func=..., draw_covers_clip_bounds=..., bounds=..., paint_type=blink::CanvasRenderingContext2DState::kStrokePaintType, image_type=blink::CanvasRenderingContext2DState::kNoImage, clip_bounds=...,
    draw_type=blink::CanvasPerformanceMonitor::DrawType::kPath) at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h:726
#2  0x00007f60a2405c7c in blink::BaseRenderingContext2D::Draw<(blink::BaseRenderingContext2D::OverdrawOp)0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillTyp
e, cc::UsePaintCache)::$_0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_1>(blink::BaseRenderingContext2D::DrawPathInternal(blin
k::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0 const&, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFi
llType, cc::UsePaintCache)::$_1 const&, SkRect const&, blink::CanvasRenderingContext2DState::PaintType, blink::CanvasRenderingContext2DState::ImageType, blink::CanvasPerformanceMonitor::DrawType) (this=0x23e50058b2f8, draw_func=...,
    draw_covers_clip_bounds=..., bounds=..., paint_type=blink::CanvasRenderingContext2DState::kStrokePaintType, image_type=blink::CanvasRenderingContext2DState::kNoImage, draw_type=blink::CanvasPerformanceMonitor::DrawType::kPath)
    at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h:763
#3  0x00007f60a2405951 in blink::BaseRenderingContext2D::DrawPathInternal (this=0x23e50058b2f8, path=..., paint_type=blink::CanvasRenderingContext2DState::kStrokePaintType, fill_type=SkPathFillType::kWinding,
    use_paint_cache=cc::UsePaintCache::kDisabled) at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc:1029
#4  0x00007f60a2405ec7 in blink::BaseRenderingContext2D::stroke (this=0x23e50058b2f8) at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc:1077
#5  0x00007f60a178a36a in blink::(anonymous namespace)::v8_canvas_rendering_context_2d::StrokeOperationOverload1 (info=...) at gen/third_party/blink/renderer/bindings/modules/v8/v8_canvas_rendering_context_2d.cc:4466
#6  0x00007f60a176c3db in blink::(anonymous namespace)::v8_canvas_rendering_context_2d::StrokeOperationCallback (info=...) at gen/third_party/blink/renderer/bindings/modules/v8/v8_canvas_rendering_context_2d.cc:4506

至此,我们知道了所有的绘制都保存到了 blink::CanvasResourceProvider 中。这些数据会在下一环节被取出。

Tips: getImageData

在旧版本的内核中,当调用 getImageData 读取 canvas 的内容的时候会禁用 canvas 的 GPU 加速,在最新的内核中当调用该函数的次数大于2次也会关闭GPU加速,因此如果只是偶尔使用该 API,又不想导致 canvas 的 GPU 加速被关闭,可以在创建 context 的时候指定 willReadFrequentlyfalse,此时如果调用超过 2 次只会打印一条警告信息,不会切换到 CPU。最新版本内核的判断逻辑如下:

ImageData* BaseRenderingContext2D::getImageDataInternal(...) {
  ...
  // The default behavior before the willReadFrequently feature existed:
  // Accelerated canvases fall back to CPU when there is a readback.

  // will_read_frequently_value 默认为 kUndefined
  if (will_read_frequently_value ==
      CanvasContextCreationAttributesCore::WillReadFrequently::kUndefined) {
    // GetImageData is faster in Unaccelerated canvases.
    // In Desynchronized canvas disabling the acceleration will break
    // putImageData: crbug.com/1112060.
    if (IsAccelerated() && !IsDesynchronized()) {
      read_count_++;
      // kFallbackToCPUAfterReadbacks = 2
      if (read_count_ >= kFallbackToCPUAfterReadbacks) {
        // 关闭 GPU 加速,切换到 CPU 模式
        DisableAcceleration();
      }
    }
  }
}

完成绘制,提交结果

当所有的 js 绘制指令执行完毕之后,html canvas 在 2d context 下不需要显式的提交结果(C++内部会自动 flush),这点和 OffscreenCanvas 以及非 2d context 不同,这些模式都需要显示的提交绘制结果(在某些情况下也可以省略)。

桩点2: 取出 Canvas 中的内容

在上一节中,我们知道所有的绘制操作都保存到了 blink::CanvasResourceProvider 中,再具体一些是保存在了 cc::PaintRecord 中。但是取出 canvas 的内容并不仅仅是取出 cc::PaintRecord 而已。

其实理论上这样做也可以,把这些内容包装到 cc::PictureLayer 中然后和其他普通的 html 元素走一样的流程,不过这样只能处理 2d context 的 canvas,其他的比如 webgl context 的 canvas 还要单独处理。

话说回来,取出 cc::PaintRecord 之后需要进行 raster 才能提交。触发 raster 的时机在 canvas 的低延迟模式和非低延迟模式下是不同的。下面分别分析。

正常模式下取出 Canvas 数据

取出 Canvas 数据分两个过程,首先创建 cc::TextureLayer,然后用 raster 的结果填充 cc::TextureLayer,最终产生一个完整的 cc::TextureLayer 对象,至此外部就只需要和 cc::TextureLayer 打交道(比如合成,提交等)了,后续逻辑和 canvas 就没有任何关系了。

下面的逻辑取出 HTMLCanvasElement 对应的 Canvas2DLayerBridge 中提前创建好的 cc::TextureLayer,它会在后续提交的时候通过 cc::TextureLayerClient 回调从 blink::CanvasResource(Provider) 中取出 raster 的结果。

void HTMLCanvasPainter::PaintReplaced(const PaintInfo& paint_info,
                                      const PhysicalOffset& paint_offset) {
  ...
  // 取出 cc::TextureLayer
  if (auto* layer = canvas->ContentsCcLayer()) {
    ...
    // We do not take the foreign layer code path when printing because it
    // prevents painting canvas content as vector graphics.
    if (!paint_info.ShouldOmitCompositingInfo() && !canvas->IsPrinting()) {
      gfx::Rect pixel_snapped_rect = ToPixelSnappedRect(paint_rect);
      layer->SetBounds(pixel_snapped_rect.size());
      layer->SetIsDrawable(true);
      layer->SetHitTestable(true);
      // 记录 cc::TextureLayer 到 blink::PaintArtifact 中
      RecordForeignLayer(context, layout_html_canvas_,
                         DisplayItem::kForeignLayerCanvas, layer,
                         pixel_snapped_rect.origin());
      return;
    }
  }
  // 绘制 Canvas 初始化前的首帧
  canvas->Paint(context, paint_rect, paint_info.ShouldOmitCompositingInfo());
}

调用堆栈:

将取出的 cc::TextureLayer 保存到 blink::PaintArtifact 中(详情见后续的 blink 渲染分享)。 
#0  blink::RecordForeignLayer (context=..., client=..., type=blink::DisplayItem::kForeignLayerFirst, layer=scoped_refptr((cc::TextureLayer *)0x55d116e653f0), origin=..., properties=0x0)
    at ../../third_party/blink/renderer/platform/graphics/paint/foreign_layer_display_item.cc:52
↑
取出 HTMLCanvasElement 中提前创建好的 cc::TextureLayer
#0  blink::Canvas2DLayerBridge::Layer (this=0x55d116edaa50) at ../../third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc:715
#1  0x00007f60a243757f in blink::CanvasRenderingContext2D::CcLayer (this=0x23e5002104a0) at ../../third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc:1103
#2  0x00007f60b0a0bb4f in blink::HTMLCanvasElement::ContentsCcLayer (this=0x23e5001251a0) at ../../third_party/blink/renderer/core/html/canvas/html_canvas_element.cc:1805
#3  0x00007f60b1cbee6e in blink::HTMLCanvasPainter::PaintReplaced (this=0x7ffd4ecc0550, paint_info=..., paint_offset=...) at ../../third_party/blink/renderer/core/paint/html_canvas_painter.cc:47
#4  0x00007f60b13e5eaf in blink::LayoutHTMLCanvas::PaintReplaced (this=0x23e500103360, paint_info=..., paint_offset=...) at ../../third_party/blink/renderer/core/layout/layout_html_canvas.cc:46
#5  0x00007f60b1db01d6 in blink::ReplacedPainter::Paint (this=0x7ffd4ecc0928, paint_info=...) at ../../third_party/blink/renderer/core/paint/replaced_painter.cc:188
#6  0x00007f60b14512e7 in blink::LayoutReplaced::Paint (this=0x23e500103360, paint_info=...) at ../../third_party/blink/renderer/core/layout/layout_replaced.cc:157
#7  0x00007f60b1d12801 in blink::ObjectPainter::PaintAllPhasesAtomically (this=0x7ffd4ecc09a8, paint_info=...) at ../../third_party/blink/renderer/core/paint/object_painter.cc:120
#8  0x00007f60b1cd7aa0 in blink::(anonymous namespace)::PaintFragment (fragment=..., paint_info=...) at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:403
#9  0x00007f60b1cd9777 in blink::NGBoxFragmentPainter::PaintBoxItem (this=0x7ffd4ecc1a18, item=..., child_fragment=..., cursor=..., paint_info=..., paint_offset=...)
    at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:1675
#10 0x00007f60b1cda1ab in blink::NGBoxFragmentPainter::PaintBoxItem (this=0x7ffd4ecc1a18, item=..., cursor=..., paint_info=..., paint_offset=..., parent_offset=...)
    at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:1709
#11 0x00007f60b1cd5f32 in blink::NGBoxFragmentPainter::PaintInlineItems (this=0x7ffd4ecc1a18, paint_info=..., paint_offset=..., parent_offset=..., cursor=0x7ffd4ecc1238)
    at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:1467
#12 0x00007f60b1cd7403 in blink::NGBoxFragmentPainter::PaintLineBoxChildItems (this=0x7ffd4ecc1a18, children=0x7ffd4ecc13f8, paint_info=..., paint_offset=...)
    at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:1557
#13 0x00007f60b1cd65cd in blink::NGBoxFragmentPainter::PaintLineBoxes (this=0x7ffd4ecc1a18, paint_info=..., paint_offset=...) at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:839
#14 0x00007f60b1cd441c in blink::NGBoxFragmentPainter::PaintObject (this=0x7ffd4ecc1a18, paint_info=..., paint_offset=..., suppress_box_decoration_background=false)
    at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:687
#15 0x00007f60b1cd37f0 in blink::NGBoxFragmentPainter::PaintInternal (this=0x7ffd4ecc1a18, paint_info=...) at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:535
#16 0x00007f60b1cd2ffc in blink::NGBoxFragmentPainter::Paint (this=0x7ffd4ecc1a18, paint_info=...) at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:441
#17 0x00007f60b1cd7825 in blink::NGBoxFragmentPainter::PaintBlockChild (this=0x7ffd4ecc2368, child=..., paint_info=..., paint_info_for_descendants=..., paint_offset=...)
    at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:888
#18 0x00007f60b1cd6866 in blink::NGBoxFragmentPainter::PaintBlockChildren (this=0x7ffd4ecc2368, paint_info=..., paint_offset=...) at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:852
#19 0x00007f60b1cd4460 in blink::NGBoxFragmentPainter::PaintObject (this=0x7ffd4ecc2368, paint_info=..., paint_offset=..., suppress_box_decoration_background=false)
    at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:689
#20 0x00007f60b1cd37f0 in blink::NGBoxFragmentPainter::PaintInternal (this=0x7ffd4ecc2368, paint_info=...) at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:535
#21 0x00007f60b1cd2ffc in blink::NGBoxFragmentPainter::Paint (this=0x7ffd4ecc2368, paint_info=...) at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:441
#22 0x00007f60b1cd7825 in blink::NGBoxFragmentPainter::PaintBlockChild (this=0x7ffd4ecc2d40, child=..., paint_info=..., paint_info_for_descendants=..., paint_offset=...)
    at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:888
#23 0x00007f60b1cd6866 in blink::NGBoxFragmentPainter::PaintBlockChildren (this=0x7ffd4ecc2d40, paint_info=..., paint_offset=...) at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:852
#24 0x00007f60b1cd4460 in blink::NGBoxFragmentPainter::PaintObject (this=0x7ffd4ecc2d40, paint_info=..., paint_offset=..., suppress_box_decoration_background=false)
    at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:689
#25 0x00007f60b1cd37f0 in blink::NGBoxFragmentPainter::PaintInternal (this=0x7ffd4ecc2d40, paint_info=...) at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:535
#26 0x00007f60b1cd2ffc in blink::NGBoxFragmentPainter::Paint (this=0x7ffd4ecc2d40, paint_info=...) at ../../third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc:441
#27 0x00007f60b1d40a9d in blink::PaintLayerPainter::PaintFragmentWithPhase (this=0x7ffd4ecc31c0, phase=blink::PaintPhase::kForeground, fragment_data=..., physical_fragment=0x23e50023aaa8, context=..., paint_flags=0)
    at ../../third_party/blink/renderer/core/paint/paint_layer_painter.cc:378
#28 0x00007f60b1d3ff11 in blink::PaintLayerPainter::PaintWithPhase (this=0x7ffd4ecc31c0, phase=blink::PaintPhase::kForeground, context=..., paint_flags=0) at ../../third_party/blink/renderer/core/paint/paint_layer_painter.cc:412
#29 0x00007f60b1d4051a in blink::PaintLayerPainter::PaintForegroundPhases (this=0x7ffd4ecc31c0, context=..., paint_flags=0) at ../../third_party/blink/renderer/core/paint/paint_layer_painter.cc:435
#30 0x00007f60b1d3f965 in blink::PaintLayerPainter::Paint (this=0x7ffd4ecc31c0, context=..., paint_flags=0) at ../../third_party/blink/renderer/core/paint/paint_layer_painter.cc:253
#31 0x00007f60b1d400df in blink::PaintLayerPainter::PaintChildren (this=0x7ffd4ecc3518, children_to_visit=blink::kNormalFlowAndPositiveZOrderChildren, context=..., paint_flags=0)
    at ../../third_party/blink/renderer/core/paint/paint_layer_painter.cc:316
#32 0x00007f60b1d3fa14 in blink::PaintLayerPainter::Paint (this=0x7ffd4ecc3518, context=..., paint_flags=0) at ../../third_party/blink/renderer/core/paint/paint_layer_painter.cc:265
#33 0x00007f60b1cba881 in blink::FramePainter::Paint (this=0x7ffd4ecc3770, context=..., paint_flags=0) at ../../third_party/blink/renderer/core/paint/frame_painter.cc:91
#34 0x00007f60b07eee60 in blink::LocalFrameView::PaintFrame (this=0x23e50021b1d0, context=..., paint_flags=0) at ../../third_party/blink/renderer/core/frame/local_frame_view.cc:4043
#35 0x00007f60b07ed9fb in blink::LocalFrameView::PaintTree (this=0x23e50021b1d0, benchmark_mode=blink::PaintBenchmarkMode::kNormal, cycle_scope=...) at ../../third_party/blink/renderer/core/frame/local_frame_view.cc:2977
#36 0x00007f60b07ec40b in blink::LocalFrameView::RunPaintLifecyclePhase (this=0x23e50021b1d0, benchmark_mode=blink::PaintBenchmarkMode::kNormal) at ../../third_party/blink/renderer/core/frame/local_frame_view.cc:2778
#37 0x00007f60b07ead55 in blink::LocalFrameView::UpdateLifecyclePhasesInternal (this=0x23e50021b1d0, target_state=blink::DocumentLifecycle::kPaintClean) at ../../third_party/blink/renderer/core/frame/local_frame_view.cc:2519
#38 0x00007f60b07e98df in blink::LocalFrameView::UpdateLifecyclePhases (this=0x23e50021b1d0, target_state=blink::DocumentLifecycle::kPaintClean, reason=blink::DocumentUpdateReason::kBeginMainFrame)
    at ../../third_party/blink/renderer/core/frame/local_frame_view.cc:2323
#39 0x00007f60b07e8f80 in blink::LocalFrameView::UpdateAllLifecyclePhases (this=0x23e50021b1d0, reason=blink::DocumentUpdateReason::kBeginMainFrame) at ../../third_party/blink/renderer/core/frame/local_frame_view.cc:2086
#40 0x00007f60b1bfe2a7 in blink::PageAnimator::UpdateAllLifecyclePhases (this=0x23e5001e44d0, root_frame=..., reason=blink::DocumentUpdateReason::kBeginMainFrame) at ../../third_party/blink/renderer/core/page/page_animator.cc:159
#41 0x00007f60b1be6d84 in blink::Page::UpdateLifecycle (this=0x23e50020c950, root=..., requested_update=blink::WebLifecycleUpdate::kAll, reason=blink::DocumentUpdateReason::kBeginMainFrame)
    at ../../third_party/blink/renderer/core/page/page.cc:1153
#42 0x00007f60b093c094 in blink::WebFrameWidgetImpl::UpdateLifecycle (this=0x23e500211e00, requested_update=blink::WebLifecycleUpdate::kAll, reason=blink::DocumentUpdateReason::kBeginMainFrame)
    at ../../third_party/blink/renderer/core/frame/web_frame_widget_impl.cc:1395
#43 0x00007f60aa860e91 in blink::WidgetBase::UpdateVisualState (this=0x55d116e7cdd0) at ../../third_party/blink/renderer/platform/widget/widget_base.cc:891
--Type <RET> for more, q to quit, c to continue without paging--
#44 0x00007f60aa7f11e5 in blink::LayerTreeView::UpdateLayerTreeHost (this=0x55d116e1ef10) at ../../third_party/blink/renderer/platform/widget/compositing/layer_tree_view.cc:232
#45 0x00007f60bc6196ce in cc::LayerTreeHost::RequestMainFrameUpdate (this=0x55d116eb2d20, report_metrics=true) at ../../cc/trees/layer_tree_host.cc:376
#46 0x00007f60bc734139 in cc::ProxyMain::BeginMainFrame (this=0x55d116eb3ba0, begin_main_frame_state=...) at ../../cc/trees/proxy_main.cc:280

正常模式下在 blink 绘制完成之后,cc::Layer 会在 cc 的调度下通过 cc::TextureLayerClient 接口触发 Canvas 内部数据的 Raster,Raster 的结果会先保存在 blink::CanvasResource 中, 然后在 PrepareTransferableResource 调用返回之前存入 viz::TransferableResource,代码如下:

bool TextureLayer::Update() {
  bool updated = Layer::Update();
  if (client_.Read(*this)) {
    viz::TransferableResource resource;
    viz::ReleaseCallback release_callback;
    // 触发 Raster 并获取对应的 viz::TransferableResource 资源
    if (client_.Write(*this)->PrepareTransferableResource(this, &resource,
                                                          &release_callback)) {
      // Already within a commit, no need to do another one immediately.
      bool requires_commit = false;
      SetTransferableResourceInternal(resource, std::move(release_callback),
                                      requires_commit);
      updated = true;
    }
  }

  // SetTransferableResource could be called externally and the same mailbox
  // used for different textures.  Such callers notify this layer that the
  // texture has changed by calling SetNeedsDisplay, so check for that here.
  return updated || !update_rect().IsEmpty();
}

进行 Raster,结果保存在 blink::CanvasResource 中:

#0  gpu::raster::RasterImplementation::RasterCHROMIUM (this=0x5645febea560, list=0x5645fef0fbc0, 
    provider=0x5645fecb56c0, content_size=..., full_raster_rect=..., playback_rect=..., post_translate=..., 
    post_scale=..., requires_clear=false, max_op_size_hint=0x7ffe1ea89520, preserve_recording=false) 
    at ../../gpu/command_buffer/client/raster_implementation.cc:1345 
#1  0x00007fdb73f2420e in blink::CanvasResourceProvider::RasterRecordOOP (this=0x5645fedecfc0, last_recording=..., 
    needs_clear=true, mailbox=..., preserve_recording=false) 
    at ../../third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc:1471 
#2  0x00007fdb73f2e9d3 in blink::CanvasResourceProviderSharedImage::RasterRecord (this=0x5645fedecfc0, 
    last_recording=..., preserve_recording=false) 
    at ../../third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc:504 
#3  0x00007fdb73f23aa0 in blink::CanvasResourceProvider::FlushCanvasInternal (this=0x5645fedecfc0, 
    preserve_recording=false) at ../../third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc:1417 
#4  0x00007fdb73f23bd3 in blink::CanvasResourceProvider::FlushCanvasAndMaybePreserveRecording (this=0x5645fedecfc0, 
    printing=false) at ../../third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc:1392 
#5  0x00007fdb73f03637 in blink::Canvas2DLayerBridge::FlushRecording (this=0x5645fecc5c70, printing=false) 
    at ../../third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc:563 
#6  0x00007fdb73f052f9 in blink::Canvas2DLayerBridge::PrepareTransferableResource(cc::SharedBitmapIdRegistrar*, viz::TransferableResource*, base::OnceCallback<void (gpu::SyncToken const&, bool)>*) (this=0x5645fecc5c70, 
    bitmap_registrar=0x5645fecb4330, out_resource=0x7ffe1ea89a50, out_release_callback=0x7ffe1ea89a38) 
    at ../../third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc:682 
#7  0x00007fdb85fe516c in cc::TextureLayer::Update (this=0x5645fecb4220) at ../../cc/layers/texture_layer.cc:182 
#8  0x00007fdb8621de68 in cc::LayerTreeHost::PaintContent (this=0x5645feb4f480, update_layer_list=...) 
    at ../../cc/trees/layer_tree_host.cc:1645 
#9  0x00007fdb8621d0e6 in cc::LayerTreeHost::DoUpdateLayers (this=0x5645feb4f480) 
    at ../../cc/trees/layer_tree_host.cc:970 
#10 0x00007fdb8621c438 in cc::LayerTreeHost::UpdateLayers (this=0x5645feb4f480) 
    at ../../cc/trees/layer_tree_host.cc:825 
#11 0x00007fdb863346c6 in cc::ProxyMain::BeginMainFrame (this=0x5645feb50300, begin_main_frame_state=...) 
    at ../../cc/trees/proxy_main.cc:345 

通过 cc::TextureLayerClient 接口从 blink::CanvasResource 中获取 Raster 的结果(gpu::Mailbox),并据此创建 viz::TransferableResource 资源:

#0  viz::TransferableResource::MakeGpu (mailbox=..., filter=9729, texture_target=3553, sync_token=..., size=..., format=..., is_overlay_candidate=false) at ../../components/viz/common/resources/transferable_resource.h:92
#1  0x00007f60aa311d37 in viz::TransferableResource::MakeGpu (mailbox=..., filter=9729, texture_target=3553, sync_token=..., size=..., format=viz::RGBA_8888, is_overlay_candidate=false)
    at ../../components/viz/common/resources/transferable_resource.h:70
#2  0x00007f60aa30be9f in blink::CanvasResource::PrepareAcceleratedTransferableResource (this=0x29c400278700, out_resource=0x7ffd4ecc3e50, sync_mode=blink::kUnverifiedSyncToken)
    at ../../third_party/blink/renderer/platform/graphics/canvas_resource.cc:180
#3  0x00007f60aa30ba0c in blink::CanvasResource::PrepareTransferableResource(viz::TransferableResource*, base::OnceCallback<void (scoped_refptr<blink::CanvasResource>&&, gpu::SyncToken const&, bool)>*, blink::MailboxSyncMode) (
    this=0x29c400278700, out_resource=0x7ffd4ecc3e50, out_callback=0x7ffd4ecc3c60, sync_mode=blink::kUnverifiedSyncToken) at ../../third_party/blink/renderer/platform/graphics/canvas_resource.cc:163
#4  0x00007f60aa3053d2 in blink::Canvas2DLayerBridge::PrepareTransferableResource(cc::SharedBitmapIdRegistrar*, viz::TransferableResource*, base::OnceCallback<void (gpu::SyncToken const&, bool)>*) (this=0x55d116f23920,
    bitmap_registrar=0x55d116e06270, out_resource=0x7ffd4ecc3e50, out_release_callback=0x7ffd4ecc3e38) at ../../third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc:696
#5  0x00007f60bc3e516c in cc::TextureLayer::Update (this=0x55d116e06160) at ../../cc/layers/texture_layer.cc:182
#6  0x00007f60bc61de68 in cc::LayerTreeHost::PaintContent (this=0x55d116eb2d20, update_layer_list=...) at ../../cc/trees/layer_tree_host.cc:1645
#7  0x00007f60bc61d0e6 in cc::LayerTreeHost::DoUpdateLayers (this=0x55d116eb2d20) at ../../cc/trees/layer_tree_host.cc:970
#8  0x00007f60bc61c438 in cc::LayerTreeHost::UpdateLayers (this=0x55d116eb2d20) at ../../cc/trees/layer_tree_host.cc:825
#9  0x00007f60bc7346c6 in cc::ProxyMain::BeginMainFrame (this=0x55d116eb3ba0, begin_main_frame_state=...) at ../../cc/trees/proxy_main.cc:345

总结一下,整个流程可以概括为:

  • 在 blink 绘制阶段使用 blink::RecordForeignLayer() 记录 HTMLCanvasElement 对应的 cc::TextureLayer,从而在后续 blink 合成阶段将它加入 blink 合成的 cc::Layer 树;
  • 在 blink 合成阶段之后提交之前,将 CanvasResourceProvider 中记录的绘制结果 cc::PaintRecord 进行 Raster,并保存在 cc::CanvasResource 中;
  • 然后通过 cc::TextureLayerClient 接口从 cc::CanvasResource 中取出结果,放入 cc::TransferableResource;

低延迟模式下取出 Canvas 数据

低延迟模式下,canvas 的每次绘制流程开始前都会设置一个标记,表示有新内容绘制了,此时会注册回调监听 blink 线程中当前任务结束的回调,在这个回调中触发 Canvas 内容的 Raster 以及提交。

绘制前注册回调的流程:

#0  blink::CanvasRenderingContext::DidDraw (this=0x23e5002113e8, dirty_rect=..., draw_type=blink::CanvasPerformanceMonitor::DrawType::kPath) at ../../third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc:76
#1  0x00007f60a2433964 in blink::CanvasRenderingContext2D::GetPaintCanvasForDraw (this=0x23e5002113e8, dirty_rect=..., draw_type=blink::CanvasPerformanceMonitor::DrawType::kPath)
    at ../../third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc:441
#2  0x00007f60a240c6f5 in blink::BaseRenderingContext2D::DrawInternal<(blink::BaseRenderingContext2D::OverdrawOp)0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_1>(blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0 const&, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_1 const&, SkRect const&, blink::CanvasRenderingContext2DState::PaintType, blink::CanvasRenderingContext2DState::ImageType, SkIRect const&, blink::CanvasPerformanceMonitor::DrawType) (
    this=0x23e500211458, draw_func=..., draw_covers_clip_bounds=..., bounds=..., paint_type=blink::CanvasRenderingContext2DState::kStrokePaintType, image_type=blink::CanvasRenderingContext2DState::kNoImage, clip_bounds=...,
    draw_type=blink::CanvasPerformanceMonitor::DrawType::kPath) at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h:726
#3  0x00007f60a2405c7c in blink::BaseRenderingContext2D::Draw<(blink::BaseRenderingContext2D::OverdrawOp)0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_1>(blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_0 const&, blink::BaseRenderingContext2D::DrawPathInternal(blink::Path const&, blink::CanvasRenderingContext2DState::PaintType, SkPathFillType, cc::UsePaintCache)::$_1 const&, SkRect const&, blink::CanvasRenderingContext2DState::PaintType, blink::CanvasRenderingContext2DState::ImageType, blink::CanvasPerformanceMonitor::DrawType) (this=0x23e500211458, draw_func=...,
    draw_covers_clip_bounds=..., bounds=..., paint_type=blink::CanvasRenderingContext2DState::kStrokePaintType, image_type=blink::CanvasRenderingContext2DState::kNoImage, draw_type=blink::CanvasPerformanceMonitor::DrawType::kPath)
    at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.h:763
#4  0x00007f60a2405951 in blink::BaseRenderingContext2D::DrawPathInternal (this=0x23e500211458, path=..., paint_type=blink::CanvasRenderingContext2DState::kStrokePaintType, fill_type=SkPathFillType::kWinding,
    use_paint_cache=cc::UsePaintCache::kDisabled) at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc:1029
#5  0x00007f60a2405ec7 in blink::BaseRenderingContext2D::stroke (this=0x23e500211458) at ../../third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc:1077

注册回调:

// 有绘制发生时会触发
void CanvasRenderingContext::DidDraw(
    const SkIRect& dirty_rect,
    CanvasPerformanceMonitor::DrawType draw_type) {
  Host()->DidDraw(dirty_rect);

  auto& monitor = GetCanvasPerformanceMonitor();
  monitor.DidDraw(draw_type);
  if (did_draw_in_current_task_)
    return;

  monitor.CurrentTaskDrawsToContext(this);
  did_draw_in_current_task_ = true;
  // We need to store whether the document is being printed because the
  // document may exit printing state by the time DidProcessTast is called.
  // This is an issue with beforeprint event listeners.
  did_print_in_current_task_ = Host()->IsPrinting();
  // 在这里注册回调
  Thread::Current()->AddTaskObserver(this);
}

// 当前任务完成时触发
void CanvasRenderingContext::DidProcessTask(
    const base::PendingTask& /* pending_task */) {
  RenderTaskEnded();

  // The end of a script task that drew content to the canvas is the point
  // at which the current frame may be considered complete.
  if (Host())
    Host()->PreFinalizeFrame();
  // 内部触发 raster
  FinalizeFrame(did_print_in_current_task_);
  did_print_in_current_task_ = false;
  if (Host())
    // 内部触发 raster 结果的提交
    Host()->PostFinalizeFrame();
}

在回调被触发后,进行 Raster,Raster 的结果会保存在 blink::CanvasResource 中:

#0  blink::CanvasResourceProvider::RasterRecordOOP (this=0x55d116e7b1a0, last_recording=..., needs_clear=false, mailbox=..., preserve_recording=false) at ../../third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc:1439
#1  0x00007f60aa32e9d3 in blink::CanvasResourceProviderSharedImage::RasterRecord (this=0x55d116e7b1a0, last_recording=..., preserve_recording=false) at ../../third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc:504
#2  0x00007f60aa323aa0 in blink::CanvasResourceProvider::FlushCanvasInternal (this=0x55d116e7b1a0, preserve_recording=false) at ../../third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc:1417
#3  0x00007f60aa323bd3 in blink::CanvasResourceProvider::FlushCanvasAndMaybePreserveRecording (this=0x55d116e7b1a0, printing=false) at ../../third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc:1392
#4  0x00007f60aa303637 in blink::Canvas2DLayerBridge::FlushRecording (this=0x55d116f23920, printing=false) at ../../third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc:563
#5  0x00007f60aa3056fe in blink::Canvas2DLayerBridge::FinalizeFrame (this=0x55d116f23920, printing=false) at ../../third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc:731
#6  0x00007f60a2434e65 in blink::CanvasRenderingContext2D::FinalizeFrame (this=0x23e5002180d8, printing=false) at ../../third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc:676
#7  0x00007f60b0a00908 in blink::CanvasRenderingContext::DidProcessTask (this=0x23e5002180d8) at ../../third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc:100
#8  0x00007f60a2434801 in blink::CanvasRenderingContext2D::DidProcessTask (this=0x23e5002180d8, pending_task=From PostTaskToMainThread()@third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc:579 = {...})
    at ../../third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc:577

Raster 完成之后, CanvasResource 会通过 blink::CanvasResourceDispatcher::DispatchFrame 合成 CompositorFrame 然后提交。

CanvasResource 中取出 Raster 的结果,创建 viz::TransferableResource

#0  blink::CanvasResource::PrepareTransferableResource(viz::TransferableResource*, base::OnceCallback<void (scoped_refptr<blink::CanvasResource>&&, gpu::SyncToken const&, bool)>*, blink::MailboxSyncMode) (this=0x29c400278300,
    out_resource=0x7ffd4ecc43b0, out_callback=0x7f604004aec0, sync_mode=blink::kVerifiedSyncToken) at ../../third_party/blink/renderer/platform/graphics/canvas_resource.cc:155
#1  0x00007f60aa317287 in blink::CanvasResourceDispatcher::PrepareFrame (this=0x55d116e7c7a0, canvas_resource=scoped_refptr((blink::CanvasResourceRasterSharedImage *)0x29c400278300), Python Exception <class 'gdb.error'>: Cannot convert value to long.
commit_start_time=..., damage_rect=...,
    needs_vertical_flip=false, is_opaque=false, frame=0x7ffd4ecc47f0) at ../../third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc:286
#2  0x00007f60aa317865 in blink::CanvasResourceDispatcher::DispatchFrame (this=0x55d116e7c7a0, canvas_resource=scoped_refptr((blink::CanvasResourceRasterSharedImage *)0x29c400278300), Python Exception <class 'gdb.error'>: Cannot convert value to long.
commit_start_time=..., damage_rect=...,
    needs_vertical_flip=false, is_opaque=false) at ../../third_party/blink/renderer/platform/graphics/canvas_resource_dispatcher.cc:213
#3  0x00007f60b0a0e39c in blink::HTMLCanvasElement::PostFinalizeFrame (this=0x23e500127178) at ../../third_party/blink/renderer/core/html/canvas/html_canvas_element.cc:580
#4  0x00007f60b0a00934 in blink::CanvasRenderingContext::DidProcessTask (this=0x23e50058b288) at ../../third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc:103
#5  0x00007f60a2434801 in blink::CanvasRenderingContext2D::DidProcessTask (this=0x23e50058b288, pending_task=From PostTaskToMainThread()@third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc:579 = {...})
    at ../../third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc:577

创建 CompositorFrame 并提交资源:

#0  0x00007fffd952ea90 in blink::CanvasResourceDispatcher::DispatchFrame(scoped_refptr<blink::CanvasResource>&&, base::TimeTicks, SkIRect const&, bool, bool)@plt () from /home/keyou/dev/chromium2/src/out/v88/libblink_core.so
#1  0x00007fffd5e0e39c in blink::HTMLCanvasElement::PostFinalizeFrame (this=0x2e5f00123848) at ../../third_party/blink/renderer/core/html/canvas/html_canvas_element.cc:580
#2  0x00007fffd5e00934 in blink::CanvasRenderingContext::DidProcessTask (this=0x2e5f002180d8) at ../../third_party/blink/renderer/core/html/canvas/canvas_rendering_context.cc:103
#3  0x00007fffc7834801 in blink::CanvasRenderingContext2D::DidProcessTask (this=0x2e5f002180d8, pending_task=From PostTaskToMainThread()@third_party/blink/renderer/platform/widget/input/main_thread_event_queue.cc:579 = {...})
    at ../../third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc:577
#4  0x00007ffff0ce9417 in base::sequence_manager::internal::SequenceManagerImpl::NotifyDidProcessTask (this=0x7fff1c003910, executing_task=0x7fff1c0beda0, time_after_task=0x7fff397fad38)
    at ../../base/task/sequence_manager/sequence_manager_impl.cc:910
#5  0x00007ffff0ce8b50 in base::sequence_manager::internal::SequenceManagerImpl::DidRunTask (this=0x7fff1c003910, lazy_now=...) at ../../base/task/sequence_manager/sequence_manager_impl.cc:683
#6  0x00007ffff0d249d2 in base::sequence_manager::internal::ThreadControllerImpl::DoWork (this=0x7fff1c003480, work_type=base::sequence_manager::internal::ThreadControllerImpl::WorkType::kImmediate)
    at ../../base/task/sequence_manager/thread_controller_impl.cc:225

总结一下低延迟模式下的流程:

  • 在有绘制的时候调用 DidDraw,在它内部注册任务完成回调到任务调度器;
  • 当上一个任务完成的时候,触发回调,调用 CanvasRenderingContext::DidProcessTask
  • 然后 Flush canvas,将 Canvas 的绘制结果进行 Raster 并保存在 blink::CanvasResource 中;
  • blink::CanvasResource 中取出 Raster 的结果并创建 viz::TransferableResource
  • 创建 CompositorFrame,将绘制操作记录为 viz::TextureDrawQuad,并添加对 viz::TransferableResource 资源的引用,然后提交 CompositorFrame;

至此,Canvas 的数据通过不同的方式都提交到了 viz,可以看到低延迟模式下省去了很多 blink 相关链路的流程,除了 Canvas 的绘制依然受 blink 调度之外,其他的 raster,submit 流程基本上脱离了 blink。虽然如此,这些流程依然和 blink 在同一个线程中,

Tips: single buffer

低延迟模式有更多其他的优化,这里没有展开,比如在创建 CanvasResourceProvider 的时候会尽可能的优先使用支持 single buffer 模式的类。

CanvasResourceProvider 类型 single buffer viz 直接使用
blink::CanvasResourceProvider(Shared)Bitmap no no
blink::CanvasResourceProviderSharedImage 由GPU特性决定 yes
blink::CanvasResourceProviderPassThrough yes yes
blink::CanvasResourceProviderSwapChain yes yes

桩点3: 合成+渲染

不管是正常模式还是低延迟模式提交的 CompositorFrame,viz 都会一视同仁,如果当前处于 OnBeginFrame 事件的 deadline 之前,则这些 CF 会在当前帧进行合成,否则会先缓存起来,等到下一帧/vsync 的来临,在那个时候才进行合成显示。

另外,在 viz 合成 CF 的时候,如果 CF 引用到的 GL 资源还没有 raster 完成,这个时候要怎么办呢?以前 raster 和上屏在同一个线程中,不需要考虑 raster 和上屏哪个优先的问题,在新架构下 raster 和上屏分开到了两个线程中,viz 的策略是怎样的呢?有怎样的回退策略呢?需要继续研究。

总结

Canvas 从开始绘制到上屏经过以下流程:

  • canvas 初始化,获取 CanvasRenderingContext;
  • js 调用绘制 API 进行绘制,绘制的结果被 cc::RecordPaintCanvas 录制下来,保存在 blink::CanvasResourceProvider 中的 cc::PaintRecord
  • 在普通 Canvas 模式下提交绘制结果:
    • blink 进入绘制流程,从 blink::Canvas2DLayerBridge 中获取 cc::TextureLayer;
    • 在提交到 cc compositor 线程之前,调用 cc::TextureLayer::Update 触发 cc::PaintRecord 的 Raster,使用 OOP-R 机制将 Raster 任务发送到 CrGpuMain 线程进行 Raster,返回引用 Raster 结果的 gpu::Mailbox
    • 然后用 Raster 的结果 gpu::Mailbox 创建 viz::TransferableResource 并存入 cc::TextureLayer 中进行提交;
    • cc::TextureLayer 和网页中的其他元素一起提交到 cc compositor 线程,在那里创建 viz::CompositorFrame 然后提交到 viz;
  • 在低延迟 Canvas 模式下提交绘制结果:
    • 当有绘制的时候注册 blink 线程任务结束回调;
    • 当前任务结束之后,触发 blink::CanvasRenderingContext::DidProcessTask
    • 然后 flush canvas,将 cc::PaintRecord 进行 Raster,使用 OOP-R 机制将 Raster 任务发送到 CrGpuMain 线程进行 Raster,返回引用 Raster 结果的 gpu::Mailbox;
    • 然后在 blink::CanvasResourceDispatcher::DispatchFrame 中创建 viz::CompositorFrame 包装 Canvas 的内容,并提交到 viz compositor 线程进行合成;
  • viz compositor 收到 CompositorFrame 之后等待合适的时机进行上屏;