|
|
|
|
@ -8,6 +8,8 @@
|
|
|
|
|
// gpr_poc build <dir> [--line 001] [--cellXY 0.2] [--cellZ 0.05] [--out <storeDir>] [--levels 2]
|
|
|
|
|
// gpr_poc load <storeDir>
|
|
|
|
|
// gpr_poc selftest
|
|
|
|
|
// gpr_poc offscreen-smoke —— 离屏 GL 闸门冒烟
|
|
|
|
|
// gpr_poc renderB <storeDir> [--frames 120] —— 离屏体绘制/切片 fps 基准
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
@ -25,11 +27,35 @@
|
|
|
|
|
#include "core/algo/GprVolumeBuilder.hpp"
|
|
|
|
|
#include "core/algo/IInterpolator.hpp"
|
|
|
|
|
#include "core/model/GprSurvey.hpp"
|
|
|
|
|
#include "core/model/ColorScale.hpp"
|
|
|
|
|
#include "core/model/ScalarVolumeI16.hpp"
|
|
|
|
|
#include "data/store/ChunkedVolumeStore.hpp"
|
|
|
|
|
#include "io/gpr/GprSurveyAssembler.hpp"
|
|
|
|
|
#include "render/actors/VoxelActor.hpp"
|
|
|
|
|
#include "render/source/WholeVolumeSource.hpp"
|
|
|
|
|
|
|
|
|
|
// ---- VTK 离屏渲染 ----
|
|
|
|
|
#include <vtkActor.h>
|
|
|
|
|
#include <vtkCamera.h>
|
|
|
|
|
#include <vtkCubeSource.h>
|
|
|
|
|
#include <vtkImageActor.h>
|
|
|
|
|
#include <vtkImageData.h>
|
|
|
|
|
#include <vtkImageMapToColors.h>
|
|
|
|
|
#include <vtkImageMapper3D.h>
|
|
|
|
|
#include <vtkImageReslice.h>
|
|
|
|
|
#include <vtkLookupTable.h>
|
|
|
|
|
#include <vtkNew.h>
|
|
|
|
|
#include <vtkObjectFactory.h>
|
|
|
|
|
#include <vtkOutputWindow.h>
|
|
|
|
|
#include <vtkPolyDataMapper.h>
|
|
|
|
|
#include <vtkProperty.h>
|
|
|
|
|
#include <vtkRenderWindow.h>
|
|
|
|
|
#include <vtkRenderer.h>
|
|
|
|
|
#include <vtkSmartPointer.h>
|
|
|
|
|
#include <vtkSmartVolumeMapper.h>
|
|
|
|
|
#include <vtkUnsignedCharArray.h>
|
|
|
|
|
#include <vtkVolume.h>
|
|
|
|
|
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
using geopro::tools::Probe;
|
|
|
|
|
using geopro::tools::Stopwatch;
|
|
|
|
|
@ -416,12 +442,364 @@ int cmdSelftest() {
|
|
|
|
|
return ok ? 0 : 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// 离屏 GPU 渲染基准(POC-B)
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
// 捕获 VTK 错误输出的 OutputWindow:用于侦测体绘制时 vtkVolumeTexture 报的
|
|
|
|
|
// "Invalid texture dimensions" / "MAX_3D_TEXTURE_SIZE" —— 一旦出现,说明整卷
|
|
|
|
|
// 单张 3D 纹理上传失败,体绘制 fps 无意义,必须如实标 INVALID(绝不当真上报)。
|
|
|
|
|
class CapturingOutputWindow : public vtkOutputWindow {
|
|
|
|
|
public:
|
|
|
|
|
static CapturingOutputWindow* New();
|
|
|
|
|
vtkTypeMacro(CapturingOutputWindow, vtkOutputWindow);
|
|
|
|
|
|
|
|
|
|
void DisplayText(const char* txt) override {
|
|
|
|
|
if (txt) {
|
|
|
|
|
const std::string s(txt);
|
|
|
|
|
captured_ += s;
|
|
|
|
|
if (s.find("texture dimensions") != std::string::npos ||
|
|
|
|
|
s.find("MAX_3D_TEXTURE_SIZE") != std::string::npos) {
|
|
|
|
|
textureError_ = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 仍透传到 stderr,便于人工查看。
|
|
|
|
|
if (txt) std::cerr << txt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool textureError() const { return textureError_; }
|
|
|
|
|
const std::string& captured() const { return captured_; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
std::string captured_;
|
|
|
|
|
bool textureError_ = false;
|
|
|
|
|
};
|
|
|
|
|
vtkStandardNewMacro(CapturingOutputWindow);
|
|
|
|
|
|
|
|
|
|
// 创建一个离屏 vtkRenderWindow(VTK9.6:SetShowWindow(false)+OffScreenRenderingOn)。
|
|
|
|
|
vtkSmartPointer<vtkRenderWindow> makeOffscreenWindow(int w, int h) {
|
|
|
|
|
auto rw = vtkSmartPointer<vtkRenderWindow>::New();
|
|
|
|
|
rw->SetOffScreenRendering(1);
|
|
|
|
|
rw->SetShowWindow(false);
|
|
|
|
|
rw->SetSize(w, h);
|
|
|
|
|
return rw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 闸门:最小离屏渲染冒烟。返回 0=OK,非 0=离屏 GL 起不来(BLOCKED_OFFSCREEN)。
|
|
|
|
|
// 流程:离屏窗口 → 加一个 cube actor → Render() → 读回像素,确认非全黑/读得到。
|
|
|
|
|
int cmdOffscreenSmoke() {
|
|
|
|
|
std::cout << "[offscreen-smoke] 创建离屏 vtkRenderWindow...\n";
|
|
|
|
|
try {
|
|
|
|
|
auto rw = makeOffscreenWindow(256, 256);
|
|
|
|
|
|
|
|
|
|
vtkNew<vtkRenderer> ren;
|
|
|
|
|
ren->SetBackground(0.1, 0.1, 0.2);
|
|
|
|
|
rw->AddRenderer(ren);
|
|
|
|
|
|
|
|
|
|
vtkNew<vtkCubeSource> cube;
|
|
|
|
|
cube->SetXLength(1.0);
|
|
|
|
|
cube->SetYLength(1.0);
|
|
|
|
|
cube->SetZLength(1.0);
|
|
|
|
|
|
|
|
|
|
vtkNew<vtkPolyDataMapper> mapper;
|
|
|
|
|
mapper->SetInputConnection(cube->GetOutputPort());
|
|
|
|
|
|
|
|
|
|
vtkNew<vtkActor> actor;
|
|
|
|
|
actor->SetMapper(mapper);
|
|
|
|
|
actor->GetProperty()->SetColor(1.0, 0.6, 0.2);
|
|
|
|
|
ren->AddActor(actor);
|
|
|
|
|
ren->ResetCamera();
|
|
|
|
|
|
|
|
|
|
// Render():若 GL 上下文创建失败,VTK 会输出错误(多数返回,少数抛)。
|
|
|
|
|
rw->Render();
|
|
|
|
|
|
|
|
|
|
// 读回像素验证:取整窗 RGB,确认能读到且非全 0。
|
|
|
|
|
const int* sz = rw->GetSize();
|
|
|
|
|
const int w = sz[0], h = sz[1];
|
|
|
|
|
if (w <= 0 || h <= 0) {
|
|
|
|
|
std::cout << "[offscreen-smoke] FAIL: 窗口尺寸为 0(上下文未建立)\n";
|
|
|
|
|
std::cout << "STATUS=BLOCKED_OFFSCREEN\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto pixels = vtkSmartPointer<vtkUnsignedCharArray>::New();
|
|
|
|
|
// GetRGBACharPixelData(x0,y0,x1,y1,front,arr):front=1 读前缓冲。
|
|
|
|
|
const int ok =
|
|
|
|
|
rw->GetRGBACharPixelData(0, 0, w - 1, h - 1, /*front=*/1, pixels);
|
|
|
|
|
if (ok == 0 || pixels->GetNumberOfTuples() == 0) {
|
|
|
|
|
std::cout << "[offscreen-smoke] FAIL: 读不到像素\n";
|
|
|
|
|
std::cout << "STATUS=BLOCKED_OFFSCREEN\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 统计非背景像素(cube 应渲出橙色,存在像素 R 通道明显高于背景)。
|
|
|
|
|
vtkIdType nonBlack = 0;
|
|
|
|
|
const vtkIdType n = pixels->GetNumberOfTuples();
|
|
|
|
|
for (vtkIdType i = 0; i < n; ++i) {
|
|
|
|
|
const double r = pixels->GetComponent(i, 0);
|
|
|
|
|
const double g = pixels->GetComponent(i, 1);
|
|
|
|
|
const double b = pixels->GetComponent(i, 2);
|
|
|
|
|
if (r > 80 || g > 80 || b > 80) ++nonBlack;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* caps = rw->ReportCapabilities();
|
|
|
|
|
std::cout << "[offscreen-smoke] 读回像素 " << n << " 个,非背景像素 "
|
|
|
|
|
<< nonBlack << "\n";
|
|
|
|
|
std::cout << "[offscreen-smoke] GL 能力:\n"
|
|
|
|
|
<< (caps ? caps : "(无)") << "\n";
|
|
|
|
|
|
|
|
|
|
if (nonBlack == 0) {
|
|
|
|
|
std::cout << "[offscreen-smoke] FAIL: 渲染结果全为背景(actor 未画出)\n";
|
|
|
|
|
std::cout << "STATUS=BLOCKED_OFFSCREEN\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::cout << "[offscreen-smoke] OK:离屏 GL 可用,可继续真实基准。\n";
|
|
|
|
|
std::cout << "STATUS=OK\n";
|
|
|
|
|
return 0;
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
std::cout << "[offscreen-smoke] FAIL: 异常 " << e.what() << "\n";
|
|
|
|
|
std::cout << "STATUS=BLOCKED_OFFSCREEN\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 体绘制 fps:每帧绕 azimuth 旋相机再 Render(),避免被驱动优化成空渲染。
|
|
|
|
|
double benchVolumeFps(vtkRenderWindow* rw, vtkRenderer* ren, int frames) {
|
|
|
|
|
ren->ResetCamera();
|
|
|
|
|
vtkCamera* cam = ren->GetActiveCamera();
|
|
|
|
|
rw->Render(); // 预热一帧(首帧含上传显存/编译 shader,不计时)
|
|
|
|
|
Stopwatch sw;
|
|
|
|
|
for (int f = 0; f < frames; ++f) {
|
|
|
|
|
cam->Azimuth(360.0 / frames); // 每帧转一点,扫满一圈
|
|
|
|
|
rw->Render();
|
|
|
|
|
}
|
|
|
|
|
const double ms = sw.elapsedMs();
|
|
|
|
|
return ms > 0.0 ? frames * 1000.0 / ms : 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 切片扫描 fps:沿 K 轴(深度)逐偏移 reslice 取轴向切面 + 纹理渲染,每帧推进偏移。
|
|
|
|
|
double benchSliceFps(vtkRenderWindow* rw, vtkRenderer* ren,
|
|
|
|
|
vtkImageData* full, vtkLookupTable* lut, int frames) {
|
|
|
|
|
// reslice:固定轴向(XY 平面),沿 Z 改变 ResliceAxesOrigin 扫过整卷。
|
|
|
|
|
vtkNew<vtkImageReslice> reslice;
|
|
|
|
|
reslice->SetInputData(full);
|
|
|
|
|
reslice->SetOutputDimensionality(2);
|
|
|
|
|
reslice->SetInterpolationModeToLinear();
|
|
|
|
|
|
|
|
|
|
vtkNew<vtkImageMapToColors> colorize;
|
|
|
|
|
colorize->SetLookupTable(lut);
|
|
|
|
|
colorize->SetInputConnection(reslice->GetOutputPort());
|
|
|
|
|
|
|
|
|
|
vtkNew<vtkImageActor> imgActor;
|
|
|
|
|
imgActor->GetMapper()->SetInputConnection(colorize->GetOutputPort());
|
|
|
|
|
ren->AddViewProp(imgActor);
|
|
|
|
|
ren->ResetCamera();
|
|
|
|
|
|
|
|
|
|
double bounds[6];
|
|
|
|
|
full->GetBounds(bounds);
|
|
|
|
|
const double zMin = bounds[4], zMax = bounds[5];
|
|
|
|
|
const double ox = 0.5 * (bounds[0] + bounds[1]);
|
|
|
|
|
const double oy = 0.5 * (bounds[2] + bounds[3]);
|
|
|
|
|
|
|
|
|
|
rw->Render(); // 预热
|
|
|
|
|
Stopwatch sw;
|
|
|
|
|
for (int f = 0; f < frames; ++f) {
|
|
|
|
|
const double t = static_cast<double>(f) / std::max(1, frames - 1);
|
|
|
|
|
const double z = zMin + (zMax - zMin) * t;
|
|
|
|
|
reslice->SetResliceAxesOrigin(ox, oy, z);
|
|
|
|
|
reslice->Modified();
|
|
|
|
|
rw->Render();
|
|
|
|
|
}
|
|
|
|
|
const double ms = sw.elapsedMs();
|
|
|
|
|
return ms > 0.0 ? frames * 1000.0 / ms : 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 由 ColorScale 物理区间建 256 级 VTK LUT(切片纹理着色用,与体绘制色阶同源)。
|
|
|
|
|
vtkSmartPointer<vtkLookupTable> makeLut(const geopro::core::ColorScale& cs,
|
|
|
|
|
double vmin, double vmax) {
|
|
|
|
|
auto lut = vtkSmartPointer<vtkLookupTable>::New();
|
|
|
|
|
const int n = 256;
|
|
|
|
|
lut->SetNumberOfTableValues(n);
|
|
|
|
|
lut->SetRange(vmin, vmax);
|
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
|
const double v = vmin + (vmax - vmin) * i / (n - 1);
|
|
|
|
|
const auto c = cs.colorAt(v);
|
|
|
|
|
lut->SetTableValue(i, c.r / 255.0, c.g / 255.0, c.b / 255.0, 1.0);
|
|
|
|
|
}
|
|
|
|
|
lut->Build();
|
|
|
|
|
return lut;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 简单蓝-白-红色阶(与 test_color_scale 同款最简构造)。
|
|
|
|
|
geopro::core::ColorScale makeColorScale(double vmin, double vmax) {
|
|
|
|
|
geopro::core::ColorScale cs;
|
|
|
|
|
const double mid = 0.5 * (vmin + vmax);
|
|
|
|
|
cs.addStop(vmin, geopro::core::Rgba{0, 0, 255, 255});
|
|
|
|
|
cs.addStop(mid, geopro::core::Rgba{255, 255, 255, 255});
|
|
|
|
|
cs.addStop(vmax, geopro::core::Rgba{255, 0, 0, 255});
|
|
|
|
|
return cs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int cmdRenderB(int argc, char** argv) {
|
|
|
|
|
const Args a = parseArgs(argc, argv, 2);
|
|
|
|
|
if (a.positional.empty()) {
|
|
|
|
|
std::cerr << "用法: gpr_poc renderB <storeDir> [--frames 120]\n";
|
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
const std::string dir = a.positional[0];
|
|
|
|
|
const int frames = std::stoi(a.get("frames", "120"));
|
|
|
|
|
std::cout << "[renderB] storeDir=" << dir << " frames=" << frames << "\n";
|
|
|
|
|
|
|
|
|
|
// 闸门复检:renderB 前先确认离屏可用(避免在不可渲染机上跑出假数据)。
|
|
|
|
|
std::cout << "[renderB] 离屏闸门复检...\n";
|
|
|
|
|
if (cmdOffscreenSmoke() != 0) {
|
|
|
|
|
std::cout << "[renderB] 闸门失败,中止,不产出 fps。\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 1) 加载整卷(VTK_SHORT)。
|
|
|
|
|
Stopwatch swLoad;
|
|
|
|
|
geopro::render::WholeVolumeSource src(dir);
|
|
|
|
|
const double loadMs = swLoad.elapsedMs();
|
|
|
|
|
const auto& m = src.meta();
|
|
|
|
|
|
|
|
|
|
const std::int64_t voxels =
|
|
|
|
|
static_cast<std::int64_t>(m.nx) * m.ny * m.nz;
|
|
|
|
|
const std::int64_t wholeBytes = voxels * 2; // VTK_SHORT
|
|
|
|
|
std::cout << "[renderB] 整卷 " << m.nx << "x" << m.ny << "x" << m.nz
|
|
|
|
|
<< " 体素=" << voxels << " 字节=" << wholeBytes << " ("
|
|
|
|
|
<< wholeBytes / (1024.0 * 1024.0) << " MB),加载 " << loadMs
|
|
|
|
|
<< "ms\n";
|
|
|
|
|
|
|
|
|
|
auto images = src.currentImages();
|
|
|
|
|
if (images.empty() || !images.front()) {
|
|
|
|
|
std::cerr << "[renderB] 错误: currentImages 为空\n";
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
vtkImageData* shortImg = images.front().Get();
|
|
|
|
|
|
|
|
|
|
// 色阶用 meta 的物理区间。
|
|
|
|
|
const double vmin = m.vminPhys, vmax = m.vmaxPhys;
|
|
|
|
|
const geopro::core::ColorScale cs = makeColorScale(vmin, vmax);
|
|
|
|
|
|
|
|
|
|
// 2) 体绘制(离屏)。
|
|
|
|
|
auto rw = makeOffscreenWindow(1024, 768);
|
|
|
|
|
vtkNew<vtkRenderer> ren;
|
|
|
|
|
ren->SetBackground(0.0, 0.0, 0.0);
|
|
|
|
|
rw->AddRenderer(ren);
|
|
|
|
|
|
|
|
|
|
vtkSmartPointer<vtkVolume> volume =
|
|
|
|
|
geopro::render::buildVoxelI16FromImage(shortImg, m.quant, cs, vmin, vmax);
|
|
|
|
|
ren->AddVolume(volume);
|
|
|
|
|
|
|
|
|
|
// 装上捕获式 OutputWindow:拦截体绘制时的 3D 纹理维度错误。
|
|
|
|
|
auto capWin = vtkSmartPointer<CapturingOutputWindow>::New();
|
|
|
|
|
vtkOutputWindow::SetInstance(capWin);
|
|
|
|
|
|
|
|
|
|
std::cout << "[renderB] 体绘制基准(" << frames << " 帧旋转相机)...\n";
|
|
|
|
|
const double volFpsRaw = benchVolumeFps(rw, ren, frames);
|
|
|
|
|
|
|
|
|
|
const bool textureErr = capWin->textureError();
|
|
|
|
|
vtkOutputWindow::SetInstance(nullptr); // 还原默认输出窗口
|
|
|
|
|
|
|
|
|
|
// 进显存判据:SmartVolumeMapper 实际用的渲染模式(2=GPURenderMode)。
|
|
|
|
|
int renderMode = -1;
|
|
|
|
|
bool lowResResample = false;
|
|
|
|
|
if (auto* svm =
|
|
|
|
|
vtkSmartVolumeMapper::SafeDownCast(volume->GetMapper())) {
|
|
|
|
|
renderMode = svm->GetLastUsedRenderMode();
|
|
|
|
|
// 大体可能触发降质重采样(GPU 显存不足时 SmartVolumeMapper 走低分辨率)。
|
|
|
|
|
lowResResample = (svm->GetInteractiveAdjustSampleDistances() == 0 &&
|
|
|
|
|
renderMode != vtkSmartVolumeMapper::GPURenderMode);
|
|
|
|
|
}
|
|
|
|
|
const bool onGpu = (renderMode == vtkSmartVolumeMapper::GPURenderMode);
|
|
|
|
|
|
|
|
|
|
// 任一维度超过 GL_MAX_3D_TEXTURE_SIZE(本机实测 16384)→ 整卷无法成单张 3D 纹理。
|
|
|
|
|
constexpr int kMax3DTexObserved = 16384;
|
|
|
|
|
const bool dimOversize =
|
|
|
|
|
(m.nx > kMax3DTexObserved || m.ny > kMax3DTexObserved ||
|
|
|
|
|
m.nz > kMax3DTexObserved);
|
|
|
|
|
// 体绘制 fps 是否可信:上传成功(无纹理错误且未超限)才算真实整卷体绘制帧率。
|
|
|
|
|
const bool volFpsValid = !textureErr && !dimOversize;
|
|
|
|
|
const double volFps = volFpsValid ? volFpsRaw : -1.0;
|
|
|
|
|
|
|
|
|
|
std::cout << "[renderB] 体绘制 raw_fps=" << volFpsRaw
|
|
|
|
|
<< " 渲染模式=" << renderMode << (onGpu ? "(GPU)" : "(非GPU)")
|
|
|
|
|
<< " 纹理维度错误=" << (textureErr ? "是" : "否")
|
|
|
|
|
<< " 超 16384=" << (dimOversize ? "是" : "否") << "\n";
|
|
|
|
|
if (!volFpsValid) {
|
|
|
|
|
std::cout << "[renderB] 警告: 整卷未能成单张 3D 纹理(X=" << m.nx
|
|
|
|
|
<< " > " << kMax3DTexObserved
|
|
|
|
|
<< "),体绘制 fps 无意义 → 标 INVALID。\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3) 切片扫描(离屏,沿 Z 扫整卷)。
|
|
|
|
|
vtkNew<vtkRenderer> ren2;
|
|
|
|
|
ren2->SetBackground(0.0, 0.0, 0.0);
|
|
|
|
|
auto rw2 = makeOffscreenWindow(1024, 768);
|
|
|
|
|
rw2->AddRenderer(ren2);
|
|
|
|
|
vtkSmartPointer<vtkLookupTable> lut = makeLut(cs, vmin, vmax);
|
|
|
|
|
|
|
|
|
|
std::cout << "[renderB] 切片扫描基准(" << frames << " 帧沿 Z 推进)...\n";
|
|
|
|
|
const double sliceFps =
|
|
|
|
|
benchSliceFps(rw2, ren2, src.sliceSource(), lut, frames);
|
|
|
|
|
std::cout << "[renderB] 切片 fps=" << sliceFps << "\n";
|
|
|
|
|
|
|
|
|
|
const double peak = Probe::peakMemMB();
|
|
|
|
|
const std::string vram = "N/A"; // VTK 安装未带 GLEW 头,无法直查 NVX 显存
|
|
|
|
|
|
|
|
|
|
// 4) 汇总打印。
|
|
|
|
|
const std::string volFpsStr =
|
|
|
|
|
volFpsValid ? std::to_string(volFps) : "INVALID(整卷超 3D 纹理上限)";
|
|
|
|
|
|
|
|
|
|
std::cout << "\n=== renderB GPU 指标 ===\n";
|
|
|
|
|
std::cout << "离屏闸门 : OK\n";
|
|
|
|
|
std::cout << "体维度 : " << m.nx << " x " << m.ny << " x " << m.nz
|
|
|
|
|
<< "\n";
|
|
|
|
|
std::cout << "体素数 : " << voxels << "\n";
|
|
|
|
|
std::cout << "整卷字节(B) : " << wholeBytes << " ("
|
|
|
|
|
<< wholeBytes / (1024.0 * 1024.0) << " MB)\n";
|
|
|
|
|
std::cout << "体绘制 fps : " << volFpsStr << "\n";
|
|
|
|
|
if (!volFpsValid) {
|
|
|
|
|
std::cout << " (raw_fps=" << volFpsRaw
|
|
|
|
|
<< " 为空纹理渲染,X=" << m.nx << " > 16384,不可信)\n";
|
|
|
|
|
}
|
|
|
|
|
std::cout << "切片扫描 fps : " << sliceFps << " (2D 纹理,无 3D 上限约束)\n";
|
|
|
|
|
std::cout << "渲染模式 : " << renderMode
|
|
|
|
|
<< (onGpu ? " (GPU 路径)" : " (非 GPU)") << "\n";
|
|
|
|
|
std::cout << "整卷进显存 : "
|
|
|
|
|
<< (volFpsValid && onGpu ? "是(单张 3D 纹理)"
|
|
|
|
|
: "否(超 GL_MAX_3D_TEXTURE_SIZE 16384)")
|
|
|
|
|
<< "\n";
|
|
|
|
|
std::cout << "降质重采样 : " << (lowResResample ? "是" : "否") << "\n";
|
|
|
|
|
std::cout << "GPU 显存 : " << vram << "\n";
|
|
|
|
|
std::cout << "进程峰值内存(MB): " << peak << "\n";
|
|
|
|
|
|
|
|
|
|
writeMetricLine(
|
|
|
|
|
"renderB,dir=" + dir + ",nx=" + std::to_string(m.nx) +
|
|
|
|
|
",ny=" + std::to_string(m.ny) + ",nz=" + std::to_string(m.nz) +
|
|
|
|
|
",voxels=" + std::to_string(voxels) +
|
|
|
|
|
",wholeB=" + std::to_string(wholeBytes) +
|
|
|
|
|
",volFps=" + volFpsStr +
|
|
|
|
|
",volFpsRaw=" + std::to_string(volFpsRaw) +
|
|
|
|
|
",volFpsValid=" + std::to_string(volFpsValid ? 1 : 0) +
|
|
|
|
|
",sliceFps=" + std::to_string(sliceFps) +
|
|
|
|
|
",renderMode=" + std::to_string(renderMode) +
|
|
|
|
|
",onGpu=" + std::to_string(onGpu ? 1 : 0) +
|
|
|
|
|
",loadMs=" + std::to_string(loadMs) +
|
|
|
|
|
",peakMB=" + std::to_string(peak));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void usage() {
|
|
|
|
|
std::cerr << "gpr_poc —— POC-B headless 度量 CLI\n"
|
|
|
|
|
" gpr_poc build <dir> [--line 001] [--cellXY 0.2] "
|
|
|
|
|
"[--cellZ 0.05] [--out <storeDir>] [--levels 2]\n"
|
|
|
|
|
" gpr_poc load <storeDir>\n"
|
|
|
|
|
" gpr_poc selftest\n";
|
|
|
|
|
" gpr_poc selftest\n"
|
|
|
|
|
" gpr_poc offscreen-smoke\n"
|
|
|
|
|
" gpr_poc renderB <storeDir> [--frames 120]\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
@ -436,6 +814,8 @@ int main(int argc, char** argv) {
|
|
|
|
|
if (cmd == "build") return cmdBuild(argc, argv);
|
|
|
|
|
if (cmd == "load") return cmdLoad(argc, argv);
|
|
|
|
|
if (cmd == "selftest") return cmdSelftest();
|
|
|
|
|
if (cmd == "offscreen-smoke") return cmdOffscreenSmoke();
|
|
|
|
|
if (cmd == "renderB") return cmdRenderB(argc, argv);
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
std::cerr << "错误: " << e.what() << "\n";
|
|
|
|
|
return 1;
|
|
|
|
|
|