Files
Renderive/Kernel/readme.md
T
2026-08-10 15:47:51 +08:00

189 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Renderive Scene 与 Taskflow 任务图
## Scene 类型层次
Scene 不再使用维度枚举。运行时类型关系为:
```text
Scene_Base
├── Scene_2D_Base
└── Scene_3D_Base
```
`Scene2D_Context` 通过 `Triple_State_Strategy<Scene_2D_Base, ...>` 继承 `Scene_2D_Base``Scene3D_Context` 通过 `Triple_State_Strategy<Scene_3D_Base, ...>` 继承 `Scene_3D_Base`。调用方通过类型系统、概念或 `dynamic_cast` 区分 2D 与 3D Scene。
## Scene 三缓冲状态
`Scene2D_Context``Scene3D_Context` 都公开类型别名 `Scene_State_Strategy`。状态访问使用基类名限定,避免多继承时成员歧义:
```cpp
scene.Scene_State_Strategy::set<&Scene_State::value>(value);
scene.Scene_State_Strategy::publish();
scene.Scene_State_Strategy::render_use_state();
```
三缓冲分别承担正在渲染、已发布待获取、前台写入三种职责。`render()` 提交任务前调用 `acquire_render_state()`,后台帧获得稳定的状态版本。`render_use_state()` 返回状态快照,不返回内部缓冲区引用。
## Scene 观察者
Scene 状态观察者记录状态缓存修改、发布和渲染获取事件。Scene 生命周期观察者记录:
- `render_submitted`
- `render_started`
- `render_completed`
- `render_failed`
观察器和时间源沿用 `Observer_State`
## 单一 Renderable
Renderable 不再拆分为 Scene 持有缓存和独立持有缓存两类。所有对象统一继承 `Renderable_Base`
```cpp
struct Spectrum : Renderable_Base {
explicit Spectrum(Scene_Base& scene)
: Renderable_Base(scene, {.cache_enabled = true}) {}
};
```
`Renderable_Configuration::cache_enabled` 只控制渲染结果是否跨帧复用。默认关闭,以保持每次 Scene 提交都重新渲染的旧语义。Scene2D 仍为每个 Renderable 维护颜色缓存,以支持渲染依赖顺序和显示合成顺序相互独立。
缓存开启后,首次渲染或调用 `invalidate_cache()` 后执行任务图;缓存有效时跳过内部任务图,直接参与最终颜色合成。缓存关闭时每帧执行。`configuration()` 返回线程安全的配置快照,配置修改统一通过 `Scene_Base::set_renderable_configuration()` 完成。
## Renderable 内部任务图
每个 Renderable 通过 `build_task_graph(Renderable_Task_Graph&)` 定义自己的 DAG。公开接口只暴露 Renderive 的任务句柄,不暴露 `tf::Taskflow``tf::Task` 或任何 Taskflow 头文件:
```cpp
void build_task_graph(Renderable_Task_Graph& graph) override {
auto prepare = graph.emplace([this](const Scene_Render_Context&) {
prepare_data();
}, "prepare");
auto rasterize = graph.emplace([this](const Scene_Render_Context&) {
rasterize_cache();
}, "rasterize");
graph.precede(prepare, rasterize);
}
```
未覆盖 `build_task_graph` 时,基类自动创建一个调用虚函数 `render()` 的单节点图。`task_graph()` 返回不可变共享快照;`rebuild_task_graph()` 与并发读取串行化,旧快照在调用方释放前保持有效。
## 外部依赖与总图组合
依赖树表达 Renderable 之间的外部依赖,父节点先于子节点。关系修改统一通过 Scene 完成,例如 `scene.set_dependency_parent(child, parent)`,不会绕过 Scene 的渲染同步边界。每次后台渲染会:
1. 为每个 Renderable 构造或复用其内部图描述。
2. 将内部图转换为一个 Taskflow module task。
3. 按依赖树连接 module task。
4. 将所有 module task 连接到最终颜色缓存生成任务。
5. 交给进程内共享的长期 `tf::Executor` 执行并等待完成。
Taskflow 只在 `Scene_Base.cpp` 中包含。`Scene_Base` 通过内部执行上下文访问进程内共享的 `tf::Executor`Executor 跨 Scene、跨帧复用。`renderive_scene` 对 Taskflow include 路径使用 `PRIVATE`,外部头文件不会受到 Taskflow 宏、类型或包含链污染。
## 显示树与依赖树
显示树只决定最终颜色缓存合成顺序,依赖树只决定 Renderable module task 的执行依赖。两棵树继续保持独立,轴可以先计算但最后合成。节点对象不再作为公开拓扑读接口;调用方通过 `topology_snapshot()` 获取线程安全关系快照,父子关系只通过 `set_display_parent()``set_dependency_parent()` 修改。参与显示树或依赖树的 Renderable 必须先 attach 到所属 Scene,避免拓扑引用未被 Scene 持有的对象。
## 测试入口
`BUILD_TESTING=ON` 时构建 `renderive_scene_tests` 并通过 CTest 运行;`BUILD_TESTING=OFF` 时只构建 `renderive_scene` 库,不再编译 `tests/main.cpp` 或任何测试源文件。
## 线程模型
Scene、Frame Strategy、Real-Time-Data、State Strategy、Renderable task graph 的允许并发组合、生命周期边界和对应测试统一记录在 `threading.md`。多线程语义测试作为普通测试套件的一部分构建,不依赖 ThreadSanitizerLinux CI 建议额外用 TSan 执行同一套测试。
## Taskflow 查找
CMake 通过 `RENDERIVE_TASKFLOW_ROOT` 或系统 include 路径查找 `taskflow/taskflow.hpp`。找到 Taskflow 4.1.0 时使用真实执行器;找不到时使用 `src/renderive/compat/taskflow/taskflow.hpp` 的内置顺序依赖图执行器,使工程仍可完成 CMake 配置、编译并运行依赖图测试。兼容执行器只实现 Renderive 当前使用的 Taskflow 子集,用于验证内部 DAG、Renderable 外部依赖和 module 完成关系是否被正确转换,不模拟并行调度能力。真实 Taskflow 下额外运行并行执行测试。
## Flow 并发队列
`Flow_Refresh_Strategy` 当前使用 `std::pmr::deque<Memory_Resource_Unique_Ptr<Frame>>` 保存待渲染帧,不引入 Boost.Lockfree 或其他新增队列依赖。多个 painter 可以并发创建 Frame,入队由 `state_mutex_` 串行化;多个 renderer 调用也安全,但实际消费由 `render_mutex_` 串行化,因此每个 Frame 最多被消费一次。多 producer 下的全局顺序以实际完成入队的线性化顺序为准。
Flow 的 observer 允许同线程重入 Observer_Staterenderer observer 中再次申请同一个 Flow renderer 会立即得到空 lease,不会等待当前 renderer lease 自身释放。完整线程契约和多线程压力测试见 `threading.md`
## 实时数据附件所有权
`With_Real_Time_Data` 接收 `std::shared_ptr<Data>``Attach_Real_Time_Data` 在自身生命周期内持有这些实时数据源,避免 Renderable 仍存活时外部提前销毁 Data 产生悬空指针:
```cpp
auto data = std::make_shared<Latest_Real_Time_Data<int>>();
auto renderable = std::make_shared<Attach_Real_Time_Data<My_Renderable, Latest_Real_Time_Data<int>>>(
With_Real_Time_Data(data), scene);
```
## Scene 内存域
`Scene_Base` 接受外部 `std::pmr::memory_resource` 作为上游资源。Scene 内部建立 `std::pmr::synchronized_pool_resource`,所有可能跨线程访问的持久容器都使用该线程安全内存域:
```cpp
std::pmr::unsynchronized_pool_resource application_resource;
Scene2D_Context<> scene(application_resource);
```
外部资源决定最终的上游分配机制。Scene 内部的同步池负责将来自调用线程、后台 Scene 线程和 Renderable 构图路径的分配串行化。外部资源必须比 Scene 以及由 Scene 工厂创建后仍存活的 Renderable 更长寿。
可通过以下接口访问两个层级:
```cpp
scene.upstream_memory_resource();
scene.memory_resource();
```
`upstream_memory_resource()` 返回构造时传入的资源;`memory_resource()` 返回 Scene 实际使用的线程安全同步池。
单次渲染构图不长期占用同步池。`Scene_Base::execute_taskflow()` 和 Scene2D 树顺序生成使用局部 `std::pmr::monotonic_buffer_resource`,返回后整体释放临时索引、标记和任务句柄容器。
## Renderable 两种构造模式
Scene 工厂模式同时控制 Renderable 对象、`shared_ptr` 控制块以及 Renderable 内部树和任务图分配:
```cpp
Scene2D_Context<> scene(memory_resource);
auto renderable = scene.make_renderable<Spectrum_Renderable>(configuration);
scene.attach_renderable(renderable);
```
工厂只构造对象,不自动挂载,`attach_renderable()` 的原有语义不变。工厂分配器持有 Scene 内存域的共享所有权,因此 Renderable 析构时内存域仍然有效。
独立构造模式继续保留:
```cpp
auto renderable = std::make_shared<Spectrum_Renderable>(scene, configuration);
Spectrum_Renderable stack_renderable(scene, configuration);
```
独立模式下,Renderable 对象本身和 `shared_ptr` 控制块由调用方选择的机制分配;`Multiway_Node``Renderable_Task_Graph`、任务名称和依赖边仍使用所属 Scene 的内存域。Renderable 可以晚于 Scene 析构以完成自身释放;Scene 销毁后实时数据绑定自动失效,继续调用 `scene()` 会抛出 `std::logic_error`,不会返回悬空 Scene 引用。
## 独立 allocator-aware 组件
以下组件可脱离 Scene 独立注入资源:
- `Multiway_Node`
- `Renderable_Task_Graph`
- `Recording_Color_Cache`
- `Flow_Refresh_Strategy`
- `History_Real_Time_Data`
- `Property_Builder::build_with_resource()`
- `Property_Builder::build_unique_with_resource()`
`History_Real_Time_Data` 的时间戳容器始终使用传入资源。历史值容器只有在 `Container` 本身支持 PMR allocator 时才使用该资源:
```cpp
History_Real_Time_Data<int, std::pmr::vector<int>> history(memory_resource);
```
## 不受 Scene PMR 控制的分配
以下内存不属于 Renderive 自己的 allocator-aware 场景域:
- Taskflow 4.1.0 内部任务节点、执行队列和 executor 工作资源
- `std::function` 超出小对象优化后的内部存储
- `std::thread` 的系统线程对象和线程栈
- 异常运行时对象
- 用户自定义 State、Frame、Observer、Renderable 成员内部自行使用的非 PMR 容器
- 第三方渲染后端内部资源
Scene PMR 的语义是控制 Renderive 明确实现为 allocator-aware 的核心对象,不承诺接管第三方库和用户类型的全部动态分配。