feat(poc): build-stream 多线合并流式建体 + Track B 总验收实测
This commit is contained in:
parent
77cbe4a305
commit
ba59c8861a
|
|
@ -0,0 +1,94 @@
|
||||||
|
# Track B 总验收实测结果(build-stream 多线合并流式建体)
|
||||||
|
|
||||||
|
工具:`tools/gpr_poc`(CLI),子命令 `build-stream`。
|
||||||
|
执行机:Windows 11,MSVC(VS18 Community)+ Ninja,Release(/O2),开发机(RTX 3060 Laptop GPU 旁)。
|
||||||
|
日期:2026-06-23。
|
||||||
|
|
||||||
|
整条流式链路(B1→B5 成果在 B6 串起来):
|
||||||
|
`assembleGprSurveySlab(道窗口)→ sampleGprPoint(结构化插值)→ StreamingVolumeWriter(增量 zlib 写 brick)→ buildPyramidStreaming(从盘 brick 逐级降采样)→ WholeVolumeSource(load)`。
|
||||||
|
|
||||||
|
> Track B 的核心命题:把建体管线改成沿 X 分段(slab)流式(逐段读道→插值→写 brick→释放),
|
||||||
|
> 使建「20 线合并大体」的峰值内存**有界、不随线数增长**、无 OOM,且产物可被 `load` 读回。
|
||||||
|
> 对比基线:旧 double-survey 非流式方案,单线装配峰值 ≈4.2 GB,20 线 ≈84 GB → 装配阶段必然 OOM。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 验收命令
|
||||||
|
|
||||||
|
```
|
||||||
|
gpr_poc build-stream "D:\Downloads\明星路" --cellXY 0.2 --cellZ 0.05 --out <storeDir> --levels 2
|
||||||
|
gpr_poc load <storeDir>
|
||||||
|
```
|
||||||
|
|
||||||
|
- 合并方式:**沿 X 顺序排列**(退路近似)——各线按 brick 对齐的 X 偏移依次拼入一个连续 store。
|
||||||
|
真实 RTK 几何拼接(按各线实际平面坐标拼成路网大体)留 **Track D**。
|
||||||
|
- 量化 scale/offset **全局一致**:先扫一遍全部 20 线得全局 min/max(流式下不能每 slab 各算),
|
||||||
|
再以全局值域逐线逐 slab 写 brick。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. build-stream 实测指标(20 线合并大体,cellXY=0.2,cellZ=0.05,levels=2)
|
||||||
|
|
||||||
|
| 指标 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| 合并测线数 | **20** |
|
||||||
|
| 合并体维度(nx×ny×nz) | **156544 × 8 × 82** |
|
||||||
|
| 体素数 | **102,692,864**(≈1.03 亿) |
|
||||||
|
| 原始体积(int16,进显存判据) | **195.871 MB** |
|
||||||
|
| 落盘 data.bin(含金字塔各级) | **105.422 MB** |
|
||||||
|
| 压缩比(原始/落盘) | **1.858×** |
|
||||||
|
| 扫全局量化区间耗时 | **181,288 ms**(一次性读全 14 GB 原始道) |
|
||||||
|
| 建体(流式 slab→写 brick)耗时 | **89,927 ms** |
|
||||||
|
| 流式金字塔耗时 | **6,972 ms** |
|
||||||
|
| build-stream 端到端墙钟 | **278,788 ms(≈4.6 min)** |
|
||||||
|
| **峰值内存** | **246.164 MB** |
|
||||||
|
|
||||||
|
### 峰值内存全程稳定(核心结论证据)
|
||||||
|
|
||||||
|
20 线全程峰值内存稳在 **≈246 MB**(245.973 MB → 246.164 MB),**不随合并线数增长**:
|
||||||
|
|
||||||
|
| 阶段 | 峰值内存 |
|
||||||
|
|------|----------|
|
||||||
|
| 扫全局量化 / 起始线 | 245.973 MB |
|
||||||
|
| 第 20 线写入完成 | 246.164 MB |
|
||||||
|
|
||||||
|
即流式建体的峰值内存**有界**——只随单个 slab + 一行 brick 的工作集,与合并总量(线数/体素数)无关。
|
||||||
|
对比旧 double-survey 非流式方案 20 线需 ≈84 GB,本方案以 246 MB 建出 1 亿体素的合并大体,**无 OOM**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. load 实测指标 —— 产物可读 ✓
|
||||||
|
|
||||||
|
命令:`gpr_poc load <storeDir>`
|
||||||
|
|
||||||
|
| 指标 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| 加载耗时 | **1,756 ms** |
|
||||||
|
| 整卷维度 | **156544 × 8 × 82** |
|
||||||
|
| 峰值内存 | **214.55 MB** |
|
||||||
|
|
||||||
|
流式产出的合并大体 store 可被 `WholeVolumeSource` 完整加载、维度一致、**产物可读**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Track B 总验收结论
|
||||||
|
|
||||||
|
- ✅ **峰值内存有界、与总量无关**:20 线合并大体建体全程峰值稳在 ≈246 MB
|
||||||
|
(245.973→246.164),不随线数增长。这是 Track B 的核心目标,达成。
|
||||||
|
- ✅ **无 OOM**:旧非流式方案 20 线 ≈84 GB 必 OOM;流式方案 246 MB 即建出 1 亿体素合并体。
|
||||||
|
- ✅ **产物可读**:`load` 在 1,756 ms 内读回,维度一致(156544×8×82),峰值 214.55 MB。
|
||||||
|
- ✅ **压缩与落盘正常**:原始 195.871 MB → data.bin 105.422 MB(含金字塔各级),压缩比 1.858×。
|
||||||
|
|
||||||
|
### 已知约束 / 留待后续
|
||||||
|
|
||||||
|
1. **扫全局量化 181 s 是「读全 14 GB 原始道」的 I/O 开销**——为保证量化 scale/offset
|
||||||
|
全局一致必须先全扫一遍 min/max。可后续优化(如复用建体阶段的单遍读、或用头估值域),本次不做。
|
||||||
|
2. **合并几何用退路近似(沿 X 顺序排列,brick 对齐)**——非真实路网平面几何。
|
||||||
|
真实 RTK 几何拼接(按各线实际平面坐标拼成路网大体)留 **Track D**。
|
||||||
|
3. **最低配未验**:仅在本机(RTX 3060 Laptop 开发机)实测;最低配机器内存/磁盘表现待补测。
|
||||||
|
|
||||||
|
### 与 Track C 的衔接
|
||||||
|
|
||||||
|
Track B 已证「大体能建(246 MB 有界)+ 能读(1.76 s 加载)」。Track C 在此大 store 上做
|
||||||
|
视野自适应 LOD 体绘制(视锥裁剪+视距选层 → 视野区域单纹理重组 → 平滑切换+后台预取),
|
||||||
|
解决 POC-B §4 记录的「整卷 X 维超 GL 单轴纹理上限(16384)」问题。
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
#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 "io/gpr/IprHeader.hpp"
|
||||||
#include "render/actors/VoxelActor.hpp"
|
#include "render/actors/VoxelActor.hpp"
|
||||||
#include "render/source/OutOfCoreSource.hpp"
|
#include "render/source/OutOfCoreSource.hpp"
|
||||||
#include "render/source/WholeVolumeSource.hpp"
|
#include "render/source/WholeVolumeSource.hpp"
|
||||||
|
|
@ -315,6 +316,368 @@ int cmdBuild(int argc, char** argv) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// build-stream:多线合并流式建大体(Track B 总验收)
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// 把工区目录下全部测线(各 14 通道 .iprb + 该线 .ord)流式建成【一个连续合并大体】:
|
||||||
|
// 1) 扫目录发现所有线号;
|
||||||
|
// 2) 定合并网格:沿 X 顺序排列(线 i 接在线 i-1 之后,按 brick 对齐对齐到 64 格边界),
|
||||||
|
// Y/Z 取各线最大值——退路近似(report 标注),证明大体流式建得出来且内存有界;
|
||||||
|
// 3) 全局量化:单遍扫所有线所有 slab 定全局 vmin/vmax(一次只持一个 64 道 slab);
|
||||||
|
// 4) 单个 StreamingVolumeWriter 跨线逐 slab 逐 brick 写(各线落在合并网格对应 X 区域),
|
||||||
|
// 全程不持整卷/不持整线 survey;
|
||||||
|
// 5) buildPyramidStreaming → finalize。
|
||||||
|
//
|
||||||
|
// 复用 buildGprVolumeStreaming 的 slab/采样核机制(sampleGprPoint + StreamingVolumeWriter),
|
||||||
|
// 仅在 X 方向把多线拼到同一 store。
|
||||||
|
|
||||||
|
constexpr int kStreamBrick = 64; // 与 StreamingVolumeWriter/Store 内部 brick 一致
|
||||||
|
|
||||||
|
int ceilDivInt(int n, int b) { return (n + b - 1) / b; }
|
||||||
|
int extentOf(int n, int b, int brick) {
|
||||||
|
const int got = n - b * brick;
|
||||||
|
return got < brick ? got : brick;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发现工区内全部线号(三位零填充,如 "001"):扫 .ord,取 "*_<NNN>.ord" 的 NNN。
|
||||||
|
std::vector<std::string> discoverLines(const std::string& dir) {
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
for (const auto& e : fs::directory_iterator(dir)) {
|
||||||
|
if (!e.is_regular_file()) continue;
|
||||||
|
if (e.path().extension().string() != ".ord") continue;
|
||||||
|
const std::string stem = e.path().stem().string(); // 明星路_001
|
||||||
|
const std::size_t us = stem.find_last_of('_');
|
||||||
|
if (us == std::string::npos) continue;
|
||||||
|
const std::string num = stem.substr(us + 1);
|
||||||
|
bool allDigit = !num.empty();
|
||||||
|
for (char c : num)
|
||||||
|
if (!std::isdigit(static_cast<unsigned char>(c))) allDigit = false;
|
||||||
|
if (allDigit) lines.push_back(num);
|
||||||
|
}
|
||||||
|
std::sort(lines.begin(), lines.end());
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一条线的几何 + 道距 + 全线总道数(不持整线:1 道 slab 取标尺,header 取总道数)。
|
||||||
|
struct LineGeom {
|
||||||
|
int samples = 0;
|
||||||
|
std::int64_t totalTraces = 0;
|
||||||
|
double dx = 1.0, dz = 1.0;
|
||||||
|
std::vector<double> channelY; // 升序
|
||||||
|
int nx = 0, ny = 0, nz = 0; // 该线在合并网格下的体素维度(X 未对齐 brick)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 全线总道数 = min 通道(fileBytes/(samples*2)),与 assembleGprSurvey 对齐口径一致。
|
||||||
|
std::int64_t totalTracesOf(const std::vector<std::string>& iprb, int samples) {
|
||||||
|
std::int64_t minTr = std::numeric_limits<std::int64_t>::max();
|
||||||
|
const std::int64_t per = static_cast<std::int64_t>(samples) * 2;
|
||||||
|
for (const auto& p : iprb) {
|
||||||
|
const std::int64_t bytes = static_cast<std::int64_t>(fs::file_size(p));
|
||||||
|
if (per <= 0) throw std::runtime_error("samples<=0");
|
||||||
|
minTr = std::min(minTr, bytes / per);
|
||||||
|
}
|
||||||
|
return minTr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmdBuildStream(int argc, char** argv) {
|
||||||
|
const Args a = parseArgs(argc, argv, 2);
|
||||||
|
if (a.positional.empty()) {
|
||||||
|
std::cerr << "用法: gpr_poc build-stream <dir> [--cellXY 0.05] "
|
||||||
|
"[--cellZ 0.05] [--out <storeDir>] [--levels 3] "
|
||||||
|
"[--sliceXBricks 8] [--maxLines N]\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
const std::string dir = a.positional[0];
|
||||||
|
const double cellXY = std::stod(a.get("cellXY", "0.05"));
|
||||||
|
const double cellZ = std::stod(a.get("cellZ", "0.05"));
|
||||||
|
const int levels = std::stoi(a.get("levels", "3"));
|
||||||
|
int sliceXBricks = std::stoi(a.get("sliceXBricks", "8"));
|
||||||
|
if (sliceXBricks <= 0) sliceXBricks = 1;
|
||||||
|
const int maxLines = std::stoi(a.get("maxLines", "0")); // 0=全部
|
||||||
|
const std::string out =
|
||||||
|
a.get("out", (fs::temp_directory_path() / "gpr_store_merged").string());
|
||||||
|
|
||||||
|
std::cout << "[build-stream] dir=" << dir << " cellXY=" << cellXY
|
||||||
|
<< " cellZ=" << cellZ << " levels=" << levels
|
||||||
|
<< " sliceXBricks=" << sliceXBricks << " out=" << out << "\n";
|
||||||
|
|
||||||
|
// 1) 发现线号。
|
||||||
|
std::vector<std::string> lineNos = discoverLines(dir);
|
||||||
|
if (maxLines > 0 && static_cast<int>(lineNos.size()) > maxLines)
|
||||||
|
lineNos.resize(maxLines);
|
||||||
|
std::cout << "[build-stream] 发现测线数=" << lineNos.size() << "\n";
|
||||||
|
if (lineNos.empty()) {
|
||||||
|
std::cerr << "[build-stream] 错误: 未发现任何 .ord 测线\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stopwatch swTotal;
|
||||||
|
|
||||||
|
// 2) 各线文件 + 几何(1 道 slab 取标尺,不持整线)。
|
||||||
|
std::vector<LineFiles> files;
|
||||||
|
std::vector<LineGeom> geom;
|
||||||
|
files.reserve(lineNos.size());
|
||||||
|
geom.reserve(lineNos.size());
|
||||||
|
for (const std::string& ln : lineNos) {
|
||||||
|
LineFiles lf = discoverLine(dir, ln);
|
||||||
|
if (lf.iprb.empty() || lf.ord.empty()) {
|
||||||
|
std::cerr << "[build-stream] 警告: 线 " << ln << " 缺 iprb/ord,跳过\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LineGeom g;
|
||||||
|
// 1 道 slab:取 dx/dz/samples/channelY(内存只随 1 道)。
|
||||||
|
const geopro::core::GprSurvey s0 =
|
||||||
|
geopro::io::gpr::assembleGprSurveySlab(lf.iprb, lf.ord, 0, 1);
|
||||||
|
g.samples = s0.samples;
|
||||||
|
g.dx = s0.dx;
|
||||||
|
g.dz = s0.dz;
|
||||||
|
g.channelY = s0.channelY;
|
||||||
|
g.totalTraces = totalTracesOf(lf.iprb, g.samples);
|
||||||
|
|
||||||
|
// 该线在合并网格下维度(X/Z 落格,Y 跨通道):与 specFromSurvey 同式。
|
||||||
|
const double rangeX = (g.totalTraces > 1) ? (g.totalTraces - 1) * g.dx : 0.0;
|
||||||
|
const double rangeY =
|
||||||
|
g.channelY.empty() ? 0.0 : (g.channelY.back() - g.channelY.front());
|
||||||
|
const double rangeZ = (g.samples > 1) ? (g.samples - 1) * g.dz : 0.0;
|
||||||
|
auto cells = [](double range, double cell) {
|
||||||
|
if (cell <= 0.0) return 1;
|
||||||
|
return static_cast<int>(std::ceil(range / cell)) + 1;
|
||||||
|
};
|
||||||
|
g.nx = cells(rangeX, cellXY);
|
||||||
|
g.ny = cells(rangeY, cellXY);
|
||||||
|
g.nz = cells(rangeZ, cellZ);
|
||||||
|
|
||||||
|
std::cout << "[build-stream] 线 " << ln << " 通道=" << lf.iprb.size()
|
||||||
|
<< " 道数=" << g.totalTraces << " samples=" << g.samples
|
||||||
|
<< " nx=" << g.nx << " ny=" << g.ny << " nz=" << g.nz << "\n";
|
||||||
|
files.push_back(std::move(lf));
|
||||||
|
geom.push_back(std::move(g));
|
||||||
|
}
|
||||||
|
if (files.empty()) {
|
||||||
|
std::cerr << "[build-stream] 错误: 无可用测线\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 合并网格(沿 X 顺序排列;各线 X 起点对齐到 brick 边界)。
|
||||||
|
// 每线占 [xBrickOffset, xBrickOffset + ceil(nx/brick)) 的 brick 列。
|
||||||
|
std::vector<int> xBrickOffset(files.size());
|
||||||
|
int mergedBx = 0, mergedNy = 0, mergedNz = 0;
|
||||||
|
for (std::size_t i = 0; i < files.size(); ++i) {
|
||||||
|
xBrickOffset[i] = mergedBx;
|
||||||
|
mergedBx += ceilDivInt(geom[i].nx, kStreamBrick);
|
||||||
|
mergedNy = std::max(mergedNy, geom[i].ny);
|
||||||
|
mergedNz = std::max(mergedNz, geom[i].nz);
|
||||||
|
}
|
||||||
|
const int mergedNx = mergedBx * kStreamBrick; // 末线 brick 对齐后整宽
|
||||||
|
const int bY = ceilDivInt(mergedNy, kStreamBrick);
|
||||||
|
const int bZ = ceilDivInt(mergedNz, kStreamBrick);
|
||||||
|
|
||||||
|
std::cout << "[build-stream] 合并网格 nx=" << mergedNx << " ny=" << mergedNy
|
||||||
|
<< " nz=" << mergedNz << " (bX=" << mergedBx << " bY=" << bY
|
||||||
|
<< " bZ=" << bZ << ")\n";
|
||||||
|
|
||||||
|
// 每线网格 spec(origin 沿 X 平移到该线 brick 起点的世界 X)。
|
||||||
|
auto specForLine = [&](std::size_t i) {
|
||||||
|
geopro::core::GridSpec spec{};
|
||||||
|
spec.ox = xBrickOffset[i] * kStreamBrick * cellXY; // 该线在合并体的世界 X 起点
|
||||||
|
spec.oy = geom[i].channelY.empty() ? 0.0 : geom[i].channelY.front();
|
||||||
|
spec.oz = 0.0;
|
||||||
|
spec.dx = cellXY;
|
||||||
|
spec.dy = cellXY;
|
||||||
|
spec.dz = cellZ;
|
||||||
|
spec.nx = geom[i].nx;
|
||||||
|
spec.ny = geom[i].ny;
|
||||||
|
spec.nz = geom[i].nz;
|
||||||
|
spec.power = 2.0;
|
||||||
|
spec.maxDist = cellXY * 2.0;
|
||||||
|
return spec;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4) 全局量化:单遍扫所有线所有 slab(一次只持一个 64 道 slab)。
|
||||||
|
std::cout << "[build-stream] 扫全局量化区间...\n";
|
||||||
|
Stopwatch swScan;
|
||||||
|
double vmin = std::numeric_limits<double>::infinity();
|
||||||
|
double vmax = -std::numeric_limits<double>::infinity();
|
||||||
|
constexpr std::int64_t kScanChunk = 64;
|
||||||
|
for (std::size_t i = 0; i < files.size(); ++i) {
|
||||||
|
const std::int64_t total = geom[i].totalTraces;
|
||||||
|
for (std::int64_t t0 = 0; t0 < total; t0 += kScanChunk) {
|
||||||
|
const std::int64_t t1 = std::min(total, t0 + kScanChunk);
|
||||||
|
const auto slab = geopro::io::gpr::assembleGprSurveySlab(
|
||||||
|
files[i].iprb, files[i].ord, t0, t1);
|
||||||
|
for (double v : slab.values) {
|
||||||
|
if (std::isnan(v)) continue;
|
||||||
|
if (v < vmin) vmin = v;
|
||||||
|
if (v > vmax) vmax = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!(vmin <= vmax)) {
|
||||||
|
vmin = 0.0;
|
||||||
|
vmax = 0.0;
|
||||||
|
}
|
||||||
|
const double scanMs = swScan.elapsedMs();
|
||||||
|
std::cout << "[build-stream] 全局值域 [" << vmin << ", " << vmax << "] 扫描 "
|
||||||
|
<< scanMs << "ms\n";
|
||||||
|
|
||||||
|
geopro::core::Quant quant;
|
||||||
|
quant.scale = (vmax > vmin) ? (vmax - vmin) / 64000.0 : 1.0;
|
||||||
|
quant.offset = 0.5 * (vmin + vmax);
|
||||||
|
|
||||||
|
// 5) 合并 StoreMeta + 单个 StreamingVolumeWriter。
|
||||||
|
geopro::data::StoreMeta meta;
|
||||||
|
meta.nx = mergedNx;
|
||||||
|
meta.ny = mergedNy;
|
||||||
|
meta.nz = mergedNz;
|
||||||
|
meta.brick = kStreamBrick;
|
||||||
|
meta.origin = {0.0, 0.0, 0.0};
|
||||||
|
meta.spacing = {cellXY, cellXY, cellZ};
|
||||||
|
meta.quant = quant;
|
||||||
|
meta.vminPhys = vmin;
|
||||||
|
meta.vmaxPhys = vmax;
|
||||||
|
|
||||||
|
fs::create_directories(out);
|
||||||
|
geopro::data::StreamingVolumeWriter writer(out, meta);
|
||||||
|
|
||||||
|
// 6) 跨线逐 slab 逐 brick 写。每线在自己的 brick 列区间内沿 X 分 slab;
|
||||||
|
// 合并网格 brick (mergedBx, by, bz):
|
||||||
|
// - 落在某线列区间内且该线有覆盖 → sampleGprPoint(线局部索引);
|
||||||
|
// - 否则 → blank(线间填充 + 该线 ny/nz 之外的合并余量)。
|
||||||
|
std::cout << "[build-stream] 流式写合并大体...\n";
|
||||||
|
Stopwatch swBuild;
|
||||||
|
for (std::size_t i = 0; i < files.size(); ++i) {
|
||||||
|
const geopro::core::GridSpec spec = specForLine(i);
|
||||||
|
const int lineBx = ceilDivInt(geom[i].nx, kStreamBrick);
|
||||||
|
const int lineBy = ceilDivInt(geom[i].ny, kStreamBrick);
|
||||||
|
const int lineBz = ceilDivInt(geom[i].nz, kStreamBrick);
|
||||||
|
const std::int64_t total = geom[i].totalTraces;
|
||||||
|
const double surveyDx = geom[i].dx > 0.0 ? geom[i].dx : 1.0;
|
||||||
|
|
||||||
|
// 沿 X 分 slab(brick 对齐),每 slab 含 sliceXBricks 个 X brick。
|
||||||
|
for (int bcol = 0; bcol < lineBx; bcol += sliceXBricks) {
|
||||||
|
const int bxEnd = std::min(lineBx, bcol + sliceXBricks);
|
||||||
|
const int gx0 = bcol * kStreamBrick;
|
||||||
|
const int gx1 = std::min(spec.nx, bxEnd * kStreamBrick);
|
||||||
|
|
||||||
|
// 该 slab 网格 X 列 → 全局道范围(夹到 [0,total)),可能全越界。
|
||||||
|
std::int64_t t0 = std::numeric_limits<std::int64_t>::max();
|
||||||
|
std::int64_t t1 = std::numeric_limits<std::int64_t>::min();
|
||||||
|
for (int gi = gx0; gi < gx1; ++gi) {
|
||||||
|
const double worldX = gi * cellXY; // 线局部世界 X(spec.ox 已含偏移,但落格用线内 x0=0)
|
||||||
|
const std::int64_t g = std::llround(worldX / surveyDx);
|
||||||
|
if (g < 0 || g >= total) continue;
|
||||||
|
t0 = std::min(t0, g);
|
||||||
|
t1 = std::max(t1, g);
|
||||||
|
}
|
||||||
|
const bool hasTraces = (t0 <= t1);
|
||||||
|
geopro::core::GprSurvey slab;
|
||||||
|
// 线局部 spec:x0=0 落格(与 assembleGprSurveySlab 的 x0=t0*dx 对齐靠 worldX)。
|
||||||
|
geopro::core::GridSpec localSpec = spec;
|
||||||
|
localSpec.ox = 0.0; // 采样核用线局部坐标
|
||||||
|
if (hasTraces) {
|
||||||
|
slab = geopro::io::gpr::assembleGprSurveySlab(files[i].iprb,
|
||||||
|
files[i].ord, t0, t1 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写该 slab 覆盖的合并 brick:X 列 [bcol,bxEnd) → 合并列 +xBrickOffset[i],
|
||||||
|
// Y/Z 全程(含该线 ny/nz 之外的合并余量 → blank)。
|
||||||
|
for (int bz = 0; bz < bZ; ++bz) {
|
||||||
|
for (int by = 0; by < bY; ++by) {
|
||||||
|
for (int lbx = bcol; lbx < bxEnd; ++lbx) {
|
||||||
|
const int mbx = xBrickOffset[i] + lbx; // 合并 brick X 索引
|
||||||
|
const int bw = extentOf(mergedNx, mbx, kStreamBrick);
|
||||||
|
const int bh = extentOf(mergedNy, by, kStreamBrick);
|
||||||
|
const int bd = extentOf(mergedNz, bz, kStreamBrick);
|
||||||
|
std::vector<std::int16_t> voxels(
|
||||||
|
static_cast<std::size_t>(bw) * bh * bd);
|
||||||
|
// 该 brick 是否落在线自身覆盖范围内(线 brick 网格内)。
|
||||||
|
const bool inLine =
|
||||||
|
(lbx < lineBx && by < lineBy && bz < lineBz && hasTraces);
|
||||||
|
if (!inLine) {
|
||||||
|
std::fill(voxels.begin(), voxels.end(),
|
||||||
|
geopro::core::ScalarVolumeI16::kBlank);
|
||||||
|
} else {
|
||||||
|
const int i0 = lbx * kStreamBrick, j0 = by * kStreamBrick,
|
||||||
|
k0 = bz * kStreamBrick;
|
||||||
|
std::size_t wi = 0;
|
||||||
|
for (int kk = 0; kk < bd; ++kk) {
|
||||||
|
for (int jj = 0; jj < bh; ++jj) {
|
||||||
|
for (int ii = 0; ii < bw; ++ii) {
|
||||||
|
const int gi = i0 + ii, gj = j0 + jj, gk = k0 + kk;
|
||||||
|
// 线网格之外的余格(合并 brick 比线 brick 大)→ blank。
|
||||||
|
if (gi >= spec.nx || gj >= spec.ny || gk >= spec.nz) {
|
||||||
|
voxels[wi++] = geopro::core::ScalarVolumeI16::kBlank;
|
||||||
|
} else {
|
||||||
|
voxels[wi++] = geopro::core::sampleGprPoint(
|
||||||
|
slab, localSpec, gi, gj, gk, quant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.writeBrick(mbx, by, bz, voxels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << "[build-stream] 线 " << lineNos[i] << " 写入完成 ("
|
||||||
|
<< (i + 1) << "/" << files.size()
|
||||||
|
<< ") 峰值内存(MB)=" << Probe::peakMemMB() << "\n";
|
||||||
|
}
|
||||||
|
writer.finalize();
|
||||||
|
const double buildMs = swBuild.elapsedMs();
|
||||||
|
|
||||||
|
// 7) 流式金字塔。
|
||||||
|
std::cout << "[build-stream] 流式金字塔 levels=" << levels << "...\n";
|
||||||
|
Stopwatch swPyr;
|
||||||
|
{
|
||||||
|
geopro::data::ChunkedVolumeStore store(out);
|
||||||
|
store.buildPyramidStreaming(levels);
|
||||||
|
}
|
||||||
|
const double pyrMs = swPyr.elapsedMs();
|
||||||
|
|
||||||
|
const std::int64_t voxels =
|
||||||
|
static_cast<std::int64_t>(mergedNx) * mergedNy * mergedNz;
|
||||||
|
const std::int64_t rawBytes = voxels * 2;
|
||||||
|
const std::int64_t dataBytes = storeDataBytes(out);
|
||||||
|
const double ratio =
|
||||||
|
dataBytes > 0 ? static_cast<double>(rawBytes) / dataBytes : 0.0;
|
||||||
|
const double totalMs = swTotal.elapsedMs();
|
||||||
|
const double peak = Probe::peakMemMB();
|
||||||
|
|
||||||
|
std::cout << "\n=== build-stream 指标(多线合并大体)===\n";
|
||||||
|
std::cout << "合并方式 : 沿 X 顺序排列(退路近似,brick 对齐)\n";
|
||||||
|
std::cout << "测线数 : " << files.size() << "\n";
|
||||||
|
std::cout << "合并体维度 : " << mergedNx << " x " << mergedNy << " x "
|
||||||
|
<< mergedNz << "\n";
|
||||||
|
std::cout << "体素数 : " << voxels << "\n";
|
||||||
|
std::cout << "原始体积(B) : " << rawBytes << " ("
|
||||||
|
<< rawBytes / (1024.0 * 1024.0) << " MB)\n";
|
||||||
|
std::cout << "data.bin(B) : " << dataBytes << " ("
|
||||||
|
<< dataBytes / (1024.0 * 1024.0) << " MB)\n";
|
||||||
|
std::cout << "压缩比 : " << ratio << " x\n";
|
||||||
|
std::cout << "扫量化耗时(ms) : " << scanMs << "\n";
|
||||||
|
std::cout << "建体耗时(ms) : " << buildMs << "\n";
|
||||||
|
std::cout << "金字塔耗时(ms) : " << pyrMs << "\n";
|
||||||
|
std::cout << "总耗时(ms) : " << totalMs << "\n";
|
||||||
|
std::cout << "峰值内存(MB) : " << peak << "\n";
|
||||||
|
|
||||||
|
writeMetricLine(
|
||||||
|
"build-stream,lines=" + std::to_string(files.size()) +
|
||||||
|
",cellXY=" + std::to_string(cellXY) + ",cellZ=" + std::to_string(cellZ) +
|
||||||
|
",nx=" + std::to_string(mergedNx) + ",ny=" + std::to_string(mergedNy) +
|
||||||
|
",nz=" + std::to_string(mergedNz) + ",voxels=" + std::to_string(voxels) +
|
||||||
|
",rawB=" + std::to_string(rawBytes) +
|
||||||
|
",dataB=" + std::to_string(dataBytes) +
|
||||||
|
",ratio=" + std::to_string(ratio) + ",scanMs=" + std::to_string(scanMs) +
|
||||||
|
",buildMs=" + std::to_string(buildMs) +
|
||||||
|
",pyrMs=" + std::to_string(pyrMs) +
|
||||||
|
",totalMs=" + std::to_string(totalMs) +
|
||||||
|
",peakMB=" + std::to_string(peak));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int cmdLoad(int argc, char** argv) {
|
int cmdLoad(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()) {
|
||||||
|
|
@ -3357,6 +3720,9 @@ 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 build-stream <dir> [--cellXY 0.05] [--cellZ 0.05] "
|
||||||
|
"[--out <storeDir>] [--levels 3] [--sliceXBricks 8] "
|
||||||
|
"[--maxLines N]\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 offscreen-smoke\n"
|
||||||
|
|
@ -3389,6 +3755,7 @@ int main(int argc, char** argv) {
|
||||||
const std::string cmd = argv[1];
|
const std::string cmd = argv[1];
|
||||||
try {
|
try {
|
||||||
if (cmd == "build") return cmdBuild(argc, argv);
|
if (cmd == "build") return cmdBuild(argc, argv);
|
||||||
|
if (cmd == "build-stream") return cmdBuildStream(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 == "offscreen-smoke") return cmdOffscreenSmoke();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue