G4.0源码解析(二)

源码解读

RenderingService中定义了所有生命周期钩子,如下:


  hooks = {
    /**
     * called before any frame rendered
     */
    init: new AsyncParallelHook<[]>(),
    /**
     * only dirty object which has sth changed will be rendered
     */
    dirtycheck: new SyncWaterfallHook<[DisplayObject | null]>(['object']),
    /**
     * do culling
     */
    cull: new SyncWaterfallHook<[DisplayObject | null, ICamera]>([
      'object',
      'camera',
    ]),
    /**
     * called at beginning of each frame, won't get called if nothing to re-render
     */
    beginFrame: new SyncHook<[]>([]),
    /**
     * called before every dirty object get rendered
     */
    beforeRender: new SyncHook<[DisplayObject]>(['objectToRender']),
    /**
     * called when every dirty object rendering even it's culled
     */
    render: new SyncHook<[DisplayObject]>(['objectToRender']),
    /**
     * called after every dirty object get rendered
     */
    afterRender: new SyncHook<[DisplayObject]>(['objectToRender']),
    endFrame: new SyncHook<[]>([]),
    destroy: new SyncHook<[]>([]),
    /**
     * use async but faster method such as GPU-based picking in `g-plugin-device-renderer`
     */
    pick: new AsyncSeriesWaterfallHook<[PickingResult], PickingResult>([
      'result',
    ]),

    /**
     * Unsafe but sync version of pick.
     */
    pickSync: new SyncWaterfallHook<[PickingResult], PickingResult>(['result']),
    /**
     * used in event system
     */
    pointerDown: new SyncHook<[InteractivePointerEvent]>(['event']),
    pointerUp: new SyncHook<[InteractivePointerEvent]>(['event']),
    pointerMove: new SyncHook<[InteractivePointerEvent]>(['event']),
    pointerOut: new SyncHook<[InteractivePointerEvent]>(['event']),
    pointerOver: new SyncHook<[InteractivePointerEvent]>(['event']),
    pointerWheel: new SyncHook<[InteractivePointerEvent]>(['event']),
    pointerCancel: new SyncHook<[InteractivePointerEvent]>(['event']),
  };

render和renderDisplayObject是这个文件中两个核心的方法,render方法通过tapable的生命周期把渲染过程所有要做的事情串联起来,在这里我们称之为渲染管线。

export class RenderingService {
  // 该方法执行了整个渲染过程
  // 其中调用了生命周期钩子(插件中会注册钩子),来完成渲染管线
  render(canvasConfig: Partial<CanvasConfig>) {
    this.stats.total = 0;
    this.stats.rendered = 0;
    this.zIndexCounter = 0;

    const { renderingContext } = this.context;

    // 计算并同步图形矩阵
    this.globalRuntime.sceneGraphService.syncHierarchy(renderingContext.root);
    // 触发BOUNDS_CHANGED事件
    this.globalRuntime.sceneGraphService.triggerPendingEvents();

    if (renderingContext.renderReasons.size && this.inited) {
      // 批量渲染根节点下所有图形对象
      this.renderDisplayObject(
        renderingContext.root,
        canvasConfig,
        renderingContext,
      );

      // 执行所有注册的beginFrame钩子(全局搜索beginFrame.tap可查到注册的位置)
      this.hooks.beginFrame.call();

      // renderListCurrentFrame用来存储当前帧所有要重新渲染的图形对象
      // 存储过程在renderDisplayObject中完成
      renderingContext.renderListCurrentFrame.forEach((object) => {
      	// 执行所有注册的beforeRender钩子
        this.hooks.beforeRender.call(object);
      	// 执行所有注册的render钩子
        this.hooks.render.call(object);
      	// 执行所有注册的afterRender钩子
        this.hooks.afterRender.call(object);
      });

      // 执行所有注册的endFrame钩子
      this.hooks.endFrame.call();
      // 清空脏图形对象栈
      renderingContext.renderListCurrentFrame = [];
      renderingContext.renderReasons.clear();
    }

    // console.log('stats', this.stats);
  }

  // 批量渲染图形的方法
  private renderDisplayObject(
    displayObject: DisplayObject,
    canvasConfig: Partial<CanvasConfig>,
    renderingContext: RenderingContext,
  ) {
    const { enableDirtyCheck, enableCulling } =
      canvasConfig.renderer.getConfig();
    // 批量更新样式属性,相当于konva的setAttr
    // *** 这里涉及到更新包围盒的处理逻辑,具体代码在styleValueRegistry》processProperties中执行
    this.globalRuntime.styleValueRegistry.recalc(displayObject);

    // TODO: relayout

    // 获取脏节点
    const objectChanged = enableDirtyCheck
      ? this.hooks.dirtycheck.call(displayObject)
      : displayObject;
    if (objectChanged) {
      // 做一次剔除过滤
      const objectToRender = enableCulling
        ? this.hooks.cull.call(objectChanged, this.context.camera)
        : objectChanged;

      // 要渲染的图形放到renderListCurrentFrame栈中
      if (objectToRender) {
        this.stats.rendered++;
        renderingContext.renderListCurrentFrame.push(objectToRender);
      }
    }

    // 移除渲染脏标记
    displayObject.renderable.dirty = false;
    displayObject.sortable.renderOrder = this.zIndexCounter++;

    this.stats.total++;

    // sort is very expensive, use cached result if posible
    const sortable = displayObject.sortable;
    let renderOrderChanged = false;
    // 按先后渲染顺序排序
    if (sortable.dirty) {
      sortable.sorted = displayObject.childNodes.slice().sort(sortByZIndex);
      renderOrderChanged = true;
      sortable.dirty = false;
    }

    // recursive rendering its children
    (sortable.sorted || displayObject.childNodes).forEach(
      (child: DisplayObject) => {
        // 递归渲染子节点
        this.renderDisplayObject(child, canvasConfig, renderingContext);
      },
    );

    // 触发RENDER_ORDER_CHANGED事件
    if (renderOrderChanged) {
      displayObject.forEach((child: DisplayObject) => {
        this.renderOrderChangedEvent.target = child;
        this.renderOrderChangedEvent.detail = {
          renderOrder: child.sortable.renderOrder,
        };
        child.ownerDocument.defaultView.dispatchEvent(
          this.renderOrderChangedEvent,
          true,
        );
      });
    }
  }
}

接下来我们继续看每个生命周期钩子都有哪些插件注册以及做了什么

目前存在5个钩子(beginFrame、beforeRender、render、afterRender、endFrame),其中beforeRender、render、afterRender是每个图形对象触发render前后要做的事情,而beginFrame、endFrame是当前帧这一批脏对象渲染前后的钩子,具体渲染的操作在endFrame中执行。

beginFrame

beginFrame钩子与2D相关的只有CanvasRenderPlugin中

// 注册beginFrame钩子
renderingService.hooks.beginFrame.tap(CanvasRendererPlugin.tag, () => {
  const context = contextService.getContext();
  const dpr = contextService.getDPR();
  const { width, height } = config;
  const { dirtyObjectNumThreshold, dirtyObjectRatioThreshold } =
    this.canvasRendererPluginOptions;

  // total是记录当前帧需要重新渲染的图形数量(超总量的80%直接进行画布重绘)
  const { total, rendered } = renderingService.getStats();
  const ratio = rendered / total;

  // 大于渲染数量阙值的直接全屏重绘 (500个或 0.8比例)
  this.clearFullScreen =
    renderingService.disableDirtyRectangleRendering() ||
    (rendered > dirtyObjectNumThreshold &&
     ratio > dirtyObjectRatioThreshold);

  // 画布清屏
  if (context) {
    context.resetTransform();
    if (this.clearFullScreen) {
      this.clearRect(
        context,
        0,
        0,
        width * dpr,
        height * dpr,
        config.background,
      );
    }
  }
});

beforeRender

目前没有地方注册

render

render钩子与2D相关在CanvasRenderPlugin中

renderingService.hooks.render.tap(
  CanvasRendererPlugin.tag,
  (object: DisplayObject) => {
    if (!this.clearFullScreen) {
      // 放入渲染队列中
      this.renderQueue.push(object);
    }
  },
);

在阅读源码的过程中,发现上面代码的renderQueue和renderListCurrentFrame两个参数的意义并没什么不同,都是作为当前帧要渲染的图形队列,可能区别在于renderListCurrentFrame是相对RenderingService环境的,而renderQueue是相对CanvasRenderPlugin环境的。

afterRender

renderingService.hooks.afterRender.tap(CullingPlugin.tag, (object: DisplayObject) => {
  object.cullable.visibilityPlaneMask = -1;
});

endFrame

整个流程中最重要的绘制过程发生在endFrame中

最后来看CanvasRendererPlugin::renderDisplayObject的内部处理过程

流程总结

渲染过程:

beginFrame -> (beforeRender -> render -> afterRender) -> endFrame

括号内为每个节点的渲染过程

局部重绘流程(endFrame):

  1. mergeDirtyAABBs 将每个重绘图形的包围盒合并
  2. removedRBushNodeAABBs.map(…) 创建包围盒
  3. safeMergeAABB 合并所有脏矩形形成新的包围盒
  4. convertAABB2Rect 转换为脏矩形对象信息
  5. Math浮点清除计算
  6. clip 脏矩形裁切
  7. setTransform 坐标系移动
  8. searchDirtyObjects 寻找区域内所有需要重绘图形
  9. sort 根据z-index排序
  10. renderDisplayObject 遍历渲染队列执行图形渲染流程
  11. 存储脏矩形包围盒
  12. 清空渲染队列、恢复状态队列

原文链接:https://juejin.cn/post/7345357796634607643 作者:愚公移山w

(0)
上一篇 2024年3月13日 下午4:52
下一篇 2024年3月13日 下午5:03

相关推荐

发表评论

登录后才能评论