Files
ViewDesignEngine/tests/gpu/test_gpu_acceleration.cpp
T
茂之钳 e469ef75ef
CI / Build & Test (push) Failing after 35s
CI / Release Build (push) Failing after 41s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled
feat(v5-M4): industrial formats + GPU acceleration + Python bindings
M4.1 — 工业格式 (Agent #0):
- JT parser: ISO 14306 Segment→Part→Mesh/LOD, XT B-Rep decode
- Parasolid XT: text/binary parser, Body→BrepModel mapping
- ACIS SAT: text format parser with version handling
- IFC: SPF parser, IfcWall/Slab/Beam/Column entities
- STEP AP242: PMI annotation + tolerance export
- 47 tests covering all formats + edge cases

M4.2 — GPU 加速 (Agent #1):
- CUDA kernels: mc_kernel, qem_cost_kernel, tri_intersect_kernel
- __constant__ memory for edge tables, atomic triangle collection
- CPU fallback when CUDA unavailable (seamless degradation)
- VDE_USE_CUDA CMake option, .cu compilation support
- 9 tests with CPU path validation

M4.3 — Python 绑定 (Agent #2):
- vde_brep: BrepModel, make_*, boolean, STEP/IGES, heal, validate
- vde_sdf: primitives, operations, to_mesh, gradient descent
- vde_cam: roughing/finishing/drilling, Tool, ToolLibrary
- vde_assembly: Assembly, AssemblyNode, interference, explode
- Version: 3.3.0 synced across pyproject/setup/__init__

13 files, ~5200 lines, 56+ tests
2026-07-26 21:34:44 +08:00

255 lines
9.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file test_gpu_acceleration.cpp
* @brief GPU 加速模块测试(8 项)
*
* 测试覆盖:
* 1. gpu_available — 检测 CUDA 可用性
* 2. gpu_marching_cubes — SDF → 网格(球体)
* 3. gpu_marching_cubes — 低分辨率
* 4. gpu_marching_cubes — 立方体 SDF
* 5. gpu_mesh_simplify — 基本简化
* 6. gpu_mesh_simplify — 微小 target_ratio
* 7. gpu_boolean_intersect — 两个相交球体
* 8. gpu_boolean_intersect — 不相交网格
*/
#include "vde/gpu/gpu_acceleration.h"
#include "vde/mesh/marching_cubes.h"
#include "vde/mesh/halfedge_mesh.h"
#include <gtest/gtest.h>
#include <cmath>
#include <memory>
using namespace vde::gpu;
using vde::core::AABB3D;
using vde::core::Point3D;
using vde::core::Vector3D;
using vde::mesh::HalfedgeMesh;
// ── 帮助函数 ─────────────────────────────────────────────────────
/// 创建一个简单球体 SDF 函数
static auto make_sphere_sdf(double cx, double cy, double cz, double r) {
return [=](double x, double y, double z) -> double {
return std::sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy) + (z - cz) * (z - cz)) - r;
};
}
/// 创建立方体 SDF 函数
static auto make_box_sdf(double hx, double hy, double hz) {
return [=](double x, double y, double z) -> double {
double dx = std::abs(x) - hx, dy = std::abs(y) - hy, dz = std::abs(z) - hz;
return std::sqrt(std::max(dx, 0.0) * std::max(dx, 0.0) +
std::max(dy, 0.0) * std::max(dy, 0.0) +
std::max(dz, 0.0) * std::max(dz, 0.0)) +
std::min(std::max({dx, dy, dz}), 0.0);
};
}
/// 快速创建简单三角网格(正四面体)
static HalfedgeMesh make_tetrahedron(double ox, double oy, double oz, double s) {
HalfedgeMesh mesh;
std::vector<Point3D> verts = {
{ox, oy + s, oz},
{ox - s * 0.866, oy, oz - s * 0.5},
{ox + s * 0.866, oy, oz - s * 0.5},
{ox, oy, oz + s}
};
std::vector<std::array<int, 3>> tris = {
{0, 1, 2}, {0, 2, 3}, {0, 3, 1}, {1, 3, 2}
};
for (auto& v : verts) mesh.add_vertex(v);
for (auto& t : tris) mesh.add_face({t[0], t[1], t[2]});
return mesh;
}
/// 验证网格基本有效性
static void expect_valid_mesh(const HalfedgeMesh& mesh) {
EXPECT_GT(mesh.num_vertices(), 0u);
EXPECT_GT(mesh.num_faces(), 0u);
EXPECT_GT(mesh.num_edges(), 0u);
// 欧拉公式:V - E + F ≈ 2(对于闭曲面)
// 不作严格检查,但确保拓扑合理
int euler = static_cast<int>(mesh.num_vertices()) -
static_cast<int>(mesh.num_edges()) +
static_cast<int>(mesh.num_faces());
EXPECT_GE(euler, 0) << "Euler characteristic should be non-negative";
}
// ======================================================================
// 测试 1gpu_available
// ======================================================================
TEST(GpuAcceleration, GpuAvailable) {
// 无论 CUDA 是否可用,函数必须不崩溃并返回布尔值
bool avail = gpu_available();
EXPECT_TRUE(avail == true || avail == false);
#if VDE_USE_CUDA
// CUDA 编译时开启 → 运行时至少应无错误
// (CUDA 设备可能为 0,但调用不崩溃)
SUCCEED() << "CUDA compiled in, runtime detection: " << (avail ? "YES" : "NO");
#else
EXPECT_FALSE(avail) << "Without CUDA build, gpu_available must return false";
#endif
}
// ======================================================================
// 测试 2gpu_marching_cubes — 球体 SDF
// ======================================================================
TEST(GpuAcceleration, MarchingCubesSphere) {
auto sdf = make_sphere_sdf(0, 0, 0, 1.0);
AABB3D bounds({-1.5, -1.5, -1.5}, {1.5, 1.5, 1.5});
HalfedgeMesh mesh = gpu_marching_cubes(sdf, bounds, 32);
expect_valid_mesh(mesh);
// 球体应当构成闭曲面
EXPECT_GE(mesh.num_faces(), 50u) << "Low-res sphere should have at least 50 faces";
// 所有顶点应大致在球体表面上
for (size_t i = 0; i < mesh.num_vertices(); ++i) {
const auto& p = mesh.vertex(i);
double dist = std::sqrt(p.x() * p.x() + p.y() * p.y() + p.z() * p.z());
EXPECT_NEAR(dist, 1.0, 0.15) << "Vertex " << i << " distance from origin";
}
}
// ======================================================================
// 测试 3gpu_marching_cubes — 低分辨率
// ======================================================================
TEST(GpuAcceleration, MarchingCubesLowRes) {
auto sdf = make_sphere_sdf(0, 0, 0, 1.0);
AABB3D bounds({-1.2, -1.2, -1.2}, {1.2, 1.2, 1.2});
HalfedgeMesh mesh = gpu_marching_cubes(sdf, bounds, 8);
// 极低分辨率仍有输出
EXPECT_GT(mesh.num_vertices(), 0u);
EXPECT_GT(mesh.num_faces(), 0u);
}
// ======================================================================
// 测试 4gpu_marching_cubes — 立方体 SDF
// ======================================================================
TEST(GpuAcceleration, MarchingCubesBox) {
auto sdf = make_box_sdf(1.0, 0.5, 0.3);
AABB3D bounds({-1.5, -1.0, -0.8}, {1.5, 1.0, 0.8});
HalfedgeMesh mesh = gpu_marching_cubes(sdf, bounds, 24);
expect_valid_mesh(mesh);
// 检查包围盒尺寸与预期一致
auto bb = mesh.bounds();
double max_dim = std::max({bb.extent().x(), bb.extent().y(), bb.extent().z()});
EXPECT_GT(max_dim, 0.5);
EXPECT_LT(max_dim, 4.0);
}
// ======================================================================
// 测试 5gpu_mesh_simplify — 基本简化
// ======================================================================
TEST(GpuAcceleration, MeshSimplifyBasic) {
auto sdf = make_sphere_sdf(0, 0, 0, 1.0);
AABB3D bounds({-1.5, -1.5, -1.5}, {1.5, 1.5, 1.5});
HalfedgeMesh original = gpu_marching_cubes(sdf, bounds, 32);
size_t orig_faces = original.num_faces();
EXPECT_GT(orig_faces, 0u);
// 简化为 50% 面数
HalfedgeMesh simplified = gpu_mesh_simplify(original, 0.5f);
expect_valid_mesh(simplified);
// 简化后面数应减少
EXPECT_LE(simplified.num_faces(), orig_faces)
<< "Simplified mesh should have ≤ original face count";
}
// ======================================================================
// 测试 6gpu_mesh_simplify — 极低 target_ratio
// ======================================================================
TEST(GpuAcceleration, MeshSimplifyTinyRatio) {
auto sdf = make_sphere_sdf(0, 0, 0, 1.0);
AABB3D bounds({-1.5, -1.5, -1.5}, {1.5, 1.5, 1.5});
HalfedgeMesh original = gpu_marching_cubes(sdf, bounds, 20);
ASSERT_GT(original.num_faces(), 10u);
// target_ratio = 0.1 (极低比例)
HalfedgeMesh simplified = gpu_mesh_simplify(original, 0.1f);
// 不应崩溃且至少保留一些面
EXPECT_GT(simplified.num_vertices(), 3u);
EXPECT_GT(simplified.num_faces(), 1u);
EXPECT_LT(simplified.num_faces(), original.num_faces());
}
// ======================================================================
// 测试 7gpu_boolean_intersect — 两个相交球体
// ======================================================================
TEST(GpuAcceleration, BooleanIntersectIntersecting) {
// 两个相交球体(中心相距 1.0,半径各 1.2)
auto sdf_a = make_sphere_sdf(-0.5, 0, 0, 1.2);
auto sdf_b = make_sphere_sdf(0.5, 0, 0, 1.2);
AABB3D bounds({-2.0, -1.5, -1.5}, {2.0, 1.5, 1.5});
HalfedgeMesh mesh_a = gpu_marching_cubes(sdf_a, bounds, 24);
HalfedgeMesh mesh_b = gpu_marching_cubes(sdf_b, bounds, 24);
ASSERT_GT(mesh_a.num_faces(), 0u);
ASSERT_GT(mesh_b.num_faces(), 0u);
HalfedgeMesh result = gpu_boolean_intersect(mesh_a, mesh_b);
// 交集存在 → 非空输出
EXPECT_GT(result.num_vertices(), 0u) << "Intersection of overlapping spheres should not be empty";
}
// ======================================================================
// 测试 8gpu_boolean_intersect — 不相交网格
// ======================================================================
TEST(GpuAcceleration, BooleanIntersectDisjoint) {
// 两个远距离球体(中心相距 10,半径各 1)
auto sdf_a = make_sphere_sdf(-5, 0, 0, 1.0);
auto sdf_b = make_sphere_sdf(5, 0, 0, 1.0);
AABB3D bounds({-6.5, -1.5, -1.5}, {6.5, 1.5, 1.5});
HalfedgeMesh mesh_a = gpu_marching_cubes(sdf_a, bounds, 20);
HalfedgeMesh mesh_b = gpu_marching_cubes(sdf_b, bounds, 20);
ASSERT_GT(mesh_a.num_faces(), 0u);
ASSERT_GT(mesh_b.num_faces(), 0u);
HalfedgeMesh result = gpu_boolean_intersect(mesh_a, mesh_b);
// 不相交 → 空输出(0 顶点或 0 面)
// 不要求严格为 0,但不应有大量三角形
EXPECT_LE(result.num_faces(), mesh_a.num_faces() / 2u)
<< "Disjoint meshes should have few or no intersection faces";
}
// ======================================================================
// 额外测试:空网格安全
// ======================================================================
TEST(GpuAcceleration, EmptyMeshSafety) {
HalfedgeMesh empty;
// simplify 空网格不崩溃
HalfedgeMesh r1 = gpu_mesh_simplify(empty, 0.5f);
EXPECT_EQ(r1.num_vertices(), 0u);
EXPECT_EQ(r1.num_faces(), 0u);
// boolean intersect 空网格不崩溃
auto sdf = make_sphere_sdf(0, 0, 0, 1.0);
AABB3D bounds({-1.5, -1.5, -1.5}, {1.5, 1.5, 1.5});
HalfedgeMesh sphere = gpu_marching_cubes(sdf, bounds, 16);
HalfedgeMesh r2 = gpu_boolean_intersect(empty, sphere);
EXPECT_EQ(r2.num_faces(), 0u);
HalfedgeMesh r3 = gpu_boolean_intersect(sphere, empty);
EXPECT_EQ(r3.num_faces(), 0u);
}