Skip to content

Add TSDF / ESDF / Occupancy / Decay APIs and a fast marching-cubes variant (on upstream memory-resource seams) - #733

Draft
swahtz wants to merge 9 commits into
openvdb:mainfrom
swahtz:feat/tsdf-esdf-torch-resource
Draft

Add TSDF / ESDF / Occupancy / Decay APIs and a fast marching-cubes variant (on upstream memory-resource seams)#733
swahtz wants to merge 9 commits into
openvdb:mainfrom
swahtz:feat/tsdf-esdf-torch-resource

Conversation

@swahtz

@swahtz swahtz commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Native CUDA kernels and Python wrappers for TSDF and ESDF reconstruction, occupancy mapping, dynamic-scene decay, and a fast sparse-compact marching-cubes variant, sharing a common PersistentTSDFState + BuildPointTruncationShell substrate.

This supersedes #656, rebased onto the upstream-seams allocator work instead of the forked-header approach: it is stacked on #732 (feat/nanovdb-torch-resource), so the diff shown here includes #732's commits until that PR merges — review only the last three commits:

  1. TSDF + ESDF + Occupancy + Decay + fast marching cubesFork a small set of nanoVDB headers to share PyTorch's CUDA allocator #655/Add TSDF / ESDF / Occupancy / Decay APIs and a fast marching-cubes variant #656's original feature commit, cherry-picked with authorship preserved. Content is unchanged from Add TSDF / ESDF / Occupancy / Decay APIs and a fast marching-cubes variant #656 apart from one merge-conflict resolution in tests/unit/test_basic_ops.py (main's new ray-intersection tests vs. this PR's fp16-expanded test_marching_cubes decorator; both kept). The multi-frame integrator paths rely on the TorchResource allocator routing from Route NanoVDB builder scratch through PyTorch's active CUDA allocator via upstream memory-resource seams #732 rather than the deleted nanovdb_overrides/ fork.
  2. Format fix for the new TSDF / ESDF sources — pure clang-format pass over the three sources touched by the adaptation commit, kept separate so the adaptation diff below stays readable.
  3. Adapt TSDF / ESDF stack to current nanovdb and fvdb APIs — the rebase adaptations (46+/35− lines), detailed below.

Draft until the upstream NanoVDB memory-resource stack (AcademySoftwareFoundation/openvdb#2268 / #2269 / #2270 / #2272 / #2273, tracking issue #2232) merges and #732's temporary nanovdb pin is repointed at AcademySoftwareFoundation/openvdb.

For the full feature inventory (topology/state primitives, depth + LiDAR TSDF integrators, occupancy mapping, ESDF, marching cubes, Python surface) see #656's description — the feature set is unchanged.

Rebase notes (May-era main → current main + memory-resource nanovdb)

Three mechanical adaptations to API drift, no behavioral change (commit 3 above):

  • fvdb::HDDAVoxelIteratorfvdb::HDDALeafVoxelIterator in the two point-cloud integrators — the renamed alias whose contract matches their per-leaf-voxel sidecar indexing (getValue(ijk) - 1).
  • buildVoxelBlockManager raw-pointer overload → direct BuildVoxelBlockManagerFunctor launch in ComputeESDF.cu. The overload was removed upstream in favor of a VoxelBlockManagerHandle API; the handle can't own TorchDeviceBuffers yet because its accessors static_cast from deviceData() (upstream issue NanoVDB: VoxelBlockManagerHandle accessors don't compile for buffer types whose data()/deviceData() return typed pointers AcademySoftwareFoundation/openvdb#2274). Keeping the firstLeafID / jumpMap arrays in torch tensors and launching the public functor directly performs the identical launch while staying in torch's pool; once #2274 lands this can move to the handle.
  • VoxelBlockManager<BlockWidth>VoxelBlockManager<Log2BlockWidth> in the ESDF sweep kernel — the template parameter changed meaning upstream.

Test plan

On a clean ./build.sh install against the stacked branch (full CUDA build, -Werror):

  • tests/unit/test_persistent_tsdf_state.py — 7 tests
  • tests/unit/test_compute_esdf.py — 17 tests (exercise the rewritten VBM build path)
  • tests/unit/test_dirty_mask.py — 9 tests
  • tests/unit/test_integrate_occupancy.py — 7 tests
  • tests/unit/test_decay_and_prune.py — 9 tests
  • tests/unit/test_basic_ops.py — 286 tests including the new TSDF / marching-cubes fp16 / occupancy additions alongside main's current suite

335 passed, 1 skipped locally on the fresh install.

Followups

🤖 Generated with Claude Code

swahtz and others added 6 commits August 7, 2026 17:03
Points the nanovdb pin at the merge of AcademySoftwareFoundation/openvdb
PRs #2268, #2269, #2270, #2272 and #2273 (tracking issue #2232), which
give every CUDA builder fvdb uses a ResourceT injection seam. Temporary
until the stack merges upstream; the pin is a fast-forward of the
previous one (f9754140 is an ancestor of the stack's base).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Adds fvdb::TorchResource, a stream-ordered nanovdb memory resource
backed by c10::cuda::CUDACachingAllocator (raw_alloc_with_stream /
raw_delete), and passes it as the ResourceT template parameter at every
builder call site: voxelsToGrid and the DilateGrid / MergeGrids /
PruneGrid / RefineGrid / CoarsenGrid ops. Builder scratch (sort keys,
CUB temp storage, topology mask buffers) now lives in the same pool as
fvdb / PyTorch tensors instead of a second cudaMallocAsync pool that
fragments VRAM against it.

This supersedes the forked-header approach of openvdb#655: same allocator
routing, but through upstream nanovdb's injection seams instead of
shadowed copies of DeviceBuffer.h / DeviceResource.h. The
FVDB_NANOVDB_TRACE_ALLOCS env var from that PR is preserved inside
TorchResource (=1 traces allocs >= 256 KiB, =2 traces all).

Not routed (no upstream seam yet, all off the hot paths):
DistributedPointsToGrid multi-GPU scratch, indexToGrid scratch in
SaveNanoVDB, and the builders' small dual-space mProcessedRoot / mData
buffers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Add native CUDA kernels and Python wrappers for TSDF and ESDF
reconstruction, occupancy mapping, dynamic-scene decay, and a fast
sparse-compact marching-cubes variant. These features sit on top of
the nanoVDB allocator-overrides change (parent PR) and share a
common `PersistentTSDFState` + `BuildPointTruncationShell` substrate.

Topology + state primitives

  src/fvdb/detail/ops/BuildPointTruncationShell.{cu,h}
      Shared primitive that turns `(points, base_grid, truncation_margin)`
      into the set of voxels within the truncation shell. Used by both
      depth and LiDAR TSDF integrators.

  src/fvdb/detail/ops/PersistentTSDFState.{cu,h}
      Grow-on-touch state holder for incremental integration: wraps a
      monotonically-growing live grid with fixed-shape tsdf / weights /
      optional feature sidecars and exposes a `grow` method that
      expands the grid + sidecars atomically while preserving values
      at already-live voxels.

  src/python/PersistentTSDFStateBinding.cpp
      Pybind11 binding for the above.

Integrators

  src/fvdb/detail/ops/IntegrateTSDF.{cu,h}  (modified)
      Depth TSDF integrator now uses `BuildPointTruncationShell` and
      `PersistentTSDFState`, and exposes a new N-frame batched entry
      point `integrateTSDFBatch` that grows the union grid one frame
      at a time and copy-forwards sidecars through the
      persistent-state object. Bit-identical to the per-frame loop
      (pinned by `test_integrate_tsdf_frames_matches_sequential`).

  src/fvdb/detail/ops/IntegrateTSDFFromPoints.{cu,h}
      Native LiDAR / range-sensor TSDF integrator: per-point thread
      HDDA-walks the union grid and `atomicAdd`s a running-sum into
      (sum_w_sdf, sum_w, sum_w_feat) accumulators within the
      truncation (and optionally free-space) band. Single-frame,
      with-features, and N-frames-batched variants.

  src/fvdb/detail/ops/IntegrateOccupancyFromPoints.{cu,h}
      LiDAR occupancy mapping with free-space carving and log-odds
      updates. Single-frame and N-frames-batched variants. Same
      ray-walk structure as the LiDAR TSDF integrator.

ESDF

  src/fvdb/detail/ops/ComputeESDF.{cu,h}
      Euclidean Signed Distance Field from an integrated narrow-band
      TSDF. Composition pattern is
      `dilateGrid -> esdfSeed -> N sweeps of 26-N min-propagation`,
      reusing the topology-op primitives.

  src/fvdb/detail/ops/DirtyMaskFromSidecars.{cu,h}
      Per-voxel dirty-mask primitive that lets the incremental ESDF
      variant scope work to just the voxels whose sidecars changed.

Marching cubes

  src/fvdb/detail/ops/MarchingCubesFast.{cu,h}
      Sparse-compact, packed-key marching cubes for fp32 / fp16 CUDA.
      `marchingCubes` now dispatches to this for eligible inputs and
      to `marchingCubesLegacy` (the previous default, kept verbatim)
      otherwise.

  src/fvdb/detail/ops/MarchingCubes.{cu,h}  (modified)
      Routes through to the new fast path.

Python surface

  fvdb/functional/_meshing.py
      Wrappers for the new N-frame + with-features + LiDAR variants
      of TSDF integration, occupancy mapping (single + frames), and
      ESDF (single + incremental).

  fvdb/functional/_topology.py
      Wrapper for `dirty_mask_from_sidecars_single`.

  fvdb/grid.py
      New methods on `Grid`: `decay_and_prune`,
      `integrate_tsdf_frames`, `integrate_tsdf_with_features`,
      `integrate_tsdf_from_points` (+ frames + with-features
      variants), `integrate_occupancy_from_points` (+ frames),
      `compute_esdf`, `compute_esdf_incremental`. `decay_and_prune`
      is implemented entirely in Python on top of existing fvdb
      sidecar + topology primitives.

  fvdb/functional/__init__.py
      Export the new functional names.

  src/python/Bindings.cpp, src/python/GridBatchOps.cpp
      Register the new C++ bindings.

Tests

  tests/unit/test_persistent_tsdf_state.py
  tests/unit/test_compute_esdf.py
  tests/unit/test_dirty_mask.py
  tests/unit/test_integrate_occupancy.py
  tests/unit/test_decay_and_prune.py
  tests/unit/test_basic_ops.py  (extended)

      Cover the new primitives, the persistent-state invariants
      (`grow` semantics, sidecar carry-forward), bit-identity of the
      batched-vs-sequential TSDF paths, atomic-noise tolerance for
      the LiDAR/occupancy variants, and fp16-vs-fp32 numerical
      agreement for the new marching-cubes fast path.

Signed-off-by: Francis Williams <francis@fwilliams.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Pure clang-format pass over ComputeESDF.cu, IntegrateTSDFFromPoints.cu
and IntegrateOccupancyFromPoints.cu; no code change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Three mechanical adaptations to API drift since the feature commit was
written (May 2026), no behavioral change:

- fvdb::HDDAVoxelIterator was renamed; the two point-cloud integrators
  now use fvdb::HDDALeafVoxelIterator, the alias whose contract matches
  their per-leaf-voxel sidecar indexing (getValue(ijk) - 1).

- nanovdb's raw-pointer buildVoxelBlockManager overload was removed in
  favor of a VoxelBlockManagerHandle API. ComputeESDF keeps its
  firstLeafID / jumpMap arrays in torch tensors (same pool as all other
  fvdb allocations) and launches the public BuildVoxelBlockManagerFunctor
  directly -- the identical launch the handle-based builder performs.
  The handle itself can't own TorchDeviceBuffers until
  AcademySoftwareFoundation/openvdb#2274 is fixed: its accessors
  static_cast from deviceData(), which TorchDeviceBuffer types as
  uint8_t*.

- nanovdb::tools::cuda::VoxelBlockManager is now templated on
  Log2BlockWidth rather than BlockWidth; the ESDF sweep kernel passes
  ESDF_BLOCK_WIDTH_LOG2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
@swahtz
swahtz force-pushed the feat/tsdf-esdf-torch-resource branch from fb34c26 to c4767fd Compare August 7, 2026 05:43
swahtz added 2 commits August 7, 2026 17:58
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
@swahtz swahtz self-assigned this Aug 7, 2026
Resolve sliced grid views by logical byte offset, avoid allocating full single-list ESDF prune indices, and add CPU/CUDA regression coverage.

Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
@swahtz swahtz added this to the v0.7 milestone Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants