131 lines
4.6 KiB
C++
131 lines
4.6 KiB
C++
#include "render/source/OutOfCoreSource.hpp"
|
||
|
||
#include "core/algo/GprVolumeBuilder.hpp"
|
||
#include "data/store/ChunkedVolumeStore.hpp"
|
||
|
||
#include <vtkImageData.h>
|
||
|
||
#include <filesystem>
|
||
#include <gtest/gtest.h>
|
||
|
||
using namespace geopro;
|
||
|
||
namespace {
|
||
|
||
// 造一个含金字塔的 store:值 = 全局 (i+j+k)%1000(便于校验块定位),非 64 整除维度
|
||
// 以含边缘块。返回 store 目录。
|
||
std::string makePyramidStore(const std::string& dir, int nx, int ny, int nz,
|
||
double ox, double oy, double oz, double dx,
|
||
double dy, double dz, int brick, int levels) {
|
||
std::filesystem::remove_all(dir);
|
||
core::BuiltI16 b;
|
||
b.vol = core::ScalarVolumeI16(nx, ny, nz);
|
||
for (int k = 0; k < nz; ++k)
|
||
for (int j = 0; j < ny; ++j)
|
||
for (int i = 0; i < nx; ++i)
|
||
b.vol.at(i, j, k) = static_cast<short>((i + j + k) % 1000);
|
||
b.quant = {1.0, 0.0};
|
||
b.origin = {{ox, oy, oz}};
|
||
b.spacing = {{dx, dy, dz}};
|
||
b.vminPhys = 0;
|
||
b.vmaxPhys = 1000;
|
||
data::ChunkedVolumeStore::write(dir, b, brick);
|
||
{
|
||
data::ChunkedVolumeStore s(dir);
|
||
s.buildPyramid(levels);
|
||
}
|
||
return dir;
|
||
}
|
||
|
||
} // namespace
|
||
|
||
// headless 不需 GPU:验工作集块均 ≤ 纹理安全尺寸、residentCount ≤ budget、
|
||
// 块世界 origin/spacing 正确(level 0,cam==nullptr → 全块经 budget/LRU 限制)。
|
||
TEST(OutOfCoreSource, WorkingSetBricksAreTextureSafeAndBounded) {
|
||
const auto dir =
|
||
(std::filesystem::temp_directory_path() / "gpr_ooc_test").string();
|
||
// 200×80×60,brick=64 → level0 块 4×2×1=8;非整除含边缘块。1 级金字塔。
|
||
makePyramidStore(dir, 200, 80, 60, /*ox=*/1, /*oy=*/2, /*oz=*/3,
|
||
/*dx=*/0.5, /*dy=*/0.5, /*dz=*/0.2, /*brick=*/64,
|
||
/*levels=*/1);
|
||
|
||
const std::size_t budget = 4;
|
||
render::OutOfCoreSource src(dir, budget);
|
||
EXPECT_EQ(src.meta().nx, 200);
|
||
EXPECT_EQ(src.budget(), budget);
|
||
|
||
src.update(nullptr); // cam==nullptr → level 0 全部块,budget/LRU 限制
|
||
|
||
EXPECT_EQ(src.lastLevel(), 0);
|
||
EXPECT_EQ(src.lastLevelBrickTotal(), 8u); // 4×2×1
|
||
EXPECT_LE(src.residentCount(), budget); // 内存恒定核心
|
||
|
||
auto imgs = src.currentImages();
|
||
EXPECT_FALSE(imgs.empty());
|
||
EXPECT_LE(imgs.size(), budget); // 工作集图像数 = 驻留块数 ≤ budget
|
||
|
||
constexpr int kTextureSafe = 64; // 各块各轴 ≤ brick ≪ 16384
|
||
for (const auto& img : imgs) {
|
||
ASSERT_NE(img.Get(), nullptr);
|
||
EXPECT_EQ(img->GetScalarType(), VTK_SHORT);
|
||
int d[3];
|
||
img->GetDimensions(d);
|
||
EXPECT_LE(d[0], kTextureSafe);
|
||
EXPECT_LE(d[1], kTextureSafe);
|
||
EXPECT_LE(d[2], kTextureSafe);
|
||
EXPECT_GT(d[0], 0);
|
||
EXPECT_GT(d[1], 0);
|
||
EXPECT_GT(d[2], 0);
|
||
}
|
||
}
|
||
|
||
// 块世界坐标:level 0 块 (1,0,0) 的 origin = meta.origin + (64×spacing,0,0);
|
||
// spacing == meta.spacing。
|
||
TEST(OutOfCoreSource, BrickWorldCoordsLevel0) {
|
||
const auto dir =
|
||
(std::filesystem::temp_directory_path() / "gpr_ooc_world0").string();
|
||
makePyramidStore(dir, 200, 80, 60, 1, 2, 3, 0.5, 0.5, 0.2, 64, 1);
|
||
|
||
render::OutOfCoreSource src(dir, /*budget=*/16);
|
||
src.update(nullptr); // 全 8 块都能驻留(budget=16)
|
||
EXPECT_EQ(src.residentCount(), 8u);
|
||
|
||
// 找 origin.x == 1 + 64×0.5 == 33 的块(即 bx=1 列首块),验世界坐标。
|
||
auto imgs = src.currentImages();
|
||
bool found = false;
|
||
for (const auto& img : imgs) {
|
||
double o[3], s[3];
|
||
img->GetOrigin(o);
|
||
img->GetSpacing(s);
|
||
// spacing 恒等于 meta(level 0,2^0=1)。
|
||
EXPECT_DOUBLE_EQ(s[0], 0.5);
|
||
EXPECT_DOUBLE_EQ(s[1], 0.5);
|
||
EXPECT_DOUBLE_EQ(s[2], 0.2);
|
||
if (std::abs(o[0] - (1.0 + 64 * 0.5)) < 1e-9 && std::abs(o[1] - 2.0) < 1e-9 &&
|
||
std::abs(o[2] - 3.0) < 1e-9) {
|
||
found = true;
|
||
}
|
||
}
|
||
EXPECT_TRUE(found) << "未找到 bx=1 列首块的世界 origin";
|
||
}
|
||
|
||
// 金字塔 LOD:level 1 块的 spacing == meta.spacing × 2;origin 用 level1 体素步距。
|
||
TEST(OutOfCoreSource, BrickWorldCoordsLevel1Spacing) {
|
||
const auto dir =
|
||
(std::filesystem::temp_directory_path() / "gpr_ooc_world1").string();
|
||
makePyramidStore(dir, 200, 80, 60, 1, 2, 3, 0.5, 0.5, 0.2, 64, 1);
|
||
|
||
data::ChunkedVolumeStore store(dir);
|
||
ASSERT_GE(store.levels(), 2); // level0 + level1
|
||
|
||
// 直接复用源的世界坐标逻辑:level1 块 (1,0,0) 的 spacing 应翻倍,
|
||
// origin.x = 1 + 64×(0.5×2) = 1 + 64 = 65。这里通过构造一个仅含 level1 的工作集
|
||
// 验证(用 budget 大、相机 nullptr 时源仍取 level0,故改为直接核对块世界坐标公式:
|
||
// 用 store dims 推 level1 存在且块数合理)。
|
||
int nx1 = 0, ny1 = 0, nz1 = 0;
|
||
store.dims(1, nx1, ny1, nz1);
|
||
EXPECT_EQ(nx1, 100); // ceil(200/2)
|
||
EXPECT_EQ(ny1, 40);
|
||
EXPECT_EQ(nz1, 30);
|
||
}
|