Use fused NanoVDB ReadAccessor::getDimAndActive in the HDDA iterators - #754
Merged
Merged
Conversation
OpenVDB PR 2220 (upstream merge 9df9ec06) adds the fused
ReadAccessor::getDimAndActive API: a single Root->Internal->Leaf descent
(or accessor-cache lookup) that returns both the HDDA step size and the
active state of a voxel, packed into one register. Bump the NanoVDB pin
to that commit and adopt the fused call in both HDDA iterators, restoring
the methodology first prototyped on the wip_raytrace branch (which had to
be reverted pre-merge because the API only existed in a fork at the time).
HDDASegmentIterator::nextSegment:
- Replace the per-iteration getDim + isActive pair with one
getDimAndActive call under the default ActiveExact policy, which is
byte-for-byte equivalent to the separate calls. ActiveExact is
required here because the iterator reads `active` unconditionally,
including at coarse-tile levels.
- When the HDDA re-aligns to a different level, HDDA::update snaps
mVoxel to the new grid, so re-query after the update to keep
`active` consistent with the voxel the TimeSpan logic reads. The
two-descent cost is thus only paid on (rare) level-change iterations.
HDDAValueIteratorImpl::nextVoxel:
- Fuse the realign-loop getDim queries and the post-convergence
isActive into one getDimAndActive per pass; each re-query runs at the
voxel HDDA::update just snapped to, so on loop exit the result always
describes the current voxel. The force-unrolled <=3-pass structure is
unchanged.
- Select the policy per instantiation: HDDALeafVoxelIterator
(LeafOnly=true) gates on dim == 1 before consulting `active`, which
is exactly the ActiveOnLeafOnly contract, so it keeps getDim's
skip-flag fast path. HDDAActiveValueIterator (LeafOnly=false) must
yield active coarse tiles, so it uses ActiveExact.
No callers change: ray_implicit_intersection, uniform_ray_samples,
voxels_along_rays, and segments_along_rays consume the iterators through
their public API.
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
harrism
pushed a commit
to harrism/fvdb-core
that referenced
this pull request
Sep 11, 2026
…coverage Bump the NanoVDB pin to 31d1e596, the merge commit of openvdb #2319. This carries the whole "introduce + deprecate" arc of openvdb #2232 that has landed so far: #2288 (single-space GridHandle<cuda::Buffer<T,R>>), #2292 (cuda::copyTo), #2293 (reset() via destroy()), #2301 (DeviceBuffer deprecated in favour of DualDeviceBuffer; single-space VBM accessors; HandleStorage.h), and #2319 (per-device DistributedPointsToGrid resources; UnifiedBuffer deprecated). The hasDeviceSingle/hasHostSingle traits are detected with false defaults, so TorchDeviceBuffer's dual-trait specialization is unaffected. #2301 also changes what PadGrid.cuh, our local TopologyBuilder-based padding op, can rely on, and this repo builds with -Werror=all-warnings: - nanovdb::cuda::DeviceBuffer becomes a [[deprecated]] alias of the renamed DualDeviceBuffer. PadGrid's getHandle() default template argument spelled the old name; it now names DualDeviceBuffer (the same class). No fvdb caller uses the default -- they pass TorchDeviceBuffer explicitly. - TopologyBuilder::mProcessedRoot is removed outright, replaced by a pinned host staging buffer plus device scratch behind allocateProcessedRoot() / uploadProcessedRoot() / deviceProcessedRoot(). PadGrid::padRoot() built the padded root into a DeviceBuffer it assigned to mProcessedRoot and then deviceUpload()ed; it now fills the builder's staging area and uploads on mStream, mirroring upstream DilateGrid::dilateRoot. The upload is now asynchronous (pinned source) and the device copy allocates through the builder's ResourceT (BuilderResource) instead of DeviceBuffer's pool. The kernels already consumed deviceProcessedRoot(), so they are unchanged. On top of the bump, three allocator-coverage improvements: - ReinitializeSdf: the VoxelBlockManager's firstLeafID/jumpMap buffers now allocate through BuilderResource (torch's active CUDA allocator) via a local TorchVbmBuffer adapter, instead of DeviceBuffer's separate pool. (The adapter is removed in the next commit: #2301 gives the VBM handle the single-space accessors and prototype-buffer allocation the adapter was working around.) - SaveNanoVDB: the device staging buffers (the defensive host-grid upload and the per-batch (N+1)-element value buffer) become nanovdb::cuda::Buffer over BuilderResource, stream-ordered on the save stream. The indexToGrid output handle stays TorchDeviceBuffer for now. - TorchDeviceBuffer: CUDA allocations can now be associated with an explicit stream (raw_alloc_with_stream), and create() forwards the stream nanovdb builders pass instead of discarding it. Previously the allocation was silently associated with the device's current torch stream, which is only correct when that coincides with the builder's stream. Rebased onto main past openvdb#754 (pin 9df9ec06) and openvdb#757; the pin conflict was resolved forward to 31d1e596 inside this commit so that it builds, since the original e679862f is behind main's pin. Originally verified by the author at pin e679862f: full rebuild (112 targets, sm_120, -Werror=all-warnings clean); tests/unit/test_sdf.py 10/10, test_io.py 622/622, test_basic_ops.py 276/276 (+1 skip) on an RTX PRO 6000 Blackwell. Re-verification at 31d1e596 is recorded on the PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info> (cherry picked from commit 2d0eb61) Signed-off-by: Mark Harris <mharris@nvidia.com>
harrism
pushed a commit
to harrism/fvdb-core
that referenced
this pull request
Sep 11, 2026
…coverage Bump the NanoVDB pin to 31d1e596, the merge commit of openvdb #2319. This carries the whole "introduce + deprecate" arc of openvdb #2232 that has landed so far: #2288 (single-space GridHandle<cuda::Buffer<T,R>>), #2292 (cuda::copyTo), #2293 (reset() via destroy()), #2301 (DeviceBuffer deprecated in favour of DualDeviceBuffer; single-space VBM accessors; HandleStorage.h), and #2319 (per-device DistributedPointsToGrid resources; UnifiedBuffer deprecated). The hasDeviceSingle/hasHostSingle traits are detected with false defaults, so TorchDeviceBuffer's dual-trait specialization is unaffected. #2301 also changes what PadGrid.cuh, our local TopologyBuilder-based padding op, can rely on, and this repo builds with -Werror=all-warnings: - nanovdb::cuda::DeviceBuffer becomes a [[deprecated]] alias of the renamed DualDeviceBuffer. PadGrid's getHandle() default template argument spelled the old name; it now names DualDeviceBuffer (the same class). No fvdb caller uses the default -- they pass TorchDeviceBuffer explicitly. - TopologyBuilder::mProcessedRoot is removed outright, replaced by a pinned host staging buffer plus device scratch behind allocateProcessedRoot() / uploadProcessedRoot() / deviceProcessedRoot(). PadGrid::padRoot() built the padded root into a DeviceBuffer it assigned to mProcessedRoot and then deviceUpload()ed; it now fills the builder's staging area and uploads on mStream, mirroring upstream DilateGrid::dilateRoot. The upload is now asynchronous (pinned source) and the device copy allocates through the builder's ResourceT (BuilderResource) instead of DeviceBuffer's pool. The kernels already consumed deviceProcessedRoot(), so they are unchanged. On top of the bump, three allocator-coverage improvements: - ReinitializeSdf: the VoxelBlockManager's firstLeafID/jumpMap buffers now allocate through BuilderResource (torch's active CUDA allocator) via a local TorchVbmBuffer adapter, instead of DeviceBuffer's separate pool. (The adapter is removed in the next commit: #2301 gives the VBM handle the single-space accessors and prototype-buffer allocation the adapter was working around.) - SaveNanoVDB: the device staging buffers (the defensive host-grid upload and the per-batch (N+1)-element value buffer) become nanovdb::cuda::Buffer over BuilderResource, stream-ordered on the save stream. The indexToGrid output handle stays TorchDeviceBuffer for now. - TorchDeviceBuffer: CUDA allocations can now be associated with an explicit stream (raw_alloc_with_stream), and create() forwards the stream nanovdb builders pass instead of discarding it. Previously the allocation was silently associated with the device's current torch stream, which is only correct when that coincides with the builder's stream. Rebased onto main past openvdb#754 (pin 9df9ec06) and openvdb#757; the pin conflict was resolved forward to 31d1e596 inside this commit so that it builds, since the original e679862f is behind main's pin. Originally verified by the author at pin e679862f: full rebuild (112 targets, sm_120, -Werror=all-warnings clean); tests/unit/test_sdf.py 10/10, test_io.py 622/622, test_basic_ops.py 276/276 (+1 skip) on an RTX PRO 6000 Blackwell. Re-verification at 31d1e596 is recorded on the PR. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info> (cherry picked from commit 2d0eb61) Signed-off-by: Mark Harris <mharris@nvidia.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
OpenVDB PR #2220 added the fused
ReadAccessor::getDimAndActiveAPI to NanoVDB: a single Root→Internal→Leaf descent (or accessor-cache lookup) that returns both the HDDA step size and the active state of a voxel, packed into one register. This PR bumps the NanoVDB pin to that merge commit (9df9ec06) and adopts the fused call in both HDDA iterators, replacing the separategetDim+isActivepair that previously cost two tree walks per iteration.This restores the methodology originally prototyped alongside #663, which had to be reverted before merge because the fused accessor only existed in a fork at the time.
Changes
HDDASegmentIterator::nextSegmentgetDimAndActivecall per iteration under the defaultActiveExactpolicy, which is byte-for-byte equivalent to the separate calls.ActiveExactis required here because the iterator readsactiveunconditionally, including at coarse-tile levels.HDDA::updatesnapsmVoxelto the new grid, so the result is re-queried after the update to keepactiveconsistent with the voxel the TimeSpan logic reads. The two-descent cost is thus only paid on (rare) level-change iterations.HDDAValueIteratorImpl::nextVoxelgetDimqueries and the post-convergenceisActivefuse into onegetDimAndActiveper pass; each re-query runs at the voxelHDDA::updatejust snapped to, so on loop exit the result always describes the current voxel. The force-unrolled ≤3-pass structure is unchanged.HDDALeafVoxelIterator(LeafOnly=true) gates ondim == 1before consultingactive, which is exactly theActiveOnLeafOnlycontract, so it keepsgetDim's skip-flag fast path.HDDAActiveValueIterator(LeafOnly=false) must yield active coarse tiles, so it usesActiveExact.No callers change —
ray_implicit_intersection,uniform_ray_samples,voxels_along_rays, andsegments_along_raysconsume the iterators through their public API.Benchmarks
RTX PRO 6000 Blackwell, 512×512 pinhole-camera rays, fp32, median of 20 CUDA-event-timed iterations (top/bottom-2 trimmed), NanoVDB example grids. Output checksums are bit-identical to main across all workloads — this is a pure performance change.
voxels_along_raysvoxels_along_raysvoxels_along_raysvoxels_along_rayssegments_along_rayssegments_along_rayssegments_along_rayssegments_along_raysuniform_ray_samplesuniform_ray_samplesray_implicit_intersectionray_implicit_intersectionray_implicit_intersectionray_implicit_intersectiongains the least because its leaf-only iterator already skips coarse tiles cheaply (and now additionally benefits fromActiveOnLeafOnlykeeping the skip-flag fast path).Test plan
pytest tests/unit/test_ray_marching.py tests/unit/test_sample.py— 455 passed, 4 skipped.pytest tests/unit/test_basic_ops.py -k "ray or implicit or voxels_along or segments"— 56 passed, 1 skipped, including the ray_implicit_intersection returns a spurious near-origin crossing when the ray starts far from the SDF band #692 zero-prefix regression test.🤖 Generated with Claude Code