Files
2026-08-14 06:22:14 +08:00

13 KiB
Raw Permalink Blame History

可以,下面这份就按 Codex 执行计划 来做。基于你当前代码,重点不是再动 Datoviz,而是把 Kernel → Scene3D → Datoviz backend 这条链建立起来。

1. 先扩展 Kernel 帧策略

修改:

Kernel/src/renderive/frame_control/strategy/low_latency/
Kernel/src/renderive/frame_control/strategy/manual/
Kernel/tests/renderive/frame_control/strategy/

Low_Latency_Strategy

给:

struct Configuration

增加:

bool replace_pending_frame{true};

语义:

true
= 保持现在行为
= pending frame 可以被新帧替换
= abandoned_frame_count 正常增长

false
= 已发布 pending frame 禁止自动替换
= Painter 在 pending frame 被 renderer 取得前产生背压

默认必须是 true,不能破坏 Render2D 当前语义。

禁止 busy wait。使用等待/通知机制;等待仅发生在 pending slot 争用阶段。

Renderer 完成:

cache → render

交换后立即释放状态锁并唤醒 Painter。

Manual_Refresh_Strategy

这个也必须一起处理。

当前 Manual 同样存在:

prepared A
↓
prepare B
↓
A 被 prepared_replaced

所以增加同语义配置:

bool replace_pending_frame{true};

Scene3D 固定 false

Flow_Refresh_Strategy 不改,它本身就是队列,不覆盖。

注释必须明确

Kernel 注释说明:

replace_pending_frame=true
保持 latest-frame-wins / prepared-frame-replacement 旧语义。

replace_pending_frame=false
保持单 pending frame,但通过 producer backpressure 保证已发布帧不会被后续帧自动替换。

并明确:

这个选项只控制自动 replacement
不改变显式 discard API 的语义。

Kernel 测试

必须新增:

low latency 默认仍覆盖
low latency false 不覆盖
low latency false producer 会等待
renderer 获取 pending 后 producer 被唤醒
显式 discard 后 producer 被唤醒
abandoned_frame_count 在 false 模式不增加

manual 默认仍 replacement
manual false 不 replacement
refresh 后 painter 才继续

全量 Kernel 测试通过后再进入 Render3D。


2. Render3D 正式链接 Kernel

当前:

Renderive_render_3D

只检查:

TARGET
Renderive_Kernel

但没有像 Render2D 那样真正链接。

改成:

target_link_libraries(Renderive_render_3D
        PUBLIC
        Renderive_Kernel
        ...
)

除此之外不要调整现有 Datoviz CMake 布局。


3. 不使用现有 Kernel Scene3D_Context

当前:

Kernel/Scene3D_Context
↓
Triple_State_Strategy
↓
Scene_Base
↓
Renderable DAG
↓
Frame_Render_Snapshot

Render3D 不走这条链。

原因:

Datoviz Scene/Visual
不是 Kernel Renderable

3D 不需要再套 Renderable DAG

当前 Scene3D_Context 使用 Triple_State
也不符合这次要求的 Double_State 模型

只复用 Kernel 的基础机制:

Double_State_Strategy
Low_Latency_Strategy
Manual_Refresh_Strategy
Flow_Refresh_Strategy
Property_Builder
Validated_Value / Validator
renderive_Owner
Atomic_Spin_Mutex
Observer

不要改 Scene3D_Context 做兼容层。


4. 建 Render3D 自己的对象层

目录:

render_3D/render_3D/
├── base/
│   ├── Object_Id.h
│   └── Object_Id.cpp
├── camera/
│   ├── Camera.h
│   ├── Camera.cpp
│   └── Camera_p.h
├── material/
│   ├── Material.h
│   ├── Material.cpp
│   └── Material_p.h
├── texture/
│   ├── Texture.h
│   ├── Texture.cpp
│   └── Texture_p.h
├── mesh/
│   ├── Mesh.h
│   ├── Mesh.cpp
│   └── Mesh_p.h
└── scene/
    ├── Scene.h
    ├── Scene.cpp
    ├── Scene_p.h
    └── Scene_Frame.h

第一批只做:

Scene
Camera
Mesh
Material
Texture

不要一次把 Datoviz 所有 Visual 包出去。


5. PImpl 规则

风格参考 Render2D。

例如:

Mesh
 └── unique_ptr<Mesh::Impl>

Material
 └── unique_ptr<Material::Impl>

严格规定:

public Mesh → Mesh::Impl              允许

Scene::Impl → Mesh::Impl              允许
Mesh::Impl → Material::Impl           允许
Material::Impl → Texture::Impl        允许

Scene::Impl → Mesh public API         禁止
Mesh::Impl → Material public API      禁止
backend → 任意 public API             禁止

内部需要访问别的对象时通过:

friend
+
d_func<Impl>()

完成。

不要为了访问内部状态绕回公开 getter。


6. 每个对象使用 Double_State_Strategy

例如逻辑:

Mesh::Impl
    ↓
Double_State_Strategy<Mesh_State>

状态分成:

cache
render
scratch

setter

短锁
↓
修改 cache

prepare

publish
↓
cache → scratch
↓
交换 render

交换后不再持锁。

Camera、Material、Texture 同样处理。


7. 大数据禁止跟着 Double State 整体复制

例如:

Mesh_State
├── transform
├── visible
├── material_id
└── shared_ptr<const Geometry_Data>

Geometry_Data

vertices
indices
normals
uv

Texture 同样:

Texture_State
└── shared_ptr<const Image_Data>

所以 Double State publish 只复制:

matrix
enum
id
shared_ptr

禁止每帧复制整个 vertex/image 数据。


8. Scene 结构不要做 Snapshot

本阶段 不引入 Snapshot

Scene 内部维护:

attached meshes
active camera
pending structure changes

结构修改统一通过一个内部 mutation/edit 对象完成,不增加大量:

apply_attach_xxx
apply_detach_xxx
apply_set_xxx

这种函数。

例如语义:

scene.update([](Scene_Edit& edit) {
    edit.attach(mesh);
    edit.detach(old_mesh);
    edit.set_camera(camera);
});

Scene_Edit 直接操作 Scene::Impl 的结构状态。


9. Scene 所有权

使用:

renderive_Owner

规则:

外部 owner
+
Scene attach 后持有自己的 owner

所以:

external mesh.reset()

但 Mesh 仍 attach

Mesh 继续存活

detach 后:

Scene 释放自己的 owner

Datoviz backend 绝不持有 public Mesh/Material/Texture 指针


10. 定义 Scene3D_Frame_Data

放:

render_3D/render_3D/scene/Scene_Frame.h

它是 Kernel frame strategy 传递的数据。

内容只包括:

scene state
object updates
structure updates
immutable resource handles

例如逻辑:

Scene3D_Frame_Data
├── camera
├── mesh_updates
├── material_updates
├── texture_updates
├── attach_updates
└── detach_updates

不要命名成 Snapshot。

它是:

Kernel 帧槽中的稳定交接数据。


11. 帧生成边界

prepare_frame() 固定流程:

acquire_painter()
↓
publish Scene Double State
↓
publish Camera/Mesh/Material/Texture Double State
↓
比较 revision
↓
把发生变化的稳定状态写入 Scene3D_Frame_Data
↓
写入 attach/detach 等结构变化
↓
Painter_Lease 结束
↓
frame publish

一旦 Painter Lease 结束:

Scene3D_Frame_Data

必须完全独立于:

public object
Double_State 内部地址
Scene::Impl 可变结构

12. Scene3D 固定关闭帧覆盖

定义类似 Render2D

Manual_Frame_Control
Low_Latency_Frame_Control
Playback_Frame_Control

但是创建 3D LowLatency 时:

configuration.replace_pending_frame = false;

Manual 同样:

configuration.replace_pending_frame = false;

代码旁必须写注释:

Scene3D 的 published frame 可能包含 Datoviz retained backend
的有序资源/状态转换,因此发布后的帧禁止被后续帧自动替换。

这个值不提供 public setter。

Flow 不需要特殊配置。


13. Scene3D 不暴露 discard

Kernel generic strategy 保留:

discard_pending_frame()

但 Scene3D public API 第一版不要暴露它。

否则:

attach/update frame
↓
显式 discard
↓
Datoviz backend 没收到

仍然会破坏 retained state 顺序。


14. 建 Datoviz adapter/backend

新目录:

render_3D/render_3D/backend/datoviz/
├── Datoviz_Renderer.h
├── Datoviz_Renderer.cpp
├── Datoviz_Resources.h
└── Datoviz_Frame.h

这里才写 Render3D ↔ Datoviz 的 C++ 适配。

不要继续修改:

render_3D/datoviz/

除非发现真正的 Datoviz 移植 bug。


15. Datoviz backend 所有权

统一:

Datoviz_Renderer
├── DvzScene
├── DvzFigure
├── DvzPanel
├── DvzDrp2Runtime
├── GPU/VMA context
├── object binding map
└── GPU frame slots

例如:

Mesh Object_Id
    ↓
Backend_Mesh
    ↓
DvzVisual*

这个映射属于:

Datoviz_Renderer

不属于:

Mesh::Impl

因此:

Mesh::~Mesh()

禁止直接调用 Datoviz/Vulkan destroy。


16. Renderer 热路径完全脱离对象层

固定:

acquire_renderer()
↓
拿到 Scene3D_Frame_Data
↓
从这里开始禁止访问 public object
↓
也禁止访问 Mesh::Impl / Material::Impl
↓
Datoviz_Renderer::render(frame)

也就是:

双缓冲 publish
↓
Frame_Data 固化
====================
下面开始无模型锁区域
====================
Datoviz state update
DRP2 emit
command recording
submit

这条边界必须写测试。


17. Datoviz retained state 只在 Renderer 线程修改

禁止:

应用线程 → dvz_visual_xxx
Painter → dvz_scene_xxx
Mesh::Impl → dvz_buffer_xxx

只有:

Datoviz_Renderer thread

允许修改:

DvzScene
DvzVisual
DvzFramePlanEmitter
DvzDrp2Runtime
Vulkan resource

这样 Datoviz 内部不用额外补大量锁。


18. GPU 帧槽独立于 Kernel 帧槽

不要做:

Kernel frame[0] == GPU frame[0]

单独建立:

GPU_Frame_Slot
├── command buffer
├── fence
├── render image
└── staging/readback resources

Renderer

选择下一 GPU slot
↓
仅等待该 slot fence
↓
reset
↓
record
↓
submit

禁止主渲染路径每帧:

vkDeviceWaitIdle()
dvz_device_wait()
queue-wide wait

19. 第一条真实功能只打通 Mesh

不要同时做所有 Datoviz Visual。

第一条完整链:

Scene
↓
Camera
↓
Mesh
↓
Material
↓
Geometry_Data
↓
Scene3D_Frame_Data
↓
Datoviz_Renderer
↓
Dvz mesh visual
↓
DRP2
↓
Vulkan offscreen image

这条跑通以后再加:

Texture
Text
Point
Sphere
Primitive
Volume
Transparency
Picking

20. Builder

对象 Builder 复用 Kernel

Property_Builder
Validator
Validated_Value

例如:

Mesh_Properties
Mesh_Builder

Material_Properties
Material_Builder

Builder 只负责:

验证 properties
↓
构造 public object + Impl

禁止 Builder

创建 DvzVisual
创建 VkBuffer
访问 Datoviz_Renderer
attach GPU resource

GPU 创建只能等 frame 到 renderer。


21. 必须补的 Render3D 测试

CPU 测试:

Mesh setter 只修改 cache

publish 后 render state 稳定

publish 后应用线程继续修改 cache
旧 Frame_Data 不变化

Impl → Impl 读取正确

Scene attach 持有 owner

external owner reset 后 attached object 存活

detach 后 Scene owner 释放

Frame_Data 不保存 public object 指针

Frame_Data 不保存 Double_State 内部引用

帧测试:

LowLatency Scene3D 不出现 abandoned frame

A published 未消费
→ B 不能覆盖 A

renderer acquire A
→ painter 才允许继续 B

并发测试:

Renderer 正在处理 Frame A
同时应用线程修改 Mesh cache
→ 不互相持有长期锁

Backend 测试:

attach Mesh
→ 创建一次 DvzVisual

修改 Mesh
→ 更新同一个 backend object

detach Mesh
→ 销毁一次

public Mesh 销毁
→ 不直接触发 Datoviz GPU 操作

22. 完成标准

这一阶段完成时必须满足:

Datoviz 源码保持独立移植目录
Kernel 不知道 Datoviz
public Render3D API 不出现 Dvz*
Scene3D 不使用 Renderable DAG
Scene3D 不使用 Frame_Render_Snapshot
对象状态使用 Double_State
LowLatency/Manual 3D 帧不自动覆盖
Frame publish 后 renderer 不访问 public/Impl
Datoviz 全部由 Renderer 线程拥有
CPU 修改下一帧与 GPU 渲染上一帧可以并行
主路径不存在 device-wide wait

执行顺序严格按:

1 Kernel replacement 配置 + 测试
2 Render3D 链接 Kernel
3 PImpl + Object State
4 Scene ownership / mutation
5 Scene3D_Frame_Data
6 Basic_Scene3D + 三种 Frame_Control
7 Datoviz_Renderer
8 Mesh 完整链路
9 并发/所有权测试
10 再扩展其他 Datoviz visual

本轮不引入任何 Snapshot。 当前稳定帧交接只需要轻量 Scene3D_Frame_Data + immutable shared payload,没有理由复制整个 Scene 或 Datoviz backend 状态。