feat(poc): POC-B 离屏 GPU 渲染基准(offscreen-smoke 闸门 + renderB 体绘制/切片 fps)

- gpr_poc 新增 offscreen-smoke: 离屏 vtkRenderWindow + cube + 读回像素, 闸门验证离屏 GL 可用
- gpr_poc 新增 renderB: 整卷 VTK_SHORT 体绘制(旋相机) + 切片扫描(reslice 沿 Z) fps 实测
- 关键发现: line 001 cellXY=0.05 整卷 44476x29x162, X 维超 GL_MAX_3D_TEXTURE_SIZE(16384),
  vtkVolumeTexture 上传失败, 体绘制 fps 如实标 INVALID(绝不上报假帧率); 切片 54.6fps 真实流畅
- 用 CapturingOutputWindow 捕获纹理维度错误 + 维度超限双判据, 避免误把空纹理假帧率当性能
- CMakeLists 补 RenderingVolume/RenderingVolumeOpenGL2/ImagingCore/InteractionStyle 组件
This commit is contained in:
gaozheng 2026-06-23 13:52:51 +08:00
parent 75cf8d40ba
commit 03805f4326
4 changed files with 502 additions and 2 deletions

View File

@ -0,0 +1,44 @@
# Task 9c 报告POC-B 离屏 GPU 渲染基准
状态:**DONE**(闸门通过;真实基准实测完成;关键发现如实记录,无任何编造 fps
执行机Windows 11MSVCVS18 Community+ NinjaReleaseGPU = NVIDIA RTX 3060 Laptop GPUOpenGL 4.5.0 NVIDIA 555.97。
日期2026-06-23。
---
## 1. 交付物
- `tools/gpr_poc/main.cpp`:新增两个子命令
- `gpr_poc offscreen-smoke` —— 最小离屏渲染冒烟(闸门),打印 OK/FAIL + GL 能力。
- `gpr_poc renderB <storeDir> [--frames 120]` —— 离屏体绘制 + 切片扫描 fps 基准。
- `tools/gpr_poc/CMakeLists.txt`:补 VTK 组件RenderingVolume / RenderingVolumeOpenGL2 / ImagingCore / InteractionStyle
- `docs/superpowers/plans/poc-results-B.md`新增「§4 离屏 GPU 渲染基准」段(闸门 + 真实指标 + 关键发现 + 结论)。
- `build/_t9c_build.bat`:本任务用的 gpr_poc 单 target 构建脚本vcvars64 直驱 cmake
## 2. 闸门结果 —— OK
`offscreen-smoke`:离屏 vtkRenderWindowSetOffScreenRendering+SetShowWindow(false))→ cube actor → Render() →
GetRGBACharPixelData 读回 65536 像素,非背景 28224。GL vendor=NVIDIA硬件加速 True。**离屏 GL 可用**,继续真实基准。
## 3. 真实 GPU 指标line 001, cellXY=0.05, cellZ=0.05
- 体维度:**44476 × 29 × 162**;体素数 ≈2.09 亿;整卷字节 **398.54 MB**int16
- **体绘制 fpsINVALID** —— 整卷 X 维 44476 超 `GL_MAX_3D_TEXTURE_SIZE=16384`
`vtkVolumeTexture``Invalid texture dimensions [44476,29,162]`,未真正绘出体数据。
raw_fps=295.6 是空纹理假帧率,已显式标 INVALID**不作为体绘制性能上报**。
- **切片扫描 fps54.6 fps**120 帧沿 Z 扫整卷vtkImageReslice 2D 切面 + 2D 纹理;不受 3D 纹理上限约束。≥30fps 目标达成。
- 是否进显存:**否**(瓶颈是单轴纹理维度上限 16384非显存字节整卷 398 MB << RTX 3060 6GB 显存
- GPU 显存NVX**N/A**(随包 VTK 安装未带 GLEW 头,无法链 GL loader 直查GL 扩展列表确认机器支持 NVX_gpu_memory_info
- 进程峰值内存:**≈509 MB**(加载整卷 398 MB + 渲染管线)。
- build 峰值内存4830 MB装配阶段 double survey 主导,与 §2 一致);无 OOMcellXY=0.05 一次通过。
## 4. concerns
1. **整卷朴素体绘制对长测线根本不可行**X=44476 撞 OpenGL 单轴 3D 纹理上限 16384。
与显存容量无关,是硬限制。任何「整卷一次性 3D 纹理」方案对长测线都会撞墙。
这是 **Task 12核外 / 分块 LOD / 体纹理分区 `vtkOpenGLGPUVolumeRayCastMapper::SetPartitions`**
的硬性依据。本任务按约束未做核外,仅如实记录。
2. SmartVolumeMapper 报 GPURenderMode=2 但纹理上传失败——`GetLastUsedRenderMode()` 不能单独作为
「真的渲染出来了」的判据renderB 已加 OutputWindow 捕获 + 维度超限双判据才下 INVALID 结论。
3. GPU 显存读数缺失N/A仅因 VTK 安装未带 GLEW 头;若需要可后续单独链 GL loader 调 NVX 枚举。

View File

@ -103,3 +103,77 @@ gpr_poc load <store>
**结论**`assembler`/`GprGeometry`/CLI `specFromSurvey` 的 Z 计算**全部正确** **结论**`assembler`/`GprGeometry`/CLI `specFromSurvey` 的 Z 计算**全部正确**
无需改 CLI。先前的 nz=1 症状是 soilVelocity 换算缺失时代的遗留,现已不复存在。 无需改 CLI。先前的 nz=1 症状是 soilVelocity 换算缺失时代的遗留,现已不复存在。
CLI 的 `specFromSurvey` 用的是 `survey.dz`(来自 `depthOfSample`),未误用原始 100未漏乘。 CLI 的 `specFromSurvey` 用的是 `survey.dz`(来自 `depthOfSample`),未误用原始 100未漏乘。
---
## 4. 离屏 GPU 渲染基准(任务 9c2026-06-23
工具新增子命令:`gpr_poc offscreen-smoke`(闸门)、`gpr_poc renderB <store> [--frames N]`。
执行机 GPU**NVIDIA GeForce RTX 3060 Laptop GPU**OpenGL 4.5.0 NVIDIA 555.97,硬件加速 True。
### 4.1 闸门offscreen-smoke —— **OK离屏 GL 可用)**
命令:`gpr_poc offscreen-smoke`
- 离屏 `vtkRenderWindow``SetOffScreenRendering(1)`+`SetShowWindow(false)`256×256
→ 加 cube actor → `Render()``GetRGBACharPixelData` 读回。
- 读回 65536 像素,非背景像素 **28224**cube 正确画出)。
- GL vendor=NVIDIA Corporationrenderer=RTX 3060 Laptop GPU硬件加速 True。
- **结论:离屏 GPU 渲染在本机可用**,继续真实基准(非编造)。
### 4.2 基准数据line 001更细一档 cellXY=0.05
命令:`gpr_poc build "D:\Downloads\明星路" --line 001 --cellXY 0.05 --cellZ 0.05 --out <store> --levels 1`
| 指标 | 值 |
|------|-----|
| 体维度nx×ny×nz | **44476 × 29 × 162** |
| 体素数 | 208,948,248≈2.09 亿) |
| 整卷字节int16进显存判据 | 417,896,496 B**398.54 MB** |
| data.bin含金字塔 | 199.43 MB压缩比 2.00× |
| build 峰值内存 | 4,830 MB装配阶段 double survey 主导,同 §2.4 |
| 整卷加载耗时renderB load | ≈2.84.0 s |
| renderB 进程峰值内存 | **≈509 MB**(加载整卷 398 MB + 渲染管线) |
无 build OOMcellXY=0.05 一次通过,未调粗。
### 4.3 renderB 实测指标 —— **关键发现:整卷体绘制不可行**
命令:`gpr_poc renderB <store> --frames 120`
| 指标 | 值 |
|------|-----|
| 离屏闸门复检 | OK |
| **体绘制 fps** | **INVALID整卷超 3D 纹理上限)** |
| ├ raw_fps空纹理渲染不可信 | 295.6(仅作记录,非真实帧率) |
| ├ SmartVolumeMapper 渲染模式 | 2 = GPURenderMode |
| └ vtkVolumeTexture 报错 | `Invalid texture dimensions [44476, 29, 162]` |
| **切片扫描 fps** | **54.6 fps**120 帧沿 Z 扫整卷reslice+纹理) |
| 整卷进显存 | **否**X=44476 > GL_MAX_3D_TEXTURE_SIZE=16384 |
| 降质重采样LowRes | 否(未触发;是直接纹理维度超限失败,非显存不足降质) |
| GPU 显存NVX | **N/A**(随包 VTK 安装未带 GLEW 头,无法直查 `NVX_gpu_memory_info`
但 GL 扩展列表确认该扩展存在,机器支持,仅本工具未链 GL loader |
| 进程峰值内存 | ≈509 MB |
#### 关键发现(务必看)
1. **整卷体绘制在本机离屏下不可行**:测线 001 的 X 维(沿测线方向)= **44476**
远超本机 OpenGL `GL_MAX_3D_TEXTURE_SIZE = 16384`。`vtkSmartVolumeMapper`
走 GPU 路径mode=2但底层 `vtkVolumeTexture` **无法将整卷上传为单张 3D 纹理**
`Invalid texture dimensions`。此时 `Render()` 实际未绘出体数据,
故所谓 295 fps 是**空纹理渲染的假帧率,已如实标 INVALID绝不上报为体绘制性能**。
2. **切片扫描真实流畅**:切片走 `vtkImageReslice` 输出 2D 切面 + 2D 纹理着色,
**不受 3D 纹理维度上限约束**,实测 **54.6 fps ≥ 30fps 目标**,整卷切片交互流畅。
3. **进显存判据**:整卷 398 MB 远小于 GPU 显存RTX 3060 6GB显存容量不是瓶颈
真正的瓶颈是**单轴纹理维度上限16384**,而非显存字节数。
#### 结论
- **切片**:✅ 本机离屏下整卷切片 ≥30fps54.6fps),交互流畅,满足目标。
- **整卷体绘制**:❌ 在「整卷成单张 3D 纹理」的朴素路径下**不可行**——
长测线 X 维超 GL 单轴上限。这正是 **Task 12核外 / 分块 LOD / 体纹理分区
`vtkOpenGLGPUVolumeRayCastMapper::SetPartitions`** 必须解决的问题:
要么沿 X 分区/分块上传,要么按视相机做 LOD 工作集。本任务9c按约束**不做核外**
仅如实记录此限制作为 Task 12 的硬性依据。
- 该限制与显存容量无关,是 OpenGL 纹理维度硬上限;任何「整卷一次性 3D 纹理」方案
对长测线都会撞同一面墙。

View File

@ -6,7 +6,9 @@
# VTK_LIBRARIES find_package # VTK_LIBRARIES find_package
# geopro_render target vtk_module_autoinit # geopro_render target vtk_module_autoinit
find_package(VTK REQUIRED COMPONENTS find_package(VTK REQUIRED COMPONENTS
CommonCore CommonDataModel RenderingCore RenderingOpenGL2 GUISupportQt) CommonCore CommonDataModel
RenderingCore RenderingOpenGL2 RenderingVolume RenderingVolumeOpenGL2
ImagingCore InteractionStyle GUISupportQt)
add_executable(gpr_poc main.cpp) add_executable(gpr_poc main.cpp)

View File

@ -8,6 +8,8 @@
// gpr_poc build <dir> [--line 001] [--cellXY 0.2] [--cellZ 0.05] [--out <storeDir>] [--levels 2] // gpr_poc build <dir> [--line 001] [--cellXY 0.2] [--cellZ 0.05] [--out <storeDir>] [--levels 2]
// gpr_poc load <storeDir> // gpr_poc load <storeDir>
// gpr_poc selftest // gpr_poc selftest
// gpr_poc offscreen-smoke —— 离屏 GL 闸门冒烟
// gpr_poc renderB <storeDir> [--frames 120] —— 离屏体绘制/切片 fps 基准
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
@ -25,11 +27,35 @@
#include "core/algo/GprVolumeBuilder.hpp" #include "core/algo/GprVolumeBuilder.hpp"
#include "core/algo/IInterpolator.hpp" #include "core/algo/IInterpolator.hpp"
#include "core/model/GprSurvey.hpp" #include "core/model/GprSurvey.hpp"
#include "core/model/ColorScale.hpp"
#include "core/model/ScalarVolumeI16.hpp" #include "core/model/ScalarVolumeI16.hpp"
#include "data/store/ChunkedVolumeStore.hpp" #include "data/store/ChunkedVolumeStore.hpp"
#include "io/gpr/GprSurveyAssembler.hpp" #include "io/gpr/GprSurveyAssembler.hpp"
#include "render/actors/VoxelActor.hpp"
#include "render/source/WholeVolumeSource.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; namespace fs = std::filesystem;
using geopro::tools::Probe; using geopro::tools::Probe;
using geopro::tools::Stopwatch; using geopro::tools::Stopwatch;
@ -416,12 +442,364 @@ int cmdSelftest() {
return ok ? 0 : 1; 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);
// 创建一个离屏 vtkRenderWindowVTK9.6SetShowWindow(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() { void usage() {
std::cerr << "gpr_poc —— POC-B headless 度量 CLI\n" std::cerr << "gpr_poc —— POC-B headless 度量 CLI\n"
" gpr_poc build <dir> [--line 001] [--cellXY 0.2] " " gpr_poc build <dir> [--line 001] [--cellXY 0.2] "
"[--cellZ 0.05] [--out <storeDir>] [--levels 2]\n" "[--cellZ 0.05] [--out <storeDir>] [--levels 2]\n"
" gpr_poc load <storeDir>\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 } // namespace
@ -436,6 +814,8 @@ int main(int argc, char** argv) {
if (cmd == "build") return cmdBuild(argc, argv); if (cmd == "build") return cmdBuild(argc, argv);
if (cmd == "load") return cmdLoad(argc, argv); if (cmd == "load") return cmdLoad(argc, argv);
if (cmd == "selftest") return cmdSelftest(); if (cmd == "selftest") return cmdSelftest();
if (cmd == "offscreen-smoke") return cmdOffscreenSmoke();
if (cmd == "renderB") return cmdRenderB(argc, argv);
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << "\n"; std::cerr << "错误: " << e.what() << "\n";
return 1; return 1;