fix(gpr_poc): view 默认取景改局部段+加 --preview+fps 显真实帧率
修 view 开窗空白:默认相机框整条 2.2km 线(横截面 1:34),即便 exagg=8 也是隐形细带。三处修复: 1. 默认取景改对准沿线中段一个 ~256 道(4 brick 列)的全分辨率局部体, ResetCamera+Zoom 框满画面,首帧即见层状结构(同 lod-tuned-local.png 取景)。 2. 新增 view --preview:用与真窗口完全相同的相机/source/exagg/传函离屏渲一帧 存 view-default.png,旋 N 帧报真实 fps+结构像素数(排除深蓝灰背景)+纹理错。 3. fps 文本改为松手时连渲 3 帧累计实际 Render 耗时取均值,不再把空闲间隔算进去。 实测 preview:默认视角有结构(结构像素>0)、无纹理错、真实渲染 ~185fps。
This commit is contained in:
parent
e62e2cdc8d
commit
b1a8d1365d
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
|
|
@ -2224,7 +2224,6 @@ struct ViewState {
|
||||||
vtkCamera* cam = nullptr;
|
vtkCamera* cam = nullptr;
|
||||||
vtkTextActor* fpsText = nullptr;
|
vtkTextActor* fpsText = nullptr;
|
||||||
vtkRenderWindow* rw = nullptr;
|
vtkRenderWindow* rw = nullptr;
|
||||||
Stopwatch frameTimer;
|
|
||||||
double exagg = 8.0;
|
double exagg = 8.0;
|
||||||
int lastLevel = -1;
|
int lastLevel = -1;
|
||||||
// 整卷粗层 image 缓存(按 level 缓存,避免每帧重组整卷)。
|
// 整卷粗层 image 缓存(按 level 缓存,避免每帧重组整卷)。
|
||||||
|
|
@ -2296,16 +2295,26 @@ std::size_t viewRefreshBlocks(ViewState* st) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// interactor 回调:每次交互(旋转/缩放)结束后重选 LOD + 刷新 fps 文本。
|
// interactor 回调:每次交互(旋转/缩放)结束后重选 LOD + 刷新 fps 文本。
|
||||||
|
//
|
||||||
|
// fps 修复(Task 12d-fix3):之前用 frameTimer(上次回调到本次的墙钟)算 fps,把
|
||||||
|
// 用户思考/不动的空闲时间也算进去,显示的是「空闲间隔」(如 0.2fps),不可信。改为
|
||||||
|
// 松手时连渲 kFpsProbeFrames 帧、累计「实际 Render 耗时」取均值,得到真实渲染帧率。
|
||||||
void viewOnInteract(vtkObject*, unsigned long, void* clientData, void*) {
|
void viewOnInteract(vtkObject*, unsigned long, void* clientData, void*) {
|
||||||
auto* st = static_cast<ViewState*>(clientData);
|
auto* st = static_cast<ViewState*>(clientData);
|
||||||
// 防重入:本回调内部会 st->rw->Render(),若该 Render 再触发观察者进本回调
|
// 防重入:本回调内部会 st->rw->Render(),若该 Render 再触发观察者进本回调
|
||||||
// 将无限递归。已在回调中则直接返回(双保险)。
|
// 将无限递归。已在回调中则直接返回(双保险)。
|
||||||
if (st->inCb) return;
|
if (st->inCb) return;
|
||||||
st->inCb = true;
|
st->inCb = true;
|
||||||
const double frameMs = st->frameTimer.elapsedMs();
|
|
||||||
const std::size_t blocks = viewRefreshBlocks(st);
|
const std::size_t blocks = viewRefreshBlocks(st);
|
||||||
const int lvl = st->src->lastLevel();
|
const int lvl = st->src->lastLevel();
|
||||||
const double fps = frameMs > 0 ? 1000.0 / frameMs : 0.0;
|
|
||||||
|
// 真实渲染帧率:连渲若干帧,只累计 Render() 本身耗时(不含空闲)。首帧含切换后
|
||||||
|
// 的纹理上传/shader 编译,故跑 kFpsProbeFrames 帧取均值更可信。
|
||||||
|
constexpr int kFpsProbeFrames = 3;
|
||||||
|
Stopwatch swR;
|
||||||
|
for (int i = 0; i < kFpsProbeFrames; ++i) st->rw->Render();
|
||||||
|
const double renderMs = swR.elapsedMs() / kFpsProbeFrames;
|
||||||
|
const double fps = renderMs > 0 ? 1000.0 / renderMs : 0.0;
|
||||||
|
|
||||||
char buf[256];
|
char buf[256];
|
||||||
std::snprintf(buf, sizeof(buf),
|
std::snprintf(buf, sizeof(buf),
|
||||||
|
|
@ -2313,16 +2322,58 @@ void viewOnInteract(vtkObject*, unsigned long, void* clientData, void*) {
|
||||||
fps, lvl, blocks, st->exagg);
|
fps, lvl, blocks, st->exagg);
|
||||||
st->fpsText->SetInput(buf);
|
st->fpsText->SetInput(buf);
|
||||||
st->lastLevel = lvl;
|
st->lastLevel = lvl;
|
||||||
st->rw->Render();
|
st->rw->Render(); // 末帧带上更新后的 fps 文本
|
||||||
st->frameTimer.reset();
|
|
||||||
st->inCb = false;
|
st->inCb = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 默认取景宽度:沿测线取约 256 道(=4 brick 列×64)的一段作首帧局部段。整线横截面
|
||||||
|
// 相对长度 1:34,框整卷只会看到一条隐形细带;框这个局部段,层状结构才充满视野
|
||||||
|
// (用户可再滚轮拉远看整体——细带是物理真实,拉近看细节)。段越宽 X 越细长、截面
|
||||||
|
// 越填不满画面;256 道是 ① cmdTune 出 lod-tuned-local.png(有清晰层状结构)的取景,
|
||||||
|
// 沿用之以保证首帧同等可读。
|
||||||
|
constexpr int kViewDefaultLocalBricks = 4;
|
||||||
|
// ResetCamera 后再 Zoom 拉近填满画面(同 ① cmdTune 的 1.7×:默认框得偏松,留白多)。
|
||||||
|
constexpr double kViewDefaultZoom = 1.7;
|
||||||
|
|
||||||
|
// 建立 view 的「默认取景」:把 level0 一段局部体(沿线中段)整卷单块喂 mapper,再
|
||||||
|
// ResetCamera 到该局部段(actor 已 SetScale(1,exagg,exagg)),置相机为能看出层状
|
||||||
|
// 结构的角度。真窗口 / --smoke / --preview 三条路径共用此函数 → 渲的是同一画面。
|
||||||
|
//
|
||||||
|
// 返回喂给 mapper 的块数(=1)。同步更新 st->lastLevel=0(默认即全分辨率局部段)。
|
||||||
|
std::size_t viewSetupDefaultFrame(ViewState* st, vtkRenderer* ren) {
|
||||||
|
geopro::data::ChunkedVolumeStore& store = *st->store;
|
||||||
|
const geopro::data::StoreMeta& m = store.meta();
|
||||||
|
const int totBx = store.bricksX(0);
|
||||||
|
const int localBx = std::min(kViewDefaultLocalBricks, totBx);
|
||||||
|
const int bx0 = std::max(0, totBx / 2 - localBx / 2); // 沿线中段
|
||||||
|
vtkSmartPointer<vtkImageData> locImg =
|
||||||
|
buildLocalLevel0Image(store, m, bx0, localBx);
|
||||||
|
|
||||||
|
std::vector<vtkSmartPointer<vtkImageData>> one{locImg};
|
||||||
|
auto mb = makeMultiBlock(one);
|
||||||
|
st->mapper->SetInputDataObject(mb);
|
||||||
|
st->mapper->Update();
|
||||||
|
st->cachedWholeImg = locImg; // 持有引用,避免被释放
|
||||||
|
st->cachedWholeLevel = 0;
|
||||||
|
st->lastLevel = 0;
|
||||||
|
|
||||||
|
// 框住局部段:用无参 ResetCamera(按 actor 的【已 SetScale(1,exagg,exagg)】缩放
|
||||||
|
// 后包围盒框,把 exagg 后的 Y/Z 一并纳入;mapper->GetBounds() 是未缩放的,不可用),
|
||||||
|
// 相机角度沿用能看出结构的 Elevation/Azimuth,再 Zoom 拉近填满画面。
|
||||||
|
ren->ResetCamera();
|
||||||
|
st->cam = ren->GetActiveCamera();
|
||||||
|
st->cam->Elevation(28.0); // 同 ① cmdTune 的取景角度
|
||||||
|
st->cam->Azimuth(30.0);
|
||||||
|
st->cam->Zoom(kViewDefaultZoom); // 拉近填满画面,避免局部段缩在角落
|
||||||
|
ren->ResetCameraClippingRange();
|
||||||
|
return one.size();
|
||||||
|
}
|
||||||
|
|
||||||
int cmdView(int argc, char** argv) {
|
int cmdView(int argc, char** argv) {
|
||||||
const Args a = parseArgs(argc, argv, 2);
|
const Args a = parseArgs(argc, argv, 2);
|
||||||
if (a.positional.empty()) {
|
if (a.positional.empty()) {
|
||||||
std::cerr << "用法: gpr_poc view <storeDir> [--exagg 8] [--opacity 0.5] "
|
std::cerr << "用法: gpr_poc view <storeDir> [--exagg 8] [--opacity 0.5] "
|
||||||
"[--budget 64] [--smoke]\n";
|
"[--budget 64] [--smoke] [--preview] [--frames 90]\n";
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
const std::string dir = a.positional[0];
|
const std::string dir = a.positional[0];
|
||||||
|
|
@ -2330,13 +2381,22 @@ int cmdView(int argc, char** argv) {
|
||||||
const double opacity = std::stod(a.get("opacity", "0.5"));
|
const double opacity = std::stod(a.get("opacity", "0.5"));
|
||||||
const std::size_t budget =
|
const std::size_t budget =
|
||||||
static_cast<std::size_t>(std::stoul(a.get("budget", "64")));
|
static_cast<std::size_t>(std::stoul(a.get("budget", "64")));
|
||||||
const bool smoke = a.kv.count("smoke") > 0 ||
|
const int frames = std::stoi(a.get("frames", "90"));
|
||||||
|
auto hasFlag = [&](const char* name) {
|
||||||
|
return a.kv.count(name) > 0 ||
|
||||||
std::find(a.positional.begin(), a.positional.end(),
|
std::find(a.positional.begin(), a.positional.end(),
|
||||||
"--smoke") != a.positional.end();
|
std::string("--") + name) != a.positional.end();
|
||||||
|
};
|
||||||
|
const bool smoke = hasFlag("smoke");
|
||||||
|
const bool preview = hasFlag("preview");
|
||||||
std::cout << "[view] storeDir=" << dir << " exagg=" << exagg
|
std::cout << "[view] storeDir=" << dir << " exagg=" << exagg
|
||||||
<< " opacity=" << opacity << " budget=" << budget
|
<< " opacity=" << opacity << " budget=" << budget
|
||||||
<< (smoke ? " [SMOKE 离屏]" : " [真窗口交互]") << "\n";
|
<< (preview ? " [PREVIEW 离屏存图+测fps]"
|
||||||
|
: (smoke ? " [SMOKE 离屏]" : " [真窗口交互]"))
|
||||||
|
<< "\n";
|
||||||
|
|
||||||
|
// preview/smoke 走离屏。
|
||||||
|
const bool offscreen = smoke || preview;
|
||||||
const int winW = 1280, winH = 800;
|
const int winW = 1280, winH = 800;
|
||||||
|
|
||||||
// 核外源(读 meta + 建 pager,不载整卷)。
|
// 核外源(读 meta + 建 pager,不载整卷)。
|
||||||
|
|
@ -2350,9 +2410,9 @@ int cmdView(int argc, char** argv) {
|
||||||
vtkSmartPointer<vtkVolumeProperty> prop =
|
vtkSmartPointer<vtkVolumeProperty> prop =
|
||||||
makeTunedVolumeProperty(m.quant, cs, vmin, vmax, opacity);
|
makeTunedVolumeProperty(m.quant, cs, vmin, vmax, opacity);
|
||||||
|
|
||||||
// 渲染窗口:smoke 走离屏,否则真窗口。
|
// 渲染窗口:preview/smoke 走离屏,否则真窗口。
|
||||||
vtkSmartPointer<vtkRenderWindow> rw;
|
vtkSmartPointer<vtkRenderWindow> rw;
|
||||||
if (smoke) {
|
if (offscreen) {
|
||||||
rw = makeOffscreenWindow(winW, winH);
|
rw = makeOffscreenWindow(winW, winH);
|
||||||
} else {
|
} else {
|
||||||
rw = vtkSmartPointer<vtkRenderWindow>::New();
|
rw = vtkSmartPointer<vtkRenderWindow>::New();
|
||||||
|
|
@ -2393,34 +2453,84 @@ int cmdView(int argc, char** argv) {
|
||||||
st.rw = rw.Get();
|
st.rw = rw.Get();
|
||||||
st.exagg = exagg;
|
st.exagg = exagg;
|
||||||
|
|
||||||
// 相机初始定向:先框整体选出工作集,再 ResetCamera 到工作集包围盒(同 renderC)。
|
// 相机初始定向(修复 1):默认框「局部段」而非整卷。整线横截面 1:34,框整卷
|
||||||
ren->ResetCamera(m.origin[0], m.origin[0] + m.nx * m.spacing[0],
|
// 即便 exagg=8 也是一条隐形细带(看着空白);改为对准沿线中段一个 ~768 道窗口
|
||||||
m.origin[1], m.origin[1] + m.ny * m.spacing[1] * exagg,
|
// 的全分辨率局部体 → 开窗第一帧就看到一段有层状结构的体。三路径共用此取景。
|
||||||
m.origin[2], m.origin[2] + m.nz * m.spacing[2] * exagg);
|
const std::size_t warm = viewSetupDefaultFrame(&st, ren);
|
||||||
st.cam = ren->GetActiveCamera();
|
|
||||||
|
|
||||||
const std::size_t warm = viewRefreshBlocks(&st);
|
|
||||||
{
|
|
||||||
double b[6];
|
|
||||||
mapper->GetBounds(b);
|
|
||||||
if (b[0] <= b[1]) {
|
|
||||||
// 工作集包围盒需按 exagg 缩放后再框(actor 已 SetScale)。
|
|
||||||
ren->ResetCamera();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
st.cam->Elevation(25.0);
|
|
||||||
st.cam->Azimuth(25.0);
|
|
||||||
ren->ResetCameraClippingRange();
|
|
||||||
rw->Render();
|
rw->Render();
|
||||||
|
|
||||||
std::cout << "[view] 预热: level=" << src.lastLevel() << " 视野块="
|
std::cout << "[view] 预热(默认局部段): level=" << st.lastLevel
|
||||||
<< src.lastVisibleCount() << "/" << src.lastLevelBrickTotal()
|
<< " 渲染块=" << warm << "\n";
|
||||||
<< " 驻留=" << src.residentCount() << " 渲染块=" << warm << "\n";
|
|
||||||
|
|
||||||
const vtkIdType nonBlack = countNonBlackPixels(rw.Get(), winW, winH);
|
const vtkIdType nonBlack = countNonBlackPixels(rw.Get(), winW, winH);
|
||||||
const bool textureErr = capWin->textureError();
|
const bool textureErr = capWin->textureError();
|
||||||
const bool renderedOk = !textureErr && nonBlack > 0;
|
const bool renderedOk = !textureErr && nonBlack > 0;
|
||||||
|
|
||||||
|
if (preview) {
|
||||||
|
// 修复 2:用与真窗口完全相同的默认相机/source/exagg/传函(viewSetupDefaultFrame
|
||||||
|
// 已建好),离屏渲一帧存图 → 控制方先 Read 确认开窗默认画面非空、有结构。
|
||||||
|
const fs::path shotDir =
|
||||||
|
fs::path("docs") / "superpowers" / "plans" / "poc-lod-shots";
|
||||||
|
fs::create_directories(shotDir);
|
||||||
|
const std::string pngPath = (shotDir / "view-default.png").string();
|
||||||
|
savePng(rw.Get(), pngPath);
|
||||||
|
// 结构像素计数:背景为深蓝灰(R/G≈10,B≈20),countNonBlackPixels(>10) 会把整屏
|
||||||
|
// 背景都算「非空」,对验证「画面有结构」无意义。改为只数明显亮于背景的像素
|
||||||
|
// (任一通道 >50),作为「确有渲出的体结构」的诚实判据。
|
||||||
|
auto countStructPixels = [&]() -> vtkIdType {
|
||||||
|
auto px = vtkSmartPointer<vtkUnsignedCharArray>::New();
|
||||||
|
rw->GetRGBACharPixelData(0, 0, winW - 1, winH - 1, /*front=*/1, px);
|
||||||
|
vtkIdType n = 0;
|
||||||
|
const vtkIdType np = px->GetNumberOfTuples();
|
||||||
|
for (vtkIdType i = 0; i < np; ++i) {
|
||||||
|
if (px->GetComponent(i, 0) > 50 || px->GetComponent(i, 1) > 50 ||
|
||||||
|
px->GetComponent(i, 2) > 50) {
|
||||||
|
++n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
const vtkIdType defStruct = countStructPixels();
|
||||||
|
|
||||||
|
// 旋相机 N 帧测真实 fps(非首帧:首帧含纹理上传/shader 编译已在预热完成)。
|
||||||
|
rw->Render(); // 再预热一帧,确保管线热
|
||||||
|
Stopwatch sw;
|
||||||
|
for (int f = 0; f < frames; ++f) {
|
||||||
|
st.cam->Azimuth(360.0 / frames);
|
||||||
|
rw->Render();
|
||||||
|
}
|
||||||
|
const double ms = sw.elapsedMs();
|
||||||
|
const double fps = ms > 0 ? frames * 1000.0 / ms : 0.0;
|
||||||
|
|
||||||
|
const bool texErr2 = capWin->textureError();
|
||||||
|
vtkOutputWindow::SetInstance(nullptr);
|
||||||
|
const bool ok = !texErr2 && defStruct > 0;
|
||||||
|
|
||||||
|
std::cout << "\n=== view --preview 离屏默认视角验证 ===\n";
|
||||||
|
std::cout << "默认局部段维度 : " << kViewDefaultLocalBricks
|
||||||
|
<< " brick 列(沿线中段) level0\n";
|
||||||
|
std::cout << "存图 : " << pngPath << "\n";
|
||||||
|
std::cout << "结构像素(>50) : " << defStruct << " / " << (winW * winH)
|
||||||
|
<< " (" << (100.0 * defStruct / (winW * winH))
|
||||||
|
<< "%, 已排除深蓝灰背景)\n";
|
||||||
|
std::cout << "纹理维度错误 : " << (texErr2 ? "是(!!)" : "否") << "\n";
|
||||||
|
std::cout << "真实渲染 fps : " << (ok ? std::to_string(fps) : "INVALID")
|
||||||
|
<< " (" << frames << " 帧旋相机, 非首帧)\n";
|
||||||
|
std::cout << "preview 结果 : "
|
||||||
|
<< (ok ? "OK ✔ 默认视角有结构" : "FAIL ✘") << "\n";
|
||||||
|
|
||||||
|
writeMetricLine(
|
||||||
|
"view-preview,dir=" + dir + ",exagg=" + std::to_string(exagg) +
|
||||||
|
",opacity=" + std::to_string(opacity) +
|
||||||
|
",localBricks=" + std::to_string(kViewDefaultLocalBricks) +
|
||||||
|
",structPixels=" + std::to_string(defStruct) +
|
||||||
|
",fps=" + (ok ? std::to_string(fps) : "INVALID") +
|
||||||
|
",textureErr=" + std::to_string(texErr2 ? 1 : 0) +
|
||||||
|
",ok=" + std::to_string(ok ? 1 : 0) +
|
||||||
|
",png=" + pngPath);
|
||||||
|
return ok ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (smoke) {
|
if (smoke) {
|
||||||
// 离屏 smoke:模拟一次缩放 → 验 LOD 切换 + 不崩。
|
// 离屏 smoke:模拟一次缩放 → 验 LOD 切换 + 不崩。
|
||||||
const int lvlNear = src.lastLevel();
|
const int lvlNear = src.lastLevel();
|
||||||
|
|
@ -2472,7 +2582,6 @@ int cmdView(int argc, char** argv) {
|
||||||
iren->AddObserver(vtkCommand::EndInteractionEvent, cb);
|
iren->AddObserver(vtkCommand::EndInteractionEvent, cb);
|
||||||
|
|
||||||
std::cout << "[view] 打开真窗口。左键旋转 / 滚轮缩放(切 LOD) / q 退出。\n";
|
std::cout << "[view] 打开真窗口。左键旋转 / 滚轮缩放(切 LOD) / q 退出。\n";
|
||||||
st.frameTimer.reset();
|
|
||||||
iren->Initialize();
|
iren->Initialize();
|
||||||
rw->Render();
|
rw->Render();
|
||||||
iren->Start();
|
iren->Start();
|
||||||
|
|
@ -2497,7 +2606,7 @@ void usage() {
|
||||||
" gpr_poc fps-budget <storeDir> [--frames 90] "
|
" gpr_poc fps-budget <storeDir> [--frames 90] "
|
||||||
"[--bricks 4,16,64,128,256]\n"
|
"[--bricks 4,16,64,128,256]\n"
|
||||||
" gpr_poc view <storeDir> [--exagg 8] [--opacity 0.5] "
|
" gpr_poc view <storeDir> [--exagg 8] [--opacity 0.5] "
|
||||||
"[--budget 64] [--smoke]\n";
|
"[--budget 64] [--smoke] [--preview] [--frames 90]\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue