Files
Renderive/a.md
T
2026-08-12 16:57:03 +08:00

28 KiB
Raw Blame History

下面这份直接作为 Codex 的完整执行计划使用。所有条目都按强制要求执行,不保留旧实现,不增加兼容层,不引入独立 PerfTrace 库。

Renderive Kernel Render DAG 完整改造执行计划

0. 总目标

将 Plot 内部渲染执行统一改造成:

Renderable 状态
    +
Renderable 数据依赖
    +
Renderable 图层依赖
    +
prepare_buffer 状态
    +
paint_buffer 状态
    +
Renderable 内部任务 DAG
            │
            ▼
      render_plan compiler
            │
            ▼
       ONE Render DAG
            │
            ▼
       ONE Taskflow
            │
            ▼
        Frame 执行
            │
            ├── 普通运行:不记录详细性能
            │
            └── Capture:直接写 Frame execution slots
            │
            ▼
      三缓冲完成交换
            │
            ▼
   immutable frame_snapshot
            │
      ┌─────┴────────┐
      ▼              ▼
frame_analysis   node_statistics
      │              │
      └──────┬───────┘
             ▼
           后端 API
             │
             ▼
前端 DAG + Worker Timeline + 多帧统计

Kernel Render DAG 必须同时成为:

执行依赖的唯一事实来源
Taskflow 编译的唯一事实来源
单帧性能分析的唯一事实来源
前端 DAG 展示的唯一事实来源

1. 锁死 Renderable 三阶段语义

Renderable 的执行语义必须明确分成:

prepare
   ↓
paint
   ↓
composite

这三个阶段不是全局阶段,不允许建立全局 barrier。

它们是每个 Renderable 以及 Renderable 内部任务自己的局部依赖关系。

必须严格执行以下语义。

prepare

prepare 必须完成:

读取自身业务数据
读取其他 Renderable 提供的数据
处理所有跨 Renderable dependency
坐标转换
数据转换
几何计算
可见范围计算
裁剪信息计算
布局结果准备
分片
索引生成
raster 前置数据生成
paint 所需全部输入生成

prepare 最终必须只产生当前 Renderable 自己的:

prepare_buffer

paint

paint 必须满足:

禁止读取其他 Renderable
禁止解析 dependency
禁止查询其他 Renderable 当前状态
禁止重新执行跨 Renderable 数据计算
禁止临时从 Axis/Series/其他对象读取绘制输入

paint 只能读取:

自身 prepare_buffer
自身 paint-only 配置
自身 paint target

paint 只能写:

自身 paint_buffer

必须满足这个验证原则:

将其他 Renderable 全部从 paint 的可访问环境中移除,只保留当前 Renderable 的 prepare_bufferpaint 仍必须能够完整正确执行。

composite

composite 必须:

只读取已经完成的 paint_buffer
按照 layer 关系完成图层组合
不读取 Renderable 原始业务数据
不执行 prepare 计算

2. 建立强类型 prepare_buffer

每一个需要 prepare 数据的 Renderable 必须拥有自己的强类型 Prepare Buffer。

禁止实现:

std::unordered_map<std::string, std::any>

禁止通过通用字符串字段传递 Renderable 绘制输入。

例如:

Axis
└── axis_prepare_buffer

Spectrum
└── spectrum_prepare_buffer

Waterfall
└── waterfall_prepare_buffer

Afterglow
└── afterglow_prepare_buffer

具体字段由各 Renderable 自己定义。

例如 Waterfall

waterfall_prepare_buffer
├── transformed_data
├── visible_range
├── partition_description
├── chunk_metadata
├── raster_parameters
└── 其他 paint 必需数据

Paint 不得绕过这些结构重新访问数据源。


3. 审计并拆除所有混合 Prepare/Paint 实现

逐个审计所有 Renderable。

重点处理:

Waterfall
Spectrum
Afterglow

同时扫描其他 Renderable 中:

paint()
build_paint_task_graph()
render()
raster()

内部是否仍包含:

数据变换
跨 Renderable 查询
坐标计算
布局计算
分片
输入数据准备

全部迁移到 Prepare。

原来的混合实现直接删除。

不得保留:

旧 paint path
+
新 prepare path

两套兼容逻辑。

绘制结果必须保持一致。


4. Prepare Buffer 与 Paint Buffer 完全独立缓存

每个 Renderable 必须独立维护:

prepare_buffer validity

paint_buffer validity

不得继续使用一个 Renderable 整体缓存状态同时代表两者。

缓存关系必须严格满足:

prepare invalid
    ↓
paint 必须 invalid

但:

paint invalid

不得无条件导致:

prepare invalid

5. 建立独立 Prepare/Paint Revision

现有 revision/cache revision 体系必须拆到能够分别回答:

当前 prepare_buffer 是否仍有效

当前 paint_buffer 是否仍有效

至少在语义上存在:

prepare input revision
prepare output revision

paint input revision
paint output revision

具体字段名称保持现有项目命名风格。

Prepare 有效性必须依赖

自身数据 revision
依赖 Renderable 提供的数据 revision
会影响 prepare 的尺寸/范围/坐标参数
prepare-only properties

Paint 有效性必须依赖

当前 prepare_buffer revision
paint-only properties
影响 raster/output 的尺寸和配置

例如:

仅颜色变化

必须能够得到:

prepare cache = valid
paint cache = invalid

例如:

依赖 Axis 范围变化

必须得到:

prepare cache = invalid
paint cache = invalid

6. 修正 Dependency Invalidations

跨 Renderable dependency 必须只描述 Prepare 数据依赖。

例如:

Series depends on Axis

必须编译成:

Axis Prepare
      ↓
Series Prepare

不得编译成:

Axis Paint
      ↓
Series Prepare

不得继续使用:

整个 Renderable A
      ↓
整个 Renderable B

这种粗粒度执行约束。

Prepare 数据变化向 dependency 后继传播:

A prepare invalid
      ↓
B prepare invalid
      ↓
B paint invalid

Paint-only 状态变化不得传播到其他 Renderable 的 Prepare

A paint invalid

不得导致:

B prepare invalid

7. 建立 Renderable DAG Builder 接口

Renderable 必须正式提供建图能力。

保留运行时多态。

建立与现有类体系匹配的虚函数,语义必须等价于:

virtual void build_prepare_graph(render_graph_builder &builder);
virtual void build_paint_graph(render_graph_builder &builder);

两个函数必须写入同一个:

render_graph_builder

不得分别生成两张最终 Graph。

简单 Renderable

Prepare
   ↓
Paint

复杂 Renderable 可以展开自己的内部 DAG。


8. Waterfall 必须展开真实内部 DAG

Waterfall 分片必须全部成为正式 Node。

不得把分片隐藏为一个 Waterfall Task 内部的匿名并行执行。

例如:

Waterfall Prepare
       │
       ├── Chunk 0 Prepare -> Chunk 0 Paint ─┐
       ├── Chunk 1 Prepare -> Chunk 1 Paint ─┤
       ├── Chunk 2 Prepare -> Chunk 2 Paint ─┤
       └── Chunk N Prepare -> Chunk N Paint ─┤
                                             ▼
                                           Merge

如果某些分片只需要单独的 Prepare 或单独 Paint,则按真实依赖建立节点。

不得为了统一结构制造不存在的任务。

动态 Chunk 必须拥有稳定逻辑 Node ID。


9. prepare -> paint 只能是局部依赖

禁止建立:

所有 Prepare 完成
        ↓
所有 Paint 开始

这种全局 Barrier。

必须形成流水式 DAG

Prepare A -> Paint A

Prepare B -------------> Paint B

Prepare C -> Paint C

因此实际运行允许:

Prepare A 完成
      ↓
Paint A 开始

同时:

Prepare B
Prepare C

仍在执行。

这条必须通过并发测试明确验证。


10. Paint 必须最大程度并行

所有 Renderable 的 Paint 必须只写自己的独立:

paint_buffer

因此:

Axis Paint
Series Paint
Waterfall Paint
Legend Paint

在不存在真实 Paint 依赖的情况下必须允许完全并行。

不得直接将:

layer_node_

转换为:

Paint A -> Paint B

从而人为串行化 Paint。


11. Layer 关系必须编译到 Composite

图层顺序必须主要落实到 Composite DAG。

例如:

Background Paint
Series Paint
Axis Paint

可以并行。

最终:

Background Paint
        ↓
Composite Background
        │
Series Paint
        └────> Composite Series
                       │
Axis Paint
                       └────> Composite Axis

具体依赖按照现有 Layer Graph 的真实顺序生成。

必须保持最终像素结果与原实现一致。


12. 最终只有一张 Render DAG

最终 Compiler 输入必须为:

Renderable internal graph
+
dependency_node_
+
layer_node_
+
prepare_buffer validity
+
paint_buffer validity
+
Renderable 当前状态

输出必须只有:

render_plan
└── graph

Graph 内统一存在:

prepare node
paint node
composite node

不得生成:

prepare_graph
paint_graph

两张最终执行图。


13. Buffer 必须在建图阶段裁剪任务

缓存命中的工作必须直接从本帧有效 DAG 中删除。

禁止继续生成:

cache_hit

空 Task。

完整路径:

Prepare
   ↓
Paint
   ↓
Composite

Prepare Cache Hit

本帧必须生成:

Paint
  ↓
Composite

Paint 直接读取已有 Prepare Buffer。

Paint Cache Hit

本帧必须生成:

Composite

Composite 直接读取已有 Paint Buffer。

Prepare/Paint 均失效

生成:

Prepare
   ↓
Paint
   ↓
Composite

14. 建立稳定 node_id

Kernel/Plot 必须统一分配 DAG Node 身份。

所有逻辑任务必须拥有:

node_id

包括:

Prepare
Paint
Composite
Waterfall dynamic chunks
内部 merge
内部 geometry task

规则严格保持:

逻辑任务身份不变
=> node_id 不变
逻辑任务语义改变
=> 分配新 node_id
node 退休
=> node_id 永不复用

15. Node 必须带最小元数据

每个 Render Node 至少包含:

node_id
owner/renderable id
name
kind
execution_index

kind 至少有:

prepare
paint
composite

不提前添加无实际使用场景的分类。


16. 建立单一 render_plan

建立:

render_plan
├── version
└── graph
    ├── nodes
    └── edges

render_plan 必须代表:

当前 Plot 在当前 Renderable、Dependency、Layer、Prepare Cache、Paint Cache 状态下真正会执行的完整 Render DAG。


17. 建立 render_plan_version

以下任一情况发生时必须产生新 Version:

active node set 改变
Node 增加
Node 删除
Edge 改变
Waterfall 分片拓扑变化
dependency topology 变化
layer/composite topology 变化
prepare cache 裁剪结果变化
paint cache 裁剪结果变化

仅以下变化不得产生新 Version

Task 实际耗时变化
Task Worker 改变
调度顺序不同
实际并行程度不同

18. Render Plan 编译成一个 Taskflow

必须满足:

1 Render Node
=
1 Taskflow Task

以及:

1 Render Edge
=
1 Taskflow dependency

最终:

render_plan.graph
      ↓
Taskflow
      ↓
Taskflow Executor

不得生成两个前后 Taskflow。


19. Taskflow 只遵守偏序关系

DAG Edge 只表示:

B 不能在 A 完成以前执行

没有 Edge 的 Node 必须交由 Taskflow 自由调度。

不得写:

固定 Worker
固定执行次序
手工串行队列

除非任务本身存在真实依赖。


20. 性能采集必须请求式开启

正常运行:

capture = disabled

不得常驻记录完整 Node 时序。

必须实现:

capture_next_frame()

以及:

capture_frames(count)

例如:

capture_frames(20)

必须从后续完整 Render Frame 开始连续捕获 20 帧,然后自动停止。


21. Capture Controller 必须保持极简

Plot/Kernel 内维护:

capture enabled
remaining frame count
capture session id

使用必要的原子状态实现。

不得增加:

Event system
Record system
MPSC performance queue
TLS trace system
collector thread

22. Frame 必须持有 Capture 状态

每个正在 Render 的 Frame 在开始执行时必须确定:

capture_this_frame

该状态在本 Frame 生命周期内保持不变。

不得执行到一半再打开 Capture。


23. Frame 性能数据使用预分配 List

不得在 Task 热路径使用:

map<node_id, execution>
unordered_map<node_id, execution>

Render Plan 编译时给每个 Node 分配:

execution_index

例如:

Node #100 -> slot 0
Node #101 -> slot 1
Node #203 -> slot 2

Capture Frame 创建:

node_execution[]

长度直接根据 Plan 确定。


24. node_execution 必须记录真实时序

至少记录:

node_id
start_time
end_time
worker_id
status

duration 不重复存储:

duration = end_time - start_time

时间源必须保持单调并能在 Worker 间比较。


25. Taskflow Worker 只能写自己的 Execution Slot

编译 Taskflow 时将:

Task

直接绑定:

execution_index

Capture 开启:

execution.start = now
execution.worker = worker

执行真实任务

execution.end = now
execution.status = complete

不同 Task 不得写同一个 slot。

热路径不得用 mutex 保护整个性能数据结构。


26. Capture 关闭时不得采集详细时间

Capture 关闭时 Task 必须直接进入正常执行路径。

不得仍然执行:

now()
start record
end record
statistics aggregation

只保留最小 capture branch。


27. Renderable 自定义指标直接写 Execution Slot

Renderable 实现允许对自己当前 Node 补充固定性能指标。

例如 Waterfall Prepare

input_count
chunk_size
prepared_cells

Paint

pixel_count
primitive_count

不得在 Worker 热路径实现:

unordered_map<string, any>

使用固定结构或固定 metric slot。


28. Frame Snapshot 必须绑定 render_plan_version

Capture Frame 至少包含:

frame_id
render_plan_version
render_start
render_end
node_execution[]

不再创建:

Trace
Span
Record
Event

等额外运行层。

一帧自己的执行数据就是唯一运行快照。


29. 三缓冲交换必须成为 Snapshot 发布边界

运行阶段:

working frame
     │
Taskflow 写 execution slots
     │
render complete
     │
triple-buffer exchange
     │
completed frame

进入 Completed 后:

frame performance

必须不可再修改。

外部读取 Completed Frame 时不得需要锁住 Taskflow Worker。


30. 建立 Capture Repository

三缓冲不负责保存历史性能帧。

Capture 请求的 Completed Frame 必须在冷路径复制/移动到:

capture_repository

结构:

capture_session
├── session_id
├── requested_count
├── captured_count
├── active
└── frame_snapshots[]

抓取达到请求数量以后必须自动结束 Session。


31. 保存 Render Plan 历史结构

必须能够:

render_plan_version
      ↓
找到当时对应的 Render DAG

连续 20 帧过程中允许:

Frame 100 -> Plan 30
Frame 101 -> Plan 30
Frame 102 -> Plan 31
Frame 103 -> Plan 32

每帧必须绑定自己真实执行的 Plan Version。

不得假定一个 Capture Session 只有一个 DAG Version。


32. 实现 frame_analysis

所有重分析必须发生在 Render 热路径之外。

输入:

render_plan
+
frame_snapshot

输出:

frame_analysis

至少计算:

total_render_duration
node_duration
node_start_offset
node_end_offset
dependency_ready_time
scheduler_wait
critical_path
critical_path_duration
critical_path_contribution
total_work_duration
peak_parallelism
average_parallelism
parallel_overlap
worker utilization
bottleneck nodes

33. Scheduler Wait 必须从 DAG 和实际时间推导

对于:

A ─┐
   ├──> C
B ─┘

计算:

dependency_ready(C)
=
max(A.end, B.end)

然后:

scheduler_wait(C)
=
C.start - dependency_ready(C)

Source Node

dependency_ready
=
render_start

不得为了获得 Wait 再增加事件采集系统。


34. 正确处理并行节点的耗时占比

不得简单计算:

node_duration / sum(all node durations)

作为“帧耗时占比”。

必须明确输出不同指标:

work_contribution
critical_path_contribution

并行节点允许 Work Duration 总和远大于 Wall Duration。


35. 实现 node_statistics

按稳定:

node_id

聚合多帧数据。

至少维护:

execution_count
average
moving_average
p50
p95
p99
min
max
critical_path_frequency
average_scheduler_wait

短生命周期 Waterfall Node 同样统计。


36. 支持按 render_plan_version 聚合

必须支持:

Plan v30
├── frame...
└── statistics

Plan v31
├── frame...
└── statistics

用于直接比较 DAG 编译策略变化后的性能。

例如:

Plan v30 P95 render = 8.4 ms
Plan v31 P95 render = 6.1 ms

37. 后端增加 Capture API

按照当前后端/Adminive API 风格接入。

必须提供等价能力:

请求下一帧
请求连续 N 帧
查看 capture 状态
查看 capture session
查看 captured frame 列表
读取单帧 snapshot
读取单帧 analysis
读取 render plan topology
读取 node statistics
读取 plan version statistics

禁止为了这些 API 重新实时读取 Worker 数据。

接口只读取:

completed snapshots
analysis
statistics

38. 前端增加 Performance Capture 页面

前端必须提供:

Capture next frame

Capture N frames

默认 N 可输入:

20

Capture 时显示:

captured 7 / 20

完成后显示捕获到的 Frame 列表。


39. Frame 列表必须显示 Version

例如:

Frame #10381    Plan v57    6.81 ms
Frame #10382    Plan v57    7.02 ms
Frame #10383    Plan v58    5.93 ms

点击 Frame 后必须加载:

frame_snapshot
+
对应 render_plan_version
+
frame_analysis

40. 前端主视图必须是一张完整 Render DAG

不得拆成:

Prepare DAG
Paint DAG

两个主图。

必须显示完整:

Prepare
   ↓
Paint
   ↓
Composite

以及跨 Renderable 数据依赖。

例如:

Axis Prepare ────────────────┐
      │                      │
      ▼                      ▼
Axis Paint              Series Prepare
      │                      │
      │                      ▼
      │                 Series Paint
      │                      │
      │                      ▼
      │                Composite Series
      │                      │
      └──────────────────────┼──> Composite Axis

Waterfall 内部 Chunk 必须可以展开。


41. 前端 DAG Node 必须按 kind 区分

至少区分:

prepare
paint
composite

节点上直接显示:

当前 duration
scheduler_wait
worker
critical contribution
historical p95

不得只有一个模糊的百分比字段。


42. 前端必须显示 Prepare/Paint Cache 独立状态

Renderable Detail 必须显示:

prepare cache: HIT/MISS
paint cache: HIT/MISS

例如:

Spectrum

prepare cache: HIT
paint cache: MISS

用户必须能够直接理解为什么本 Frame DAG 中:

Spectrum Prepare

不存在,而:

Spectrum Paint

存在。


43. 前端必须实现真实 Worker Timeline

同一个 Frame 必须能够按真实时间显示:

time ─────────────────────────────────────────>

Worker 0
[Axis Prepare][Axis Paint     ]

Worker 1
[Series Prepare      ][Series Paint]

Worker 2
[WF Prepare][Chunk0 Prep][Chunk0 Paint]

Worker 3
            [Chunk1 Prep][Chunk1 Paint]

Worker 4
                                  [Composite]

这张 Timeline 使用:

start_time
end_time
worker_id

直接生成。


44. DAG 与 Timeline 必须双向联动

点击 DAG Node

Chunk 7 Paint

必须在 Timeline 高亮它的真实执行区间。

点击 Timeline 区间:

Worker 3 / 2.3ms ~ 3.7ms

必须高亮对应 DAG Node。


45. 前端 Node Detail 必须展示当前与历史对比

例如:

Waterfall / Chunk 7 / Paint

current duration          1.42 ms
scheduler wait            0.18 ms
worker                    3

moving average            1.13 ms
p95                       1.67 ms
p99                       2.10 ms

critical contribution     12.4%
critical path frequency   14 / 20

Renderable 自定义指标同时显示。


46. 连续 N 帧必须提供统计视图

Capture 20 帧以后必须提供:

total render
avg
p50
p95
max

parallelism
average
peak

scheduler wait
average
p95

critical path
node frequency

不得只提供 20 个单帧链接。


47. 不同 Plan Version 必须支持直接比较

如果 Capture 中存在:

Plan v57
Plan v58

必须能够比较:

frame count
render avg
render p95
peak parallelism
average parallelism
scheduler wait
critical path nodes

并展示拓扑变化:

新增 node
删除 node
新增 edge
删除 edge
缓存裁剪变化

48. 测试执行顺序

所有阶段必须遵守:

先补测试
↓
确认测试失败
↓
修改实现
↓
相关测试通过
↓
全量测试通过
↓
再进入下一阶段

不得先整体改完再补测试。


49. 必须增加的核心测试

覆盖:

prepare 禁止依赖 paint 结果

paint 不访问其他 Renderable

prepare_buffer 独立有效

paint_buffer 独立有效

prepare invalid -> paint invalid

paint invalid 不反向 invalid prepare

dependency 只约束 prepare

prepare 完成后对应 paint 可立即执行

不存在全局 prepare barrier

无依赖 Renderable prepare 真并行

独立 paint_buffer 的 paint 真并行

layer 顺序只正确约束 composite

cache hit 节点真正不进入 DAG

node_id 跨 plan version 稳定

retired node_id 不复用

Waterfall 动态分片 node 正确

render_plan_version 正确变化

一张 DAG 正确编译一个 Taskflow

Capture off 不生成 execution timing

Capture next frame 只抓一帧

Capture 20 frames 正好抓 20 帧

多 worker 写不同 execution slot 无竞争

Frame A/B 不串 performance 数据

三缓冲交换以后 snapshot 不再变化

Plan version 与 Frame 正确绑定

scheduler_wait 推导正确

critical path 正确

并行时间重叠计算正确

前端 API 返回 Plan + Frame 完整数据

50. 并发与 Sanitizer 验证

必须执行:

高并发 Taskflow 测试
Waterfall 大量动态 Chunk
持续 Render
Capture 连续多帧
Capture 开关反复操作
Plan 高频变化
缓存高频命中/失效

并运行项目当前环境可用的:

ASan
UBSan
TSan

TSan 重点检查:

Frame execution slots
capture controller
triple-buffer publish
capture repository
plan history

51. 性能回归验证

必须单独比较:

Capture OFF

情况下改造前后的 Render 性能。

Capture OFF 不得因为性能分析系统引入明显:

锁竞争
动态内存分配
hash lookup
时间采样
队列 push
observer 聚合

Capture ON 的开销单独测量。


52. 清理旧性能统计路径

当 Frame Capture + node_statistics 完成以后,审计现有 Renderable Task Observer / Task Execution Snapshot / Rolling Statistics 等机制。

如果职责已经完全被新结构替换:

直接删除旧实现

不得保留双统计路径。

仅保留仍有独立语义、没有被新系统替代的统计能力。


53. 最终代码结构必须满足

Plot
│
├── Renderable graph relationships
│
├── render_plan compiler
│   └── ONE Render DAG
│
├── Taskflow executor
│
├── frame buffers
│   ├── prepare_buffer state
│   ├── paint_buffer state
│   └── optional performance snapshot
│
├── capture controller
│
├── capture repository
│
├── frame_analysis
│
└── node_statistics

前端:

Performance
│
├── Capture Control
├── Captured Frames
├── Render DAG
├── Worker Timeline
├── Node Detail
├── Multi-frame Statistics
└── Plan Version Comparison

54. 最终不可破坏的设计规则

这部分直接放到 Codex 任务最顶部:

所有跨 Renderable 数据访问必须在 prepare 完成。

paint 禁止访问其他 Renderable,只能消费自身 prepare_buffer

prepare_bufferpaint_buffer 必须是完全独立的两层缓存。

prepare 缓存失效必须使 paint 缓存失效;paint 缓存失效不得反向污染 prepare

Prepare、Paint、Composite 最终必须构成同一张 Render DAG。

禁止建立全局 Prepare→Paint Barrier。

某个 Prepare 完成后,只要其真实依赖已经满足,对应 Paint 必须立即允许进入 Taskflow 调度。

独立 paint_buffer 的 Paint 必须尽可能并行,Layer 顺序落实到 Composite。

缓存命中的工作必须在 Render DAG 构建阶段直接裁剪。

Kernel Render DAG 是执行、Taskflow 编译和性能分析的唯一拓扑事实来源。

性能时序完全绑定 Frame,不建立 Trace/Event/Record/MPSC 性能事件体系。

Capture 必须请求式开启,支持下一帧和连续 N 帧。

Capture 开启时 Taskflow Task 直接写 Frame 预分配 Execution Slot。

三缓冲交换完成后 Frame Snapshot 必须不可变。

性能分析必须在完成帧冷路径执行。

前端必须同时展示 Render DAG 偏序关系和本 Frame 的真实 Worker 时序。

不得触碰 git。

所有代码保持 K&R 风格,不增加无意义空行。

被替换的旧实现直接删除,不做兼容实现。

执行完成后的最终数据流必须严格为:

Renderable / Dependency / Layer / Cache
                 │
                 ▼
          render_plan compiler
                 │
                 ▼
          ONE Render DAG
                 │
                 ▼
             Taskflow
                 │
          ┌──────┴──────┐
          │             │
   Capture OFF      Capture ON
          │             │
      正常执行      Frame slots
                        │
                        ▼
                completed Frame
                        │
                        ▼
                triple-buffer publish
                        │
                        ▼
                immutable snapshot
                        │
             ┌──────────┴──────────┐
             ▼                     ▼
       frame_analysis        node_statistics
             │                     │
             └──────────┬──────────┘
                        ▼
                    Backend API
                        │
                        ▼
             DAG + Worker Timeline
             + Multi-frame Analysis
             + Plan Version Compare

这份计划直接从 Renderable 边界改造一直执行到最终前端性能分析页面。