cmake 引入 oneTBB

This commit is contained in:
2026-08-15 15:55:51 +08:00
parent 6655e8571b
commit 07c3937779
3 changed files with 29 additions and 773 deletions
+7 -3
View File
@@ -1,4 +1,5 @@
set(Renderive_Kernel_dependencies global::taskflow)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/rely.cmake)
set(Renderive_Kernel_dependencies global::taskflow render_kernel::oneTBB)
if (RENDERIVE_BUILD_TESTS)
set(Renderive_Kernel_test_targets)
list(APPEND Renderive_Kernel_dependencies global::GTest)
@@ -13,6 +14,7 @@ endif ()
rcl_load_dependency_environment(${Renderive_Kernel_dependencies})
find_package(Taskflow CONFIG REQUIRED)
find_package(Threads REQUIRED)
find_package(TBB CONFIG REQUIRED)
if (RENDERIVE_BUILD_TESTS)
find_package(GTest CONFIG REQUIRED)
endif ()
@@ -23,7 +25,9 @@ target_include_directories(Renderive_Kernel PUBLIC
"$<BUILD_INTERFACE:${Renderive_Kernel_source_dir}>"
)
target_compile_features(Renderive_Kernel PUBLIC cxx_std_20)
target_link_libraries(Renderive_Kernel PRIVATE Taskflow::Taskflow Threads::Threads)
target_link_libraries(Renderive_Kernel PRIVATE Taskflow::Taskflow)
target_link_libraries(Renderive_Kernel PRIVATE TBB::tbb)
target_link_libraries(Renderive_Kernel PRIVATE Threads::Threads)
if (MSVC)
target_compile_options(Renderive_Kernel PRIVATE /utf-8)
endif ()
@@ -53,7 +57,7 @@ if (RENDERIVE_BUILD_TESTS)
endforeach ()
add_custom_target(Renderive_Kernel_check
COMMAND "${CMAKE_CTEST_COMMAND}" --test-dir "${CMAKE_BINARY_DIR}"
-C "$<CONFIG>" -L "^Renderive_Kernel$" --output-on-failure
-C "$<CONFIG>" -L "^Renderive_Kernel$" --output-on-failure
DEPENDS ${Renderive_Kernel_test_targets}
USES_TERMINAL)
endif ()
+22
View File
@@ -0,0 +1,22 @@
include_guard(GLOBAL)
block()
rcl_init(render_kernel)
_register_git_cmake_library(render_kernel::oneTBB
"https://github.com/uxlfoundation/oneTBB.git"
"v2023.1.0"
)
rcl_get_effective_install_dir(render_kernel::oneTBB ONETBB_ROOT)
set(TBB_CONFIG_DIR "${ONETBB_ROOT}/lib/cmake/TBB")
rcl_cmake_library_set_cmake_options(render_kernel::oneTBB ${oneTBB_option})
rcl_cmake_library_set_other_use_opt(render_kernel::oneTBB
"-DTBB_DIR=\"${TBB_CONFIG_DIR}\""
)
rcl_cmake_library_set_init_script(render_kernel::oneTBB
"set(ONETBB_ROOT \"${ONETBB_ROOT}\")"
"set(TBB_DIR \"${TBB_CONFIG_DIR}\")"
)
rcl_cmake_library_set_clear_script(render_kernel::oneTBB
"unset(ONETBB_ROOT)"
"unset(TBB_DIR)"
)
endblock()
-770
View File
@@ -1,770 +0,0 @@
# Renderive 当前架构核实与改造 TODO
## P0External Operation 生命周期与失败语义
### TODO-01:核实 External Operation 是否存在永久 pending 风险
* [ ] 阅读 `Kernel/src/renderive/render_graph/External_Operation.hpp`
* [ ] 明确 `External_Operation_Source` 当前生命周期规则
* [ ] 核实 Source 被析构但未调用 `complete()` / `fail()` 时,Operation 的最终状态
* [ ] 编写测试:创建 Operation 后直接销毁 Source
* [ ] 验证 `Render_Graph_Runtime` 是否会永久停留在 `waiting_external`
* [ ] 验证 graph future / frame completion 是否会永久阻塞
* [ ] 检查 Datoviz GPU completion 所有异常路径是否保证最终 complete/fail
**核实目标:**
确认是否存在:
```text
producer 消失
External_Operation 永久 pending
node 永久 waiting_external
frame 永久无法完成
```
**如果问题成立,再考虑改造:**
* [ ] 为 External Operation 定义明确 terminal states
```text
pending
completed
failed
cancelled / abandoned / broken
```
* [ ] Source 析构时处理未完成 Operation
* [ ] 明确 broken producer 应映射为:
* failure
* cancellation
* abandoned
* [ ] 保证 completion callback 只执行一次
* [ ] 保证 complete/fail/cancel 与析构竞态安全
* [ ] 增加 double-complete / complete-after-fail 等测试
* [ ] 增加 Runtime 等待 external 时 producer 异常退出测试
---
### TODO-02:核实 Render Graph failure 后是否可能等待无关 External Operation
重点位置:
```text
Render_Graph_Runtime.cpp
```
特别检查 graph 结束条件中:
```text
failed
active_tasks
waiting_external
unfinished_nodes
```
之间的关系。
* [ ] 构造两个并行 async nodeA、B
* [ ] A 主动 fail
* [ ] B 永久不回调
* [ ] 验证整个 graph 是否永久不返回
* [ ] 构造 A fail、B 正常延迟完成
* [ ] 验证失败 frame 的 completion 时间是否被 B 强制拖长
* [ ] 明确当前设计到底是:
* fail-fast
* drain-before-fail
* cooperative cancellation
**如果问题成立,再决定语义。**
推荐先明确 contract,不要直接写代码:
```text
方案 Afail-fast
一个节点失败后,frame 尽快失败返回。
方案 Bdrain
一个节点失败后,已启动外部操作仍全部收尾后再返回。
方案 Ccancel + drain
请求其他 operation cancellation
无法取消的 operation 做后台资源回收,
但 frame logical completion 可以提前结束。
```
* [ ] 根据 Renderive 的资源安全要求确定一种正式语义
* [ ] 再增加对应 Runtime 状态机和测试
---
## P1Render Plan 生命周期与重复编译
### TODO-03:核实 Render_Plan_History 是否长期无限增长
重点检查:
```text
Render_Plan.cpp
Render_Plan_History
compile_render_plan()
```
当前重点假设:
```text
只与 plans_.back() 比较 topology
```
可能出现:
```text
Topology A
Topology B
Topology A
Topology B
...
```
导致历史:
```text
A1 B2 A3 B4 A5 B6 ...
```
* [ ] 确认 `Render_Plan_History` 是否只 dedupe 相邻 topology
* [ ] 确认旧 Render Plan 是否存在必须永久保存的语义
* [ ] 检查 Capture 是否持有具体 Plan
* [ ] 检查 Observation / Analysis 是否依赖历史 Plan 对象
* [ ] 模拟 10 万帧 A/B topology 交替
* [ ] 观察:
* plan 数量
* 内存占用
* version 增长
* [ ] 确认长期运行是否确实存在无界增长
**如果成立,再选择方案:**
### 方案 ATopology Interning
* [ ] 为 topology 计算稳定 fingerprint/hash
* [ ] 相同 topology 复用已有 immutable Render Plan
```text
topology fingerprint
Render_Plan cache
```
### 方案 BBounded History
* [ ] History 保留最近 N 个 plan
* [ ] Capture 对需要长期保存的 Plan 单独持有 shared_ptr
### 方案 C:二者结合
* [ ] topology cache
* [ ] bounded observation history
* [ ] capture 独立 pin
优先研究方案 C。
---
### TODO-04:核实 Render Plan 是否每帧重复编译执行拓扑
重点检查:
```text
Scene_Base::compile_render_plan()
Render_Graph_Runtime constructor
```
核实每帧是否重复执行:
```text
node id → index map
successor adjacency
indegree
topological sort
cycle validation
```
* [ ] 使用 profiler 测量 Runtime constructor 成本
* [ ] 分别测试:
* 10 nodes
* 50 nodes
* 100 nodes
* 500 nodes
* 1000 nodes
* [ ] 测量每帧:
* Render Plan compile 时间
* Runtime construction 时间
* topology validation 时间
* [ ] 与真实 node execution 时间比较
* [ ] 判断这部分是否已经进入 critical path
**只有测量证明值得优化时再改。**
潜在目标结构:
```text
Render_Plan
├─ Logical metadata
└─ Compiled Execution Layout
├─ node execution index
├─ predecessor count[]
├─ successor offset[]
├─ successors[]
└─ static topology metadata
```
每帧 Runtime 只生成:
```text
remaining_predecessors[]
node_status[]
timestamps[]
external state
```
* [ ] 尽量避免每帧重新构造 immutable topology
* [ ] 保持 Render Plan 本身 immutable
* [ ] 不把 per-frame execution state 塞回 Render Plan
* [ ] benchmark 改造前后成本
---
## P1:调度模型规模化核实
### TODO-05:统计每个 Scene 实际线程成本
当前重点检查:
```text
Scene_Base worker thread
3D Point Scene:
Render_Domain thread
Gpu_Completion_Service thread
Global:
Taskflow Executor workers
```
* [ ] 启动 1 个 3D Scene,记录线程数
* [ ] 启动 10 个 Scene
* [ ] 启动 50 个 Scene
* [ ] 启动 100 个 Scene
* [ ] 测量:
* OS thread 数量
* context switches
* idle CPU
* memory/thread stack
* frame latency
* scheduler wait
* [ ] 明确 Renderive 目标部署规模:
* 单桌面程序 14 Scene
* 多窗口
* Web 多 session
* server-side rendering
**如果目标只是少量本地 Scene**
* [ ] 暂不修改
**如果未来需要大量 Scene**
* [ ] 研究共享 Render Domain
* [ ] 研究共享 GPU Completion Service
* [ ] Render Domain 按 device/backend 而不是按 Scene 划分
* [ ] 使用 serial executor / strand 保持 backend affinity
目标可能演化为:
```text
Global CPU Executor
GPU Device 0
├─ Shared Render Domain
│ ├─ Scene A
│ ├─ Scene B
│ └─ Scene C
└─ Shared Completion Service
├─ fence A
├─ fence B
└─ fence C
```
**不要在没有多 Scene 压测数据之前重构这一项。**
---
## P2Frame Pipeline 能力边界确认
### TODO-06:确认当前 Scene 是否严格 single-frame-in-flight
重点检查:
```text
Scene_Base::submit_render()
Scene_Base::lock_render_idle()
```
* [ ] 验证 GPU external operation 未完成时,下一帧 CPU DAG 能否开始
* [ ] 确认当前行为是否:
```text
Frame N CPU
Frame N GPU
Frame N complete
Frame N+1 CPU
```
而不是:
```text
Frame N GPU
+
Frame N+1 CPU
```
* [ ] 测量这种设计对 interactive latency 的影响
* [ ] 验证 low-latency 模式下 GPU queue 是否保持浅队列
* [ ] 验证 frame age 是否优于多帧堆积方案
**当前阶段不要默认这是问题。**
明确记录设计决策:
```text
Interactive mode:
优先 latency
→ single-frame-in-flight 可以是正确策略
Throughput / offline mode:
优先 throughput
→ 未来可能需要 multi-frame-in-flight
```
* [ ] 只有出现离线渲染/视频/批处理需求时再设计跨帧流水线
---
## P2Scene_Base 职责边界检查
### TODO-07:持续监控 Scene_Base 是否演化成 God Object
盘点当前职责:
```text
Renderable lifetime
Scene state
Snapshot
Dependency
Render plan compile
Frame submission
Worker control
Edit queue
Exception propagation
Observation
Capture integration
Frame control
Memory domain
```
* [ ] 给 Scene_Base 当前职责做一次正式列表
* [ ] 检查每个职责是否属于 orchestration
* [ ] 新功能进入 Scene_Base 前必须判断能否成为独立 subsystem
特别避免未来加入:
```text
GPU fence logic
backend-specific synchronization
generic async continuation internals
capture storage internals
scheduler implementation internals
GPU profiler implementation
```
应继续保持:
```text
Scene_Base
↓ orchestrate
Render_Graph_Runtime
External_Operation
Render_Domain
Gpu_Completion_Service
Capture subsystem
Observation subsystem
```
**当前不建议仅为了减小文件而拆类。**
只有当职责本身形成独立生命周期/状态机时再拆。
---
## P2Taskflow 依赖边界确认
### TODO-08:正式确认 Taskflow 只是 Executor,而不是架构语义的一部分
当前目标模型:
```text
Render_Graph_Runtime
CPU Scheduler Adapter
tf::Executor::silent_async()
```
* [ ] 搜索所有 `tf::` 使用点
* [ ] 检查 Renderable / Render Plan / External Operation 是否泄漏 Taskflow 类型
* [ ] 检查 Render_Graph_Runtime 是否依赖 Taskflow DAG API
* [ ] 确认 Taskflow 只承担 CPU function dispatch
* [ ] 做一个最小 mock executor 测试
例如:
```cpp
scheduler([] {
...
});
```
替换成测试 executor 后 Runtime 仍正常运行。
* [ ] 如果成立,记录架构结论:
```text
Taskflow is an implementation detail of CPU execution,
not Renderive's task graph model.
```
**如果已经成立,不需要再额外设计 Scheduler Concept。**
先保持最小 callable abstraction 即可。
---
## P2Datoviz Async 接入完整性核实
### TODO-09:逐条核实 GPU completion 所有路径都完成 External Operation
检查:
```text
Point_Scene
Render_Domain
Gpu_Completion_Service
Datoviz backend
```
覆盖以下路径:
* [ ] 正常 submit
* [ ] submit 失败
* [ ] fence 创建失败
* [ ] fence wait 失败
* [ ] device lost
* [ ] Render_Domain callback 抛异常
* [ ] backend.collect() 抛异常
* [ ] Scene 被销毁
* [ ] application shutdown
* [ ] completion service shutdown
* [ ] frame 被逻辑取消
* [ ] GPU operation 已提交但 Scene 生命周期结束
每条路径都验证最终发生:
```text
complete
fail/cancel
```
不能:
```text
silent disappearance
→ External Operation 永久 pending
```
---
## P2CPU / GPU 观测数据可信度核实
### TODO-10:验证节点时间戳语义
针对:
```text
ready_time
start_time
cpu_end_time
external_start_time
external_end_time
end_time
```
逐项明确语义。
* [ ] `ready_time` 是否表示所有 dependency 已满足
* [ ] `start_time - ready_time` 是否能准确代表 scheduler wait
* [ ] `cpu_end_time - start_time` 是否代表 CPU node execution
* [ ] external_start 是否发生在 CPU submit 完成附近
* [ ] external_end 是否由真正的 external completion 驱动
* [ ] node end 是否与 dependency unlock 同一语义点
* [ ] worker_id 是否只表示 CPU execution worker
* [ ] external completion thread 是否不会错误覆盖 worker_id
---
### TODO-11:验证 Critical Path 算法
* [ ] 手工构造简单 DAG
```text
A 10ms ── C 5ms
B 20ms ──┘
```
期望 critical path
```text
B → C = 25ms
```
* [ ] 增加 external node
* [ ] 增加 scheduler wait
* [ ] 明确 critical path 是否统计:
* CPU execution
* external wait
* scheduler wait
* dependency wait
* [ ] 明确算法计算的是:
* DAG theoretical execution critical path
* 还是 observed wall-clock critical path
两者不能混淆。
---
### TODO-12:验证 GPU timing 与 fence wait 的区别
确保以下数据不会混用:
```text
GPU timestamp duration
CPU fence waiting duration
```
* [ ] GPU render timestamp
* [ ] transition timestamp
* [ ] copy timestamp
* [ ] total GPU timestamp
* [ ] CPU submit duration
* [ ] completion service fence wait duration
* [ ] readback duration
输出报告时必须保持这些概念分离。
---
## P3Low Latency / Frame Control 回归测试
### TODO-13:正式锁定 latest-frame-wins 的行为语义
* [ ] 测试 pending frame 被替换
* [ ] 测试 rendering frame 不会被中断
* [ ] 测试事件修改模型后,新 snapshot 能看到状态
* [ ] 测试被替换 frame 不会导致权威模型状态丢失
* [ ] 测试 burst input
* mouse move
* resize
* camera control
* rapid data update
* [ ] 记录:
* produced frames
* rendered frames
* dropped/replaced frames
* input-to-present latency
明确:
```text
丢的是待展示/待处理 frame
不是 authoritative scene state
```
---
## P3Runtime 压测
### TODO-14:建立标准 DAG benchmark
固定测试:
```text
10 nodes
50 nodes
100 nodes
500 nodes
1000 nodes
```
分别包含:
```text
完全串行
完全并行
树形
diamond
随机 DAG
CPU + External 混合 DAG
```
记录:
* [ ] graph compile time
* [ ] runtime setup time
* [ ] scheduler wait
* [ ] total CPU work
* [ ] wall time
* [ ] parallelism
* [ ] Taskflow worker utilization
* [ ] Runtime lock contention
* [ ] completion callback cost
后续任何 scheduler 改造都与此 benchmark 对比。
---
### TODO-15:建立 Renderive 实际 workload benchmark
不要只做 synthetic benchmark。
至少保留:
* [ ] 纯 2D 场景
* [ ] 小型 3D point scene
* [ ] 大型 3D point scene
* [ ] 多 Renderable dependency
* [ ] frequent update
* [ ] low-latency input
* [ ] GPU-heavy
* [ ] CPU-heavy
* [ ] mixed CPU/GPU
每次架构改造比较:
```text
P50
P95
P99
critical path
scheduler wait
CPU work
GPU duration
frame age
memory
thread count
```
---
# 暂时不要做的事情
下面这些目前没有证据证明值得做:
* [ ] **不要**再包一层复杂 Taskflow abstraction
* [ ] **不要**给 `Scene_Render_Context` 增加 continuation
* [ ] **不要**为了异步统一而全面 coroutine 化
* [ ] **不要**现在实现 multi-frame-in-flight
* [ ] **不要**仅为了减少线程数重写整个 Scene scheduler
* [ ] **不要**仅为了“架构漂亮”拆 Scene_Base
* [ ] **不要**重复实现 Taskflow 自己已经能完成的线程池能力
* [ ] **不要**在没有 profiler 数据时优化 DAG 微小调度开销
---
# 建议执行顺序
```text
第一阶段:正确性
TODO-01 External Operation 生命周期
TODO-02 failure / cancellation
TODO-09 Datoviz 异常路径
第二阶段:长期运行稳定性
TODO-03 Render Plan History
TODO-10/11/12 观测数据可信度
第三阶段:CPU Runtime 成本
TODO-04 Render Plan compiled layout
TODO-14 synthetic benchmark
TODO-15 real workload benchmark
第四阶段:规模化
TODO-05 多 Scene thread model
TODO-06 multi-frame-in-flight 是否真的需要
持续性约束
TODO-07 Scene_Base 边界
TODO-08 Taskflow implementation-detail 边界
TODO-13 Low Latency 回归测试
```
## 最终原则
每一项都严格走:
```text
提出怀疑
写最小复现 / benchmark
确认问题是否真实存在
量化严重程度
定义行为 contract
再修改实现
回归测试 + benchmark
```
不要变成:
```text
看到一个理论缺陷
立刻重新设计架构
```
现阶段 Renderive 更需要的是**验证和收敛**,而不是继续扩张调度抽象。