Skip to content

Commit d980ad9

Browse files
harrismclaude
andauthored
NanoVDB: encode points for any resource in PointsToGrid (CUDA) (#2244)
* NanoVDB: encode points for any resource in PointsToGrid (CUDA) The point-encode step in tools::cuda::PointsToGrid was only reachable for the default DeviceResource, because it lived in the BuildT=Point full specialization of processPoints (i.e. <Point, DeviceResource>). A Point grid built with a custom resource fell through to the generic no-op and was silently left with unencoded point data. Fold the encode into the generic processPoints and gate it with if constexpr(is_same<BuildT, Point>). The encode kernels run on device data and are independent of the resource; only the trailing d_indx deallocation routes through ResourceT. Non-Point builds and default- resource Point builds are unchanged. Add a regression test that builds a NanoGrid<Point> through a custom resource and asserts every input point is recoverable from the encoded per-voxel data. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Mark Harris <mharris@nvidia.com> * NanoVDB: move the point-encode kernels out of the if-constexpr block nvcc rejects an extended lambda defined directly inside the block of an if constexpr statement on some host compilers, so the nine encode launches failed to compile against MSVC while building fine with gcc and clang. Give them their own member function and have the branch call it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Mark Harris <mharris@nvidia.com> --------- Signed-off-by: Mark Harris <mharris@nvidia.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 20f904e commit d980ad9

2 files changed

Lines changed: 77 additions & 13 deletions

File tree

nanovdb/nanovdb/tools/cuda/PointsToGrid.cuh

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,13 @@ public:
373373
template<typename PtrT>
374374
void processPoints(const PtrT points, size_t pointCount);
375375

376+
// Only instantiated when BuildT == Point, from the corresponding branch of
377+
// processPoints. Holds the encode kernels because nvcc rejects an extended
378+
// lambda defined directly inside the block of an if constexpr statement on
379+
// some host compilers.
380+
template<typename PtrT>
381+
void encodePoints(const PtrT points, size_t pointCount);
382+
376383
void processBBox();
377384

378385
// the following methods are only defined when BuildT == Point
@@ -1164,23 +1171,24 @@ inline void PointsToGrid<BuildT, ResourceT>::processLeafNodes(size_t pointCount)
11641171

11651172
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
11661173

1174+
// The point-encode kernels operate on device data and are independent of the
1175+
// resource type; only the trailing d_indx deallocation routes through ResourceT.
1176+
// Dispatching the encode with if constexpr keeps it reachable for any ResourceT.
11671177
template<typename BuildT, typename ResourceT>
11681178
template<typename PtrT>
1169-
inline void PointsToGrid<BuildT, ResourceT>::processPoints(const PtrT, size_t pointCount)
1179+
inline void PointsToGrid<BuildT, ResourceT>::processPoints(const PtrT points, size_t pointCount)
11701180
{
1181+
if constexpr(util::is_same<BuildT, Point>::value) this->encodePoints(points, pointCount);
11711182
mResource->deallocate_async(mData.d_indx, pointCount*sizeof(uint32_t), ResourceT::DEFAULT_ALIGNMENT, mStream);
1172-
}
1183+
}// PointsToGrid<BuildT, ResourceT>::processPoints
11731184

1174-
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1175-
1176-
// Template specialization with BuildT = Point
1177-
template<>
1185+
template<typename BuildT, typename ResourceT>
11781186
template<typename PtrT>
1179-
inline void PointsToGrid<Point>::processPoints(const PtrT points, size_t pointCount)
1187+
inline void PointsToGrid<BuildT, ResourceT>::encodePoints(const PtrT points, size_t pointCount)
11801188
{
11811189
switch (mPointType){
11821190
case PointType::Disable:
1183-
throw std::runtime_error("PointsToGrid<Point>::processPoints: mPointType == PointType::Disable\n");
1191+
throw std::runtime_error("PointsToGrid<Point, ResourceT>::encodePoints: mPointType == PointType::Disable\n");
11841192
case PointType::PointID:
11851193
util::cuda::lambdaKernel<<<numBlocks(pointCount), mNumThreads, 0, mStream>>>(pointCount, [=] __device__(size_t tid, PointsToGridData<Point> *d_data) {
11861194
d_data->template getPoint<uint32_t>(tid) = d_data->d_indx[tid];
@@ -1227,12 +1235,9 @@ inline void PointsToGrid<Point>::processPoints(const PtrT points, size_t pointCo
12271235
}, mDeviceData); cudaCheckError();
12281236
break;
12291237
default:
1230-
printf("Internal error in PointsToGrid<Point>::processPoints\n");
1238+
printf("Internal error in PointsToGrid<Point, ResourceT>::encodePoints\n");
12311239
}
1232-
// This is the PointsToGrid<Point> (== <Point, DeviceResource>) member specialization,
1233-
// so ResourceT is not a name here; mResource is a DeviceResource* and the alignment is concrete.
1234-
mResource->deallocate_async(mData.d_indx, pointCount*sizeof(uint32_t), nanovdb::cuda::DeviceResource::DEFAULT_ALIGNMENT, mStream);
1235-
}// PointsToGrid<Point>::processPoints
1240+
}// PointsToGrid<BuildT, ResourceT>::encodePoints
12361241

12371242
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
12381243

nanovdb/nanovdb/unittest/TestMemoryResource.cu

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,63 @@ TEST(TestMemoryResource, PointsToGrid_InjectedInstanceSeam)
209209
ASSERT_EQ(cudaFree(d_coords), cudaSuccess);
210210
}
211211

212+
//======================================================================
213+
// A Point grid built through a custom (non-default) resource must still
214+
// encode its point coordinates. The encode path must not depend on the
215+
// resource type; only allocation does.
216+
//======================================================================
217+
218+
TEST(TestMemoryResource, PointsToGrid_PointEncodedWithCustomResource)
219+
{
220+
using BuildT = nanovdb::Point;
221+
using Vec3T = nanovdb::Vec3d;
222+
223+
const size_t pointCount = 256;
224+
std::vector<Vec3T> points;
225+
points.reserve(pointCount);
226+
std::srand(98765);
227+
const int max = 128, min = -max;
228+
auto op = [&]() { return double(std::rand() % (max - min) + min); };
229+
while (points.size() < pointCount) points.emplace_back(op(), op(), op());
230+
231+
Vec3T* d_points = nullptr;
232+
const size_t pointSize = points.size() * sizeof(Vec3T);
233+
ASSERT_EQ(cudaMalloc(&d_points, pointSize), cudaSuccess);
234+
ASSERT_EQ(cudaMemcpy(d_points, points.data(), pointSize, cudaMemcpyHostToDevice), cudaSuccess);
235+
236+
const double voxelSize = 8.0;
237+
CountingResource res;
238+
nanovdb::tools::cuda::PointsToGrid<BuildT, CountingResource> converter(voxelSize, nanovdb::Vec3d(0.0), 0, res);
239+
auto handle = converter.getHandle(d_points, pointCount);
240+
ASSERT_EQ(cudaStreamSynchronize(0), cudaSuccess);
241+
ASSERT_EQ(cudaFree(d_points), cudaSuccess);
242+
243+
EXPECT_TRUE(handle.deviceData());
244+
EXPECT_TRUE(handle.deviceGrid<BuildT>());
245+
246+
handle.deviceDownload();
247+
auto* grid = handle.grid<BuildT>();
248+
ASSERT_TRUE(grid);
249+
EXPECT_EQ(pointCount, grid->pointCount());
250+
251+
// Every input point must be recoverable from the encoded per-voxel point data.
252+
nanovdb::PointAccessor<Vec3T, BuildT> acc(*grid);
253+
ASSERT_TRUE(acc);
254+
for (size_t i = 0; i < points.size(); ++i) {
255+
const nanovdb::Coord ijk = grid->worldToIndex(points[i]).round();
256+
ASSERT_TRUE(acc.probeLeaf(ijk) != nullptr);
257+
ASSERT_TRUE(acc.isActive(ijk));
258+
const Vec3T *start = nullptr, *stop = nullptr;
259+
const uint64_t count = acc.voxelPoints(ijk, start, stop);
260+
ASSERT_TRUE(start);
261+
ASSERT_TRUE(stop);
262+
bool found = false;
263+
for (uint64_t j = 0; !found && j < count; ++j)
264+
found = nanovdb::math::isApproxZero<double>((points[i] - start[j]).lengthSqr());
265+
EXPECT_TRUE(found);
266+
}
267+
268+
EXPECT_GT(res.allocs, 0);
269+
}
270+
212271
} // unnamed namespace

0 commit comments

Comments
 (0)