模块4: 渲染管线与多后端架构
理解帧循环、脏矩形优化与多渲染器抽象
1. 渲染流程概览
@antv/g 的渲染由 RenderingService 驱动,每一帧按以下流水线执行:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────┐ │ BeginFrame │ -> │ Dirty Check │ -> │ Sort & Cull │ -> │ Render │ │ 初始化帧 │ │ 脏矩形合并 │ │ Z排序+裁剪 │ │ 绘制图形 │ └─────────────┘ └──────────────┘ └─────────────┘ └──────────┘ │ │ │ ┌──────────────┐ │ └──────────────────│ EndFrame │<─────────────────────┘ │ 提交到屏幕 │ └──────────────┘
2. RenderingService 核心服务
位于 packages/g-lite/src/services/RenderingService.ts,是渲染引擎的核心调度器。
// RenderingService 核心职责 class RenderingService { // Hook 系统 - 插件可注册钩子 hooks = { init: new SyncHook(), destroy: new SyncHook(), beginFrame: new SyncHook(), beforeRender: new SyncHook(), render: new SyncHook(), afterRender: new SyncHook(), endFrame: new SyncHook(), }; // 帧循环 render(canvasConfig, renderingContext) { // 1. beginFrame // 2. 收集脏对象 // 3. 排序 (z-index) // 4. 裁剪 (culling) // 5. 渲染可见对象 // 6. endFrame } }
3. 脏矩形 (Dirty Rectangle) 优化
当图形属性变化时,@antv/g 不会重绘整个画布,而是只重绘变化区域:
- 图形属性变化时标记为 "脏" (dirty)
- 计算脏图形的包围盒 (AABB)
- 合并重叠的脏矩形
- 只清除和重绘脏矩形区域内的图形
脏矩形优化使得 Canvas2D 渲染器在大量静态图形+少量动态图形的场景下性能提升数十倍。
4. 视锥裁剪 (Culling)
不在视口内的图形会被跳过渲染:
- Cullable 组件 - 每个 DisplayObject 都有 Cullable 组件标记是否被裁剪
- 策略 - 通过 AABB (包围盒) 与视口做碰撞检测
- 3D 场景 - 使用 Frustum (视锥体) 裁剪
// Cullable 组件 (g-lite/src/components/Cullable.ts) class Cullable { visible: boolean; // 是否通过裁剪测试 strategy: Strategy; // 裁剪策略 }
5. Z-Index 排序
渲染顺序由 Sortable 组件控制:
// 设置 z-index circle.style.zIndex = 10; rect.style.zIndex = 5; // circle 会渲染在 rect 上方 // 同级同 zIndex 时按添加顺序渲染
6. 多渲染器后端
| 渲染器 | 包名 | 技术 | 适用场景 |
|---|---|---|---|
| Canvas2D | g-canvas | CanvasRenderingContext2D | 通用 2D,兼容性最好 |
| SVG | g-svg | SVG DOM | 矢量输出、无障碍、SEO |
| WebGL | g-webgl | WebGLRenderingContext | 大规模数据、3D、GPU 加速 |
| WebGPU | g-webgpu | GPUDevice API | 下一代 GPU,最高性能 |
| CanvasKit | g-canvaskit | Skia WASM | 高质量文本/路径渲染 |
7. AbstractRenderer 抽象层
所有渲染器都实现 AbstractRenderer 接口,通过插件组合构建能力:
// 源码: packages/g-lite/src/AbstractRenderer.ts abstract class AbstractRenderer { // 插件列表 private plugins: RendererPlugin[] = []; // 注册插件 registerPlugin(plugin: RendererPlugin) { ... } // 获取所有插件 getPlugins(): RendererPlugin[] { ... } } // 切换渲染器 import { Renderer as CanvasRenderer } from '@antv/g-canvas'; import { Renderer as SVGRenderer } from '@antv/g-svg'; // 只需更换 renderer 实例,业务代码无需修改 const canvas = new Canvas({ renderer: new CanvasRenderer(), // 或 new SVGRenderer() });
8. Hook 系统
渲染流水线的每个阶段都暴露 Hook,插件可以注入自定义逻辑(借鉴 Webpack Tapable):
// 插件注册 Hook renderingService.hooks.beforeRender.tap('MyPlugin', () => { // 每帧渲染前执行 }); renderingService.hooks.afterRender.tap('MyPlugin', () => { // 每帧渲染后执行 }); // 画布级事件 canvas.addEventListener('beforerender', () => {}); canvas.addEventListener('afterrender', () => {}); canvas.addEventListener('rerender', () => {});
9. 渲染器各阶段插件
每个渲染器通过插件组合完成完整的渲染能力:
| 插件 | 阶段 | 职责 |
|---|---|---|
| ContextRegisterPlugin | 初始化 | 创建渲染上下文 |
| DirtyCheckPlugin | BeginFrame | 收集脏对象 |
| CullingPlugin | Cull | 视锥/视口裁剪 |
| CanvasRendererPlugin | Render | Canvas2D 图形绘制 |
| CanvasPickerPlugin | Pick | Canvas2D 拾取 |
| EventPlugin | Event | 事件分发 |