geopro/docs/superpowers/plans/poc-results-C.md

215 lines
12 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# POC-C 实测结果(核外分块体绘制,命门探针)
工具:`tools/gpr_poc renderC`,产物 `build/release/tools/gpr_poc/gpr_poc.exe`
执行机Windows 11MSVCVS Community+ NinjaRelease/O2
GPU**NVIDIA GeForce RTX 3060 Laptop GPU**OpenGL 4.5.0 NVIDIA 555.97,硬件加速 True。
日期2026-06-23。
被测 store9c 建的单线全分辨率):`build/tmp/gpr_store_B_001`
- 体维度 **44476 × 29 × 162**≈2.09 亿体素brick=64金字塔 2 级
level0=2085 块 / level1=696 块)。
- **沿测线 X=44476 ≫ GL_MAX_3D_TEXTURE_SIZE(16384)** —— 即 renderB 标 INVALID
的那条线,本任务要分块核外把它真渲出。
实现:
- `geopro::render::OutOfCoreSource`(实现 `IVolumeRenderSource`):选 LOD相机到体中心
距离 / 体对角线 粗分档)+ 视锥裁剪选视野块 → `BrickPager.requestVisible`LRU内存恒定
→ 每块构造带世界坐标的 ≤64³ `vtkImageData`VTK_SHORT
- `renderC` 把工作集各块装进 `vtkMultiBlockDataSet`,交 `vtkMultiBlockVolumeMapper`
(内部每块一个 `vtkSmartVolumeMapper`back-to-front 排序 + 抖动压接缝)。
- 用 9c 同款 `CapturingOutputWindow` 捕获 3D 纹理维度错误;以**纹理无错 + 渲出非空像素**
为体绘制真出判据(绝不把空纹理假帧率当性能)。
主配置命令:`gpr_poc renderC <store> --budget 64 --frames 120`
---
## 0. 总结论(一句话)
**核外分块体绘制可行、内存恒定、绕开了 16384 纹理墙——但 `vtkMultiBlockVolumeMapper`
的「每块一个 SmartVolumeMapper」架构使 fps 随工作集块数急剧下降,且每帧重建 mapper +
qUncompress 解压换页是更狠的瓶颈。** renderB 整卷 INVALID根本上传不了renderC
能真渲出budget=64 静态 9.5 fps但要达交互级 fps 必须换更省的核外管线
(见 §7 阻塞 / 缓解)。
---
## 1. 六个未知的逐条实测结论
### 未知 1vtkMultiBlockVolumeMapper 能否把动态工作集块渲成正确合成体 —— ✅ 能
- budget=64纹理维度错误=**否**(每块 ≤64³ ≪ 16384逐块上传全部成功
渲出非空像素=**是**(非背景像素 748退出码 0。
- **对照 renderB**renderB 整卷报 `Invalid texture dimensions [44476,29,162]`
体绘制 INVALID空纹理假帧率renderC 把同一条线切成 ≤64³ 的块逐块上传,
**真渲出**。核心可行性成立——**分块核外绕开了 GL 单轴纹理上限。**
- 注:`vtkMultiBlockDataSet` + `vtkMultiBlockVolumeMapper` 直接吃多块 `vtkImageData`
颜色/不透明度传函挂在单个 `vtkVolume``vtkVolumeProperty` 上、全块共用,工作正常。
### 未知 2块边接缝 —— ✅ 未见明显接缝MultiBlock 内置抖动生效)
- `vtkMultiBlockVolumeMapper` 默认对块交界开抖动jittering仅 GPURenderMode 下),
实测渲出图像未见可见接缝条纹。本任务在等值密度的连续 GPR 体上,块边连续性可接受。
- 局限:不同 LOD level 相邻(接缝处分辨率突变)的接缝未单独压测——本任务同一帧同一 level
跨 level 接缝留待 Task 13/14。
### 未知 3LOD 切换 —— ✅ 机制可用,闪烁未量化
- `pickLevel` 按相机距离/体对角线比值粗分档(<1×→L0<2×→L1…,clamp 到可用层)。
- 实测预热相机框全体 **level 1**视野块 696/696ResetCamera 框住工作集后相机贴近
转为 **level 0**末帧视野块 456/2085)。LOD 选择随相机距离正确切换
- 闪烁每帧重选 level/视野块 + 重建 mapper块集合跳变会带来视觉跳变但本探针未做
逐帧像素差量化。**结论机制可用平滑度hysteresis/淡入淡出是后续优化非本探针判据。**
### 未知 4热路径解压qUncompress—— ⚠️ **这是更狠的瓶颈**
- budget=64**动态换页 1.45 fps**其中 `update(cam)`重选块 + `BrickPager.requestVisible`
`store.readBrick`→`qUncompress` 解压换入块 + 重建 `vtkMultiBlockDataSet`平均
**177.8 ms/帧**占整帧绝大部分静态工作集同 64 块只旋转时是 9.5 fps105 ms/)。
- 即换页/解压 + 每帧重建 mapper 9.5 fps 拖到 1.45 fps。**热路径解压确实拖垮帧率**——
当相机移动导致工作集大量换入时每帧要解压几十个块 + MultiBlock 重新创建所有子 mapper
并重传纹理。**实锤未知 4撞墙。** 缓解见 §7
### 未知 5内存恒定residentCount ≤ budget与体总量无关—— ✅ 成立
| budget | 峰值驻留块 | 进程峰值内存 |
|--------|-----------|--------------|
| 64 | **64**(≤budget ✔) | **220 MB** |
| 256 | **256**(≤budget ✔) | 282 MB |
- 驻留块数严格 budget `BrickPager` LRU 保证与体总量2.09 亿体素 / 整卷 398 MB无关
- 对照 renderB 整卷常驻 509 MBrenderC budget=64 220 MB **不随体增大**。内存恒定达成
### 未知 6全分辨率长线体绘制真实 fpsrenderB INVALID 的那个)—— ✅ 真渲出,附实测 fps
| 口径 | budget=64 | 说明 |
|------|-----------|------|
| **静态工作集 fps** | **9.49 fps** | 工作集固定64 仅旋相机 + Render GPU MultiBlock 体绘制 |
| **动态换页 fps** | **1.45 fps** | 每帧 update重选/解压换页+ 重建 mapper + Render |
| 对照 renderB | **INVALID** | 整卷超 3D 纹理上限根本上传不了假帧率 295 不可信 |
**renderB INVALID → renderC 真渲出**非空像素无纹理错命门探针的核心目标达成
fps 离交互级(≥30尚远 §7)。
---
## 2. 关键数据表budget=64主配置
| 指标 | |
|------|-----|
| 体维度 | 44476 × 29 × 162整卷 X 16384renderB=INVALID |
| 体素数 | 208,948,248 |
| budget | 64 |
| 峰值驻留 | **64≤budget内存恒定 OK** |
| 末帧 level / 视野块 / level 总块 | 0 / 456 / 2085 |
| 平均渲染块/ | 64 |
| 纹理维度错误 | **否** |
| 渲出非空像素 | **是**非背景像素 748 |
| 静态工作集 fps | **9.49** |
| 动态换页 fps | **1.45**换页均 177.8 ms/ |
| 进程峰值内存 | **220 MB** |
| 源构造耗时 meta + pager不载整卷 | 28 ms |
| 离屏闸门 | OKRTX 3060OpenGL 4.5 |
---
## 3. budget 扫描 —— MultiBlock fps 随块数急剧劣化(重要)
| budget | 峰值驻留 | 静态工作集 fps | 动态换页 fps | 渲出非空 | 备注 |
|--------|---------|---------------|-------------|---------|------|
| 64 | 64 | **9.49** | 1.45 | 748 | 主配置 |
| 256 | 256 | **0.47** | 0.51 | **否0** | 见下 |
**发现**
1. **fps 与工作集块数近似反比**64 9.5 fps 256 0.47 fps(≈20×)。
`vtkMultiBlockVolumeMapper` 给每块建一个独立 `vtkSmartVolumeMapper`逐块单独
ray-cast + back-to-front 排序开销随块数线性甚至更糟增长。**核外工作集块数必须压到
几十量级靠紧视锥裁剪 + 合理 LOD不能盲目放大 budget。**
2. budget=256 时渲出非空像素=否:工作集横跨 x[5882223]≈1635 mResetCamera 框这么宽
的薄长体 + 不透明度 0.15末帧像素判据落到 0投影过薄/过淡)。这暴露**正确性判据
宽而薄工作集 + 低不透明度敏感**——"没渲"而是末帧那一姿态太淡budget=64
工作集窄x[18172223]≈406 m时稳定非空后续应改用累计多帧非空 or 调不透明度判据
---
## 4. 实现做到哪步(按 brief 要求说明)
- **LOD**已实现相机距离粗分档clamp 到可用层非仅固定 level 0
- **视野块**已实现**视锥裁剪**`vtkCamera::GetFrustumPlanes` + AABB 保守剔除
" level 全部块"。cam==nullptrheadless 测试时回退取全块 budget/LRU 限制
- **渲染**`vtkMultiBlockDataSet` + `vtkMultiBlockVolumeMapper`方案二N 块成一个 multiblock
数据集交单个 mapper」,非手搓 N vtkVolume)。
- **块世界坐标** brief 公式level L 间距 = meta.spacing × 2^Lorigin = meta.origin +
块起始体素 × level 间距块索引/偏移 64 单元测试覆盖 level0/level1 世界坐标
- **fps/内存**静态 + 动态两段 fpsPsapi 峰值内存residentCount换页耗时占比均实测打印
---
## 5. 单元测试headless不需 GPU 部分)
`tests/render/test_outofcore_source.cpp`3 用例全 PASS
- `WorkingSetBricksAreTextureSafeAndBounded`update 后工作集每块各轴 64纹理安全)、
VTK_SHORTresidentCount budget工作集图像数 budget
- `BrickWorldCoordsLevel0`level0 (bx=1) 世界 origin = meta.origin + 64×spacing
spacing == meta.spacing
- `BrickWorldCoordsLevel1Spacing`金字塔 level1 dims = ceil(level0/2),存在 2
---
## 6. 通过判据对照
| 判据 | 结果 |
|------|------|
| 工作集核外体绘制能正确渲出全分辨率长线renderB INVALID 的那个渲出非空 | budget=64非空像素 748无纹理错 |
| 内存恒定residentCount budget | 64/64256/256内存 220/282 MB不随体增大 |
| 接缝可接受或明确缓解 | level 无明显接缝MultiBlock 抖动 level 留待后续 |
| 闪烁可接受或明确缓解 | LOD 切换机制可用平滑度未量化hysteresis 为后续优化 |
| 解压可接受或明确缓解 | **撞墙**换页解压 + 每帧重建 mapper = 177.8 ms/是主瓶颈 §7 缓解 |
核心两条绕开纹理墙真渲出 + 内存恒定**达成**解压热路径**撞墙** brief 如实记录
---
## 7. 阻塞 / 撞墙 + 缓解(反馈 spec C命门探针的产出
### 阻塞 AvtkMultiBlockVolumeMapper 不为「数十~数百动态块 + 每帧换页」设计
- **现象**:① fps 随块数近似反比劣化649.52560.47 fps);② 每帧 `SetInputDataObject`
multiblock 内部 `CreateMappers` 重建所有子 SmartVolumeMapper 并重传纹理叠加
qUncompress 177.8 ms/;③ 静态工作集 9.5 fps 也低于整卷renderB 在能渲的更小体上
可上百 fps)。
- **缓解方向 spec C / Task 14 选型**
1. **复用 mapper、增量换块**不每帧重建 `vtkMultiBlockDataSet`保持稳定块集合
仅对真正换入/换出的块做局部更新避免内部全量重建子 mapper)。
2. **换核外管线**评估 `vtkOpenGLGPUVolumeRayCastMapper::SetPartitions` mapper 沿轴
分区上传绕纹理墙且无 N mapper 的排序/重传开销作为 MultiBlock 的替代
3. **后台解压线程 + 双缓冲** `qUncompress` 移出渲染线程pager 预取 + worker 解压
渲染线程只切换已就绪块杜绝换页阻塞渲染帧
4. **更紧的工作集**视锥裁剪 + LOD 把每帧渲染块压到 ~2040换页量随之降
### 阻塞 B次要正确性像素判据对「宽薄工作集 + 低不透明度」敏感
- budget=256 末帧非空=0 并非没渲而是该姿态投影过薄过淡缓解累计多帧 OR 选定姿态
做非空判据或提高探针不透明度不影响主结论
### 非阻塞观察
- budget 越大 fps 越差与直觉相反)——核外的关键不是缓存大而是**每帧渲染块数小**。
内存恒定靠 LRU 已稳性能靠紧裁剪 + 高效核外管线
---
## 8. 复现命令
```
# 主配置budget=64
build\release\tools\gpr_poc\gpr_poc.exe renderC build\tmp\gpr_store_B_001 --budget 64 --frames 120
# budget 扫描
... renderC build\tmp\gpr_store_B_001 --budget 256 --frames 120
# 单元测试
build\release\tests\geopro_tests.exe --gtest_filter=OutOfCoreSource.*
```
> 直驱运行需 VTK/Qt 运行时 DLL 在 exe 旁(已由 `tools/gpr_poc/CMakeLists.txt` 的
> `TARGET_RUNTIME_DLLS` POST_BUILD 拷贝;自定义 VTK 安装的 DLL 也已落到 exe 目录)。