diff --git a/.superpowers/sdd/task-12d-fix-report.md b/.superpowers/sdd/task-12d-fix-report.md new file mode 100644 index 0000000..349350d --- /dev/null +++ b/.superpowers/sdd/task-12d-fix-report.md @@ -0,0 +1,53 @@ +# Task 12d-fix 报告:修 gpr_poc view 空窗 + 控制台乱码 + +## 状态 +DONE。两 bug 均修复,构建通过(Community vcvars64 直驱 ninja,exit 0),离屏自检通过。 + +## 提交短哈希 +`1495d0e`(feat/vtk-3d-view 分支) + +## 改动文件 +仅 `tools/gpr_poc/main.cpp`(+70 -1)。 + +### Bug 1:概览空窗(LOD 策略错) +- 根因:`view` 每帧 `viewRefreshBlocks` 无脑走分块路径,相机概览时 `pickLevel` 选 level1(696 块)被 budget=64 砍到 64/696(9% 稀疏)→ 看着空。 +- 修复:`viewRefreshBlocks` 按相机选中 level 分流(同 12c renderLOD 已验): + - 相机选中 **level0**(最近、要全分辨率,X=44476 无法成单纹理)→ 分块 + budget(核外 LRU,原路径不变)。 + - 相机选中 **level≥1**(概览/中远)→ `wholeVolumeLevelFor` 从 picked 起向粗找第一个“整卷各轴 ≤16384”的层(本数据 level0/1 的 X=44476/22238>16384 → 升 level2),用 `buildLevelImage` 整卷重组单张 image,单块喂 mapper(忽略 budget,粗层本就小)。整卷 image 按 level 缓存,仅 level 变化时重组。 +- 效果:概览不再是 64/696 稀疏块,而是 **1 个整卷块**渲染完整体。 + +### Bug 2:控制台中文乱码(GBK) +- 修复:`main()` 入口 `#ifdef _WIN32` 下 `SetConsoleOutputCP(CP_UTF8);`(含 ``)。保留全文件已有中文输出,全子命令受益。 + +## 离屏自检结果(view --smoke,tmp\store_lod_001) +修复前: +``` +[view] 预热: level=1 视野块=696/696 驻留=64 渲染块=64 ← 64/696 稀疏 +``` +修复后: +``` +[view] 预热: level=1 视野块=696/696 驻留=64 渲染块=1 ← 整卷单块(升 level2) +=== view --smoke 离屏冒烟 === +近观 level=1 → 拉远 level=3 → 再拉近 level=1 +LOD 随缩放切换 : 是 ✔ (blocksFar=1) +纹理维度错误 : 否 +渲出非空像素 : 是 (近=1024000 远拉近=1024000) +smoke 结果 : OK ✔ 不崩 +``` +- **概览渲染块 64 → 1(整卷)**:核心修复,整卷完整渲染而非 9% 稀疏。 +- 渲出非空像素:是(1024000,无纹理错、不崩)。注:该视角整卷与原稀疏块均填满帧,像素计数饱和,故区分性证据是“渲染块 64→1(整卷)”。 +- **编码正常**:`=== view --smoke 离屏冒烟 ===` 等中文在 UTF-8 控制台正确显示,无 GBK 乱码。 + +## 提交干净性确认 +- `git diff --cached --stat` 提交前确认 index 仅含 `tools/gpr_poc/main.cpp`,无 chart/scatter/quill/rangeslider/Dialog/FormK 等并行会话文件。 +- 仅 `git add tools/gpr_poc/main.cpp`(及本报告),绝无 `git add -A`。 + +## 给用户的重跑命令 +真窗口交互(开窗即见完整粗层体,滚轮拉近变清晰/分块): +``` +build\release\tools\gpr_poc\gpr_poc.exe view tmp\store_lod_001 --exagg 8 --opacity 0.6 +``` +离屏自检: +``` +build\release\tools\gpr_poc\gpr_poc.exe view tmp\store_lod_001 --exagg 8 --opacity 0.6 --smoke +``` diff --git a/tools/gpr_poc/main.cpp b/tools/gpr_poc/main.cpp index 3f21156..082b786 100644 --- a/tools/gpr_poc/main.cpp +++ b/tools/gpr_poc/main.cpp @@ -73,6 +73,10 @@ #include #include +#ifdef _WIN32 +#include // SetConsoleOutputCP(修中文控制台 GBK 乱码) +#endif + namespace fs = std::filesystem; using geopro::tools::Probe; using geopro::tools::Stopwatch; @@ -2209,9 +2213,13 @@ int cmdFpsBudget(int argc, char** argv) { // // 离屏 smoke:--smoke 时不开真窗口,只离屏建管线 + 渲一帧 + 验非空像素,确保不崩。 +// 整卷单张 3D 纹理的轴上限(同 renderLOD/renderB 实测 GL_MAX_3D_TEXTURE_SIZE)。 +constexpr int kViewMax3DTex = 16384; + // view 的每帧回调共享状态(挂到 interactor 的 EndInteraction/Timer/Render 上)。 struct ViewState { geopro::render::OutOfCoreSource* src = nullptr; + geopro::data::ChunkedVolumeStore* store = nullptr; // 整卷粗层渲染走它 vtkMultiBlockVolumeMapper* mapper = nullptr; vtkCamera* cam = nullptr; vtkTextActor* fpsText = nullptr; @@ -2219,11 +2227,65 @@ struct ViewState { Stopwatch frameTimer; double exagg = 8.0; int lastLevel = -1; + // 整卷粗层 image 缓存(按 level 缓存,避免每帧重组整卷)。 + int cachedWholeLevel = -1; + vtkSmartPointer cachedWholeImg; }; -// 用 source 当前工作集刷新 mapper 输入(每块成 MultiBlock)。返回块数。 +// 某 level 整卷各轴是否都 ≤16384(可成单张 3D 纹理 → 整卷单 mapper 渲染)。 +bool levelFitsSingleTexture(const geopro::data::ChunkedVolumeStore& store, + int level) { + int nx = 0, ny = 0, nz = 0; + store.dims(level, nx, ny, nz); + return nx <= kViewMax3DTex && ny <= kViewMax3DTex && nz <= kViewMax3DTex; +} + +// 给定相机选中的 level,返回真正用于整卷渲染的 level:从 picked 起向粗逐层找, +// 取第一个整卷各轴 ≤16384 的层(如 level0/1 长线 X 超 16384,则升到 level2)。 +// 找不到(极端情况)返回 -1,调用方退回分块路径。 +int wholeVolumeLevelFor(const geopro::data::ChunkedVolumeStore& store, + int picked) { + const int maxLevel = store.levels() - 1; + for (int lv = std::max(0, picked); lv <= maxLevel; ++lv) { + if (levelFitsSingleTexture(store, lv)) { + return lv; + } + } + return -1; +} + +// 用 source 当前工作集刷新 mapper 输入。返回喂给 mapper 的块数。 +// +// 策略(同 12c renderLOD 已验,修概览空窗): +// - 概览/中远视角(相机选中粗层)→ 升到最细的“整卷各轴 ≤16384”层,整卷重组成 +// 单张 image 单块喂 mapper(忽略 budget,粗层本就小),任何缩放都显示完整体, +// 不再是 budget 砍后 9% 稀疏。本数据 level0/1 的 X(44476/22238)>16384 → 升 level2。 +// - 只有拉近到要全分辨率(相机选中 level0,X=44476 无法成单纹理)→ 退回分块 + +// budget 路径(核外 LRU 驻留,只渲视野内子区域)。 std::size_t viewRefreshBlocks(ViewState* st) { + // 先让 source 按相机选好 LOD(同时刷新 lastLevel/视野统计供 fps 文本用)。 st->src->update(st->cam); + const int picked = st->src->lastLevel(); + + // 仅当相机选中 level0(最近、要全分辨率)才分块;其余(概览/中远)整卷渲染。 + if (st->store != nullptr && picked > 0) { + const int wlv = wholeVolumeLevelFor(*st->store, picked); + if (wlv >= 0) { + // 整卷粗层:按 level 缓存整卷 image,仅在 level 变化时重组。 + if (st->cachedWholeLevel != wlv || st->cachedWholeImg == nullptr) { + st->cachedWholeImg = + buildLevelImage(*st->store, wlv, st->store->meta()); + st->cachedWholeLevel = wlv; + } + std::vector> one{st->cachedWholeImg}; + auto mb = makeMultiBlock(one); + st->mapper->SetInputDataObject(mb); + st->mapper->Update(); + return one.size(); + } + } + + // 全分辨率长线:分块 + budget。 auto imgs = st->src->currentImages(); auto mb = makeMultiBlock(imgs); st->mapper->SetInputDataObject(mb); @@ -2272,6 +2334,8 @@ int cmdView(int argc, char** argv) { // 核外源(读 meta + 建 pager,不载整卷)。 geopro::render::OutOfCoreSource src(dir, budget); + // 整卷粗层渲染另开 store(粗层各轴 ≤16384 时整卷单 mapper 渲,绕过 budget 稀疏)。 + geopro::data::ChunkedVolumeStore store(dir); const auto& m = src.meta(); src.setAspect(static_cast(winW) / winH); const double vmin = m.vminPhys, vmax = m.vmaxPhys; @@ -2316,6 +2380,7 @@ int cmdView(int argc, char** argv) { ViewState st; st.src = &src; + st.store = &store; st.mapper = mapper.Get(); st.fpsText = fpsText.Get(); st.rw = rw.Get(); @@ -2431,6 +2496,10 @@ void usage() { } // namespace int main(int argc, char** argv) { +#ifdef _WIN32 + // Windows 控制台默认 GBK,会把 UTF-8 中文输出显示为乱码。设为 UTF-8 码页修复。 + SetConsoleOutputCP(CP_UTF8); +#endif if (argc < 2) { usage(); return 2;