Skip to content

Latest commit

 

History

History
89 lines (63 loc) · 4.32 KB

File metadata and controls

89 lines (63 loc) · 4.32 KB

保留层缓存

本文介绍 Runtime 层的保留层缓存。它是一项底层优化,不需要修改组件 API。

目标

脏区重绘可以缩小重绘区域,但不会减少该区域内需要重新提交的静态图元数量。

保留层缓存用于减少后一项成本:

静态子树图元 -> 离屏图层纹理
脏区重绘 -> 绘制已缓存的图层纹理 + 动态图元

这对 Gallery 一类复杂页面很有用,因为一次按钮 hover 可能与大量静态卡片、阴影和文本绘制段相交。

当前实现

当前实现有意采用保守策略:

  • Runtime 自动选择静态子树和相邻的稳定兄弟节点。
  • 两个及以上相邻且符合条件的兄弟节点,可以在不改变绘制顺序的前提下合并为一个保留绘制段图层。
  • 组件不需要主动启用,也不对外暴露缓存 API。
  • OpenGL 将每个保留层存储为纹理支持的 framebuffer。
  • Vulkan 将每个保留层存储为可采样的 color-attachment image 和 framebuffer,并使用预乘 alpha 合成。
  • 不支持保留层的后端会安全回退到普通图元重放。
  • Backdrop blur 和 dependent visual 子树不会被缓存。它们会切断兄弟节点绘制段,但前后的合格静态兄弟节点仍可分别形成独立绘制段。
  • 包含动画、交互、滚动、timer、frame callback、dirty key、image 或 SVG 的子树不会被缓存。
  • 子树的绘制成本和面积必须达到阈值才会进入缓存。
  • 候选内容必须连续稳定两帧,Runtime 才会创建图层纹理。

缓存键包含结构、绘制边界、绘制成本、DPI scale,以及会影响绘制结果的元素属性。

热路径优化

首版 MVP 上线后,Runtime 在渲染热路径中保留候选检查的时间过长。这对复杂静态页面影响不大,但会给主要由叶子图元组成的高动画负载 demo 带来额外开销。

当前流程如下:

  • layout() 或子树重建时,在 Element 上缓存静态阻断标记。
  • update()PaintBoundsInstance 中缓存子树当前是否存在活动动画。
  • render() 只读取这些缓存标记,并跳过仅包含叶子节点的保留层探测。

这样既能让优化保持在底层,又能避免每帧重复递归扫描子树。

渲染流程

渲染脏区
  遍历已排序的子节点
    收集每一段相邻且符合条件的稳定兄弟节点
    如果绘制段至少包含两个兄弟节点,并通过合并后的成本/面积检查:
      使用或创建一个保留绘制段图层
    否则逐个处理子节点:
      如果子树已有有效保留层:
        在 dirty/scissor 裁剪下绘制图层纹理
      否则,如果子树是缓存候选:
        创建保留层,并在本帧正常绘制该子树
      否则:
        正常绘制子树

创建单个子树图层或兄弟节点绘制段图层时,都会在该次创建过程中禁用嵌套保留层复用,避免出现意外的嵌套 framebuffer 状态。

新建或重建的图层不会在创建它的同一帧中被采样。Runtime 会先将图层标记为有效,让子树或兄弟节点绘制段在本帧继续走普通图元路径,并请求下一帧完整绘制。后续帧会从稳定状态同时接管 render cache 和保留层;此后,未变化的静态内容会继续命中保留层缓存,带 transition 能力的静态 UI 也不会因此永久禁用缓存。

统计字段

窗口标题中的渲染统计包含:

Layer H/M/D/Re
  • H:保留层缓存命中次数。
  • M:缓存未命中或候选内容不稳定的次数。
  • D:图层纹理绘制次数。
  • Re:图层纹理重建次数。

复杂静态页面上的正常按钮交互,应表现为较高的 H、较低的 Re,以及更少的图元绘制次数。

当前限制

  • 兄弟节点绘制段只合并相邻且符合条件的子节点;阻断节点会切开绘制段,Runtime 不会跨过它们重新排序或合并。
  • 为保证正确性,当前不会缓存继承了活动 transform 的内容。
  • Backdrop blur 需要采样已有 framebuffer 内容,因此不会进入缓存。
  • 它不是完整的 retained scene graph。Rect 批处理是独立的后端优化,并非由该缓存提供的跨图元通用 batch renderer。
  • Vulkan 会尽可能在 swapchain 重建期间保留图层纹理,并延迟重建依赖 render pass 的 framebuffer。