Compare commits

...
Author SHA1 Message Date
Dan Bailey 02b186de3c Test only one Windows job
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-31 10:35:28 -07:00
Dan Bailey 8b3ef6ae9e Remove numpy installation from Windows CI
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-31 10:21:52 -07:00
Nick Avramoussis 22bb394214 Merge pull request #2125 from AcademySoftwareFoundation/feature/asan_ubsan_image
Update asan/ubsan to latest 2026 image
2025-10-31 08:09:02 +13:00
Dan Bailey b777c47370 Update asan/ubsan to latest 2026 image
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-30 09:58:36 -07:00
apradhana 1b9ca6408f Merge pull request #2124 from apradhana/bugfix/test_levelset_ray_intersector
Bug fix: Use unit vector for Ray in TestLevelSetRayIntersector.
2025-10-30 08:07:47 -07:00
apradhana df578c6bbc Use unit vector for Ray in TestLevelSetRayIntersector: testMissedIntersections.
Signed-off-by: apradhana <andre.pradhana@gmail.com>
2025-10-29 23:13:15 -07:00
apradhana c69404e432 Minimum Viable Product for Half Grid Support (#2106)
Make HalfGrid a first-class-citizen in OpenVDB.

Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com>
Signed-off-by: apradhana <andre.pradhana@gmail.com>
2025-10-29 21:44:46 -07:00
Dan Bailey cf5ddffbfb Merge pull request #2123 from danrbailey/fix_value_mask_test
Fix assertion in internal node unit test
2025-10-29 17:49:10 -07:00
Dan Bailey 2086e3c932 Merge pull request #2119 from danrbailey/outofcore
Refactor out-of-core logic into a public method on LeafBuffer
2025-10-29 17:37:41 -07:00
Dan Bailey c78b9efb87 Merge remote-tracking branch 'upstream/master' into outofcore 2025-10-29 15:14:32 -07:00
Dan Bailey 9aa8050c3c Merge remote-tracking branch 'upstream/master' into value_mask_unsafe 2025-10-29 15:09:17 -07:00
Dan Bailey 2072e1d82c Merge pull request #2122 from Idclip/weeklyci
Weekly CI Fixes
2025-10-29 12:19:30 -07:00
Dan Bailey 75c1985ec9 Merge pull request #2121 from danrbailey/texlive_direct
Use direct download instead of yum repos to install texlive
2025-10-29 12:16:15 -07:00
Nick Avramoussis 8b64f41042 Weekly CI fixes
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-29 22:50:21 +13:00
Dan Bailey b44b2b31de Fix assertion in internal node unit test
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-28 23:53:13 -07:00
Dan Bailey 57434bc985 Merge pull request #2118 from danrbailey/value_mask_unsafe
Add InternalNode::setValueMaskUnsafe()
2025-10-25 11:04:36 -07:00
Dan Bailey 84eea021f6 Use direct download instead of yum to install texlive
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-23 16:04:59 -07:00
Nick Avramoussis fae3ee95d1 Merge pull request #2115 from Idclip/vdb_13_deps
Dependency bumps for VDB 13
2025-10-23 21:08:56 +13:00
Nick Avramoussis a0e513862a PR feedback - simplified a number of CI builds and fixes issues with the 2026 containers, added pendingchanges for version bumps
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-23 19:58:04 +13:00
Ken MusethandAndrew Reidmeyer a273ab358f Improvements to NanoVDB (#2108)
* major improvements to NanoToOpenVDB

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* fixed CI issues

Signed-off-by: Ken <ken.museth@gmail.com>

* fixed issues in GridBlindMetaData::isValid

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* added improvements to nanovdb_print

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* improved change notes

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* fixed build issues

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* fixed compiler warning on Windows

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* fixed compiler warning on Windows

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* fixed compiler warning on Windows

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* fixed compiler warning on Windows

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* changes to examples

Signed-off-by: Ken <ken.museth@gmail.com>

* improved API in NanoToOpenVDB

Signed-off-by: Ken <ken.museth@gmail.com>

* trying a fix for Windows

Signed-off-by: Ken <ken.museth@gmail.com>

* replaced std::emable_if with util::enable_if

Signed-off-by: Ken <ken.museth@gmail.com>

* snapshot

Signed-off-by: Ken <ken.museth@gmail.com>

* added nanovdb::util::enable_if_t and simplified code

Signed-off-by: Ken <ken.museth@gmail.com>

* cleanup

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* major cleanup

Signed-off-by: Ken <ken.museth@gmail.com>

* removed whitespace

Signed-off-by: Ken <ken.museth@gmail.com>

* minor improvements and new patch version number

Signed-off-by: Ken <ken.museth@gmail.com>

* removed nanovdb::ValueIndexMask and nanovdb::ValueOnIndexMask

Signed-off-by: Ken <ken.museth@gmail.com>

* removed ValueIndexMask in python module

Signed-off-by: Ken <ken.museth@gmail.com>

* addressed review comments

Signed-off-by: Ken <ken.museth@gmail.com>

* PNanoVDB remove indexmask grid types.

Signed-off-by: Andrew Reidmeyer <areidmeyer@nvidia.com>

* fixed spelling

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* updated change notes

Signed-off-by: Ken <ken.museth@gmail.com>

* improved documentation

Signed-off-by: Ken <ken.museth@gmail.com>

* removed whitespace

Signed-off-by: Ken <ken.museth@gmail.com>

* improved pending changes

Signed-off-by: Ken <ken.museth@gmail.com>

* addressed review comment

Signed-off-by: Ken <ken.museth@gmail.com>

---------

Signed-off-by: Ken Museth <ken.museth@gmail.com>
Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Andrew Reidmeyer <areidmeyer@nvidia.com>
Co-authored-by: Andrew Reidmeyer <areidmeyer@nvidia.com>
2025-10-22 19:54:34 -07:00
Dan Bailey 8e901cc609 Merge remote-tracking branch 'upstream/master' into value_mask_unsafe 2025-10-22 14:39:50 -07:00
Dan Bailey 8e5972026d Merge pull request #2087 from danrbailey/remove_old_io_modes
Remove Old IO Modes
2025-10-22 14:38:41 -07:00
Dan Bailey 5d9e78992f Refactor out-of-core logic into a public method on LeafBuffer
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-22 14:37:05 -07:00
Dan Bailey ddb4629dec Merge pull request #2114 from danrbailey/texlive_dnf
Fix GHA CI install for Latex
2025-10-22 13:25:36 -07:00
jmlait e5896971a9 Merge pull request #2057 from AcademySoftwareFoundation/jmertic-patch-1
Point to correct Technical Charter
2025-10-22 16:15:31 -04:00
Dan Bailey fc745334da Add InternalNode::setValueMaskUnsafe()
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-22 12:29:31 -07:00
Dan Bailey c98f29afcd Merge pull request #2103 from danrbailey/const_mask_access
Add const mask access to bool LeafBuffer
2025-10-22 11:52:02 -07:00
Dan Bailey a2c60a3a46 Merge pull request #2102 from danrbailey/deprecate_vdb_13
Remove lots of deprecated functionality
2025-10-22 11:51:02 -07:00
Simon A 3cc39c7d25 Fix missing nanovdb install files (#2117) 2025-10-22 05:52:44 -07:00
Nick Avramoussis 92ec8d2c46 GHA runner image updates for VFX26
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-22 22:43:32 +13:00
Nick Avramoussis c3f0336f1a Dependency bumps for VDB 13
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-22 22:32:50 +13:00
Dan Bailey 4fd1a8cd6c Merge remote-tracking branch 'upstream/master' into remove_old_io_modes 2025-10-21 23:41:49 -07:00
Dan Bailey 40e4129229 Switch from yum to dnf and epel release install
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 23:38:50 -07:00
Dan Bailey f2df83c304 More Interrupter cleanup
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 22:30:00 -07:00
Dan Bailey bd7a2416d4 Merge remote-tracking branch 'upstream/master' into deprecate_vdb_13 2025-10-21 21:26:53 -07:00
Dan Bailey e269c9a6e0 Merge pull request #2113 from AcademySoftwareFoundation/feature/yum
Fix GHAs failures - remove usage of yum
2025-10-21 20:57:10 -07:00
Dan Bailey 93d0b3cb33 Merge pull request #2112 from danrbailey/fix_spelling_convex
Fix minor spelling issues in Convex tools
2025-10-21 20:13:23 -07:00
Nick Avramoussis 185ecc48ba Merge pull request #2105 from Idclip/nanobind_fixes
Removed unnecessary USE_NUMPY build option
2025-10-22 15:51:55 +13:00
Nick Avramoussis e3beb85592 Merge pull request #2109 from Idclip/anisotropy_as_quaternions
Feature improvements to points::pca and points::rasterizeSdf
2025-10-22 15:51:34 +13:00
Dan Bailey 4027e3d66e Enforce CMake 3.5 minimum for doxygen
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 18:01:33 -07:00
Dan Bailey 8897a22033 Upgrade docs to 2026 images
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 16:53:44 -07:00
Dan Bailey 893a03bed8 Remove most use of yum in GHAs
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 16:53:33 -07:00
Dan Bailey 09b8ab83a0 Remove yum-utils installation
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 16:48:54 -07:00
Dan Bailey 91af0f5293 Fix minor spelling issues in Convex tools
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 12:39:13 -07:00
Nick Avramoussis 61a30b3e98 Renamed pca setting STRETCH_AND_ROTATION to STRETCH_AND_UNITARY
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-22 08:38:41 +13:00
Nick Avramoussis 70b1593a18 Added a UnsafeConstruct constructor to Quat to avoid checking for unitary properties when it's already known
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-22 08:38:41 +13:00
Nick Avramoussis bd16782e77 Improved comments and variable naming in pca methods
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-22 08:38:39 +13:00
Nick Avramoussis 7f5538fe2c Simplified reflection->rotation logic in pca
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-22 08:38:20 +13:00
Nick Avramoussis 352e672e5b Ignore deprecation warnings in user code on simple construction of PCA/raster setting
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-22 08:38:19 +13:00
Dan Bailey d3aaf4fe64 Add change notes
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 12:36:31 -07:00
Dan Bailey ae64b3c50c Merge remote-tracking branch 'upstream/master' into deprecate_vdb_13 2025-10-21 12:36:06 -07:00
Dan Bailey 2dbc79024c Add change notes
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 12:35:38 -07:00
Dan Bailey 4bdee3bd47 Add ability to retrieve const access to raw data buffer in LeafBuffer<bool>
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 12:06:13 -07:00
Dan Bailey 55258d0da9 Add more detail to the error message on how to read older VDB files.
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-21 11:54:54 -07:00
Dan Bailey e8f1f84f37 Merge remote-tracking branch 'upstream/master' into remove_old_io_modes 2025-10-21 11:49:00 -07:00
Matthew CongandEftychios Sifakis 4e02cba85a Adding coarsen/refine operators to NanoVDB (#2089)
* Adding coarsen/refine operators to NanoVDB

* namespace adjustments, stricter const-correctness

* adding grid coarsening/refinement operators in NanoVDB

* adding unittest for refine/coarsen ops

* ws fix

* better forwarding to emplace_back

* non-tbb version (refine), TBB guards, cleanup

* ws fix

* stricter const-ness for DeviceGridTraits methods

* moving synchronization-sensitive initialization to process methods

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* making helpers private; inlining/cleanup

Signed-off-by: Efty Sifakis <esifakis@nvidia.com>

---------

Signed-off-by: Matthew Cong <mcong@nvidia.com>
Signed-off-by: Efty Sifakis <esifakis@nvidia.com>
Co-authored-by: Eftychios Sifakis <esifakis@nvidia.com>
2025-10-21 11:00:51 -07:00
Dan Bailey ddaf9f42e3 Merge pull request #2110 from danrbailey/notes_081025
Meeting notes 8th October
2025-10-20 16:03:42 -07:00
Nick Avramoussis dd34dc583b Added pendingchanges
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-20 09:07:40 +13:00
Jonathan Swartz dc69e26dd5 Switch NanoVDB tests to using macos 14 runners (#2111) 2025-10-16 10:37:16 -07:00
Nick Avramoussis 592df88c88 PR feedback - always import numpy for python unit tests
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-16 20:47:35 +13:00
Nick Avramoussis 3780d983a1 Minor changes to silence precision warnings
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-15 21:13:20 +13:00
Nick Avramoussis 114db385da Feature improvements to points::pca and points::rasterizeSdf
- Added customization of the output transformation attribute for points::pca. Can be output as a quaternion or as a combined affine transform
 - Added support for quaternions attributes and affine transformations (that include scale) in PointRasterizeSDF for ellipsoidal rasterizations.

Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-15 21:13:20 +13:00
Nick Avramoussis 32dfc0e792 Removed unecessary build time USE_NUMPY option from the python module - nanobind always builds in ndarray support. Updated docs and dependency versions.
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-15 21:12:18 +13:00
Dan Bailey f564d35dca Merge pull request #2086 from danrbailey/no_leaf_string
Remove LeafNode<std::string> unit test
2025-10-14 23:40:46 -07:00
Dan Bailey 30a08a27c8 Merge remote-tracking branch 'upstream/master' into deprecate_vdb_13 2025-10-14 23:25:33 -07:00
Nick Avramoussis 6f9ca0c05a Merge pull request #2104 from Idclip/axmacos
Move to Apple silicon CI runners for AX
2025-10-15 19:21:46 +13:00
Dan Bailey 747d029abc Meeting notes
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-14 22:41:52 -07:00
Nick Avramoussis 71a5f289cb Fixes to support LLVM 21
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-10 22:09:02 +13:00
Nick Avramoussis 1c5d8d1f57 Remove deprecated macos-13 CI, moved to macos-14/15
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-09 21:49:40 +13:00
Nick Avramoussis 0952c900f9 Introduce some small acceptable tolerances for AX function tests on macos due to codegen diffs
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-10-09 19:35:17 +13:00
Dan Bailey 02478bd95f Remove lots of deprecated functionality
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-08 10:36:04 -07:00
Nick Avramoussis dd72d1f137 Merge pull request #2099 from Idclip/version_bump
Bump development to v12.1.2
2025-10-08 08:30:04 +13:00
Dan Bailey 04f4dfba83 Add io::checkFormatVersion() method to consolidate file version compatibility checks
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-06 21:13:26 -07:00
Dan Bailey eab9aaaa7a Update all IO version checks to use 222
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-10-06 15:55:35 -07:00
Dan Bailey e419f1c745 Merge pull request #2097 from danrbailey/write_compression_const
Improve const-correctness in io::writeCompressedValues
2025-10-06 14:05:27 -07:00
Matthew Cong 3b64696777 Fix CUDA 13 deprecation warning (#2100)
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-10-01 13:25:32 -07:00
Nick Avramoussis cf3c1923a5 Bump development to v12.1.2
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-30 21:30:37 +13:00
Nick Avramoussis 86aa980c9c Merge pull request #2098 from AcademySoftwareFoundation/v12.1.1_rc
v12.1.1
2025-09-30 21:28:19 +13:00
Dan Bailey d3c8c640ce Merge pull request #2096 from danrbailey/levelsetrayintersectorbug
Fix LevelSetRayIntersector Time Methods
2025-09-29 23:22:51 -07:00
Nick Avramoussis e3a9b1c1d9 Prepare v12.1.1 release
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-29 18:49:13 +13:00
Dan Bailey e1944e98e5 Merge remote-tracking branch 'upstream/master' into remove_old_io_modes 2025-09-26 15:45:44 -07:00
Dan Bailey 176a3f3725 Merge remote-tracking branch 'upstream/master' into no_leaf_string 2025-09-26 15:45:26 -07:00
Dan Bailey cb2ebfc840 Merge remote-tracking branch 'upstream/master' into levelsetrayintersectorbug 2025-09-26 15:44:41 -07:00
Dan Bailey 7c108a5d9c Add changes
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-26 15:42:30 -07:00
Dan Bailey 796a07f3b8 Improve const-correctness in io::writeCompressedValues()
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-26 15:38:46 -07:00
Dan Bailey 345b74ef71 Merge pull request #2088 from danrbailey/h21_ci
Fix Houdini CI
2025-09-26 15:36:10 -07:00
Dan Bailey 1c5a3bf382 Only evaluate unit tests for release builds (debug builds are too slow)
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-26 10:02:23 -07:00
Dan Bailey d09c9a4f67 Add build suffix to the job name
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-26 10:02:03 -07:00
Nick Avramoussis c7f7d6e737 Re-introduce AX tests in the Houdini builds
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-26 09:54:59 -07:00
Nick Avramoussis f1c46db025 Prefer the static build of OpenVDB AX when building the AX unittests
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-26 09:54:57 -07:00
Nick Avramoussis 1f713afeee Move, don't copy libs when downloading Houdini (otherwise disk space limits can be reached)
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-26 09:54:55 -07:00
Dan Bailey aa8dfc4f2a Add changes
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-25 22:26:58 -07:00
Dan Bailey 037aa886e5 Fix level set ray intersector bugs
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-25 22:16:08 -07:00
Dan Bailey ff78199ba9 Disable axtest for H21 GCC build
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-24 20:14:46 -07:00
Dan Bailey 29e764bac5 Fix Houdini build using Debug mode
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-24 16:43:07 -07:00
Jonathan Swartz 5f0432b338 Merge pull request #2079 from matthewdcong/aswf_master_merge
This adds the latest round of improvements to NanoVDB from the fVDB team, including:

Improvements and bugfixes to PNanoVDB
Add Mat classes and other math types to NanoVDB
Add support for morphology operators in NanoVDB
Improvements to mGPU grid building and introduction of a separate test executable for mGPU tests
Several fixes for compiler warnings
2025-09-24 19:39:32 -04:00
Dan Bailey b96fcf77a6 Reduce threads from 8 -> 6 for GCC
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-24 16:27:05 -07:00
Dan Bailey 0cbfa274b2 Move H21.0 to VFX 2026 images (as a result of TBB 2022)
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-24 16:26:56 -07:00
Dan Bailey edde9a605c Delete LegacyFrustum
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-24 15:07:32 -07:00
Matthew Cong 4675f5b4bc Merge remote-tracking branch 'upstream-aswf/master' into aswf_master_merge 2025-09-24 13:10:27 -07:00
Nick Avramoussis 3d45de107e Merge pull request #2085 from Idclip/hou_21_sop_activate
Fixed a compilation issue with the OpenVDB Activate SOP with Houdini 21
2025-09-24 14:25:17 +12:00
Dan Bailey ddc3d7a754 Delete RootNodeMask
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-23 18:32:38 -07:00
Dan Bailey a25d824db4 Remove all the old IO code paths earlier than 222 (OpenVDB 1.0)
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-23 18:30:35 -07:00
Dan Bailey 5c1097e362 Remove LeafNode<std::string> unit test
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-23 18:06:59 -07:00
Nick Avramoussis a02ae47d06 Fixed a compilation issue with the OpenVDB Activate SOP with Houdini 21
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-22 09:24:54 +12:00
Dan Bailey af240d55d4 Merge pull request #2083 from danrbailey/notes_10_09_17
Meeting Notes Sep 10
2025-09-18 18:36:27 -07:00
Nick Avramoussis c3219ccfd6 Merge pull request #2084 from Idclip/houdini_21
Fixed Houdini download scripts with new container images
2025-09-19 11:17:45 +12:00
Nick Avramoussis a948ff318e Fixed Houdini download scripts with new container images
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-19 10:46:18 +12:00
Nick Avramoussis 43329bd962 Merge pull request #2081 from Idclip/houdini_21
Bump Houdini CI builds to 20.5/21
2025-09-19 10:00:39 +12:00
Nick Avramoussis f91141ba9d Fixed a AX unit test failure when LLVM is built with LLVM_ENABLE_ABI_BREAKING_CHECKS
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-18 22:08:25 +12:00
Dan Bailey 559d7642c8 Add new meeting notes
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-09-17 16:52:33 -07:00
Petra Hapalova fc3f1d180f Fix warning in NanoVDB header
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-17 12:58:19 -07:00
Eftychios Sifakis b3aead3529 Adding unittests for topological operations
* fixing discrepancies between OpenToNano/PointsToGrid constructions

* dilate/inject/prune unittest

* ws fix

* adding MergeGrids unittest

* key test

---------

Co-authored-by: Efty Sifakis <esifakis@nvidia.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-15 14:30:19 -07:00
Nick Avramoussis 08931d62b4 Compilation fixes for the OpenVDB AX Houdini SOP when using LLVM >= 16
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-15 09:17:11 +12:00
Nick Avramoussis 4fb969759e Bump Houdini CI builds to 20.5/21
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-11 20:06:03 +12:00
Eftychios Sifakis 6f3f350daf fixing discrepancies between OpenToNano/PointsToGrid constructions
This PR fixes a few discrepancies between grid construction via (a) PointsToGrid, and (b) OpenToNanoVDB. The discrepancies are not typically inhibiting functionality of either construction, but result in grids that are not bit-identical, when they would otherwise be expected to be. The changes are:

Removal of the PointsToGridData::flags member. The actual functionality of this flag was to allow control of solely the leaf-level flags (interior node, and GridData flags were already set independently), and in that aspect it was even incorrectly setting the IsBreadthFirst bit (which only makes sense for GridData::mFlags, not the leaf node flags). The method includeBBox was having no effect as it was, it was never called in any of the unittests, and its effect was overwritten later in the code.

PointsToGrid has been corrected to flag HasBBox as active in every node (not just the leaf nodes, as was happening prior; the flags were previously being set to zero).

The world space bounding box in OpenToNanoVDB was incrementing the upper bound of the index-space bounding box, prior to applying the transform ( hence, a grid with a single voxel [0,0,0] would have a WS bounding box of [0,0,0]->[1,1,1]). PointsToGrid was not applying this increment, resulting in a smaller WS bounding box.

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:10:28 -07:00
Eftychios Sifakis 178b816479 restricting scope of CALL_CUBS macro
The CALL_CUBS macro has a slightly different definition in the two similar code files that use it, namely PointsToGrid.cuh and TopologyBuilder.cuh. This fix restricts the scope of each macro definition to just the code file where it is used.

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:10:28 -07:00
Andrew 37c159260d PNanoVDB. Move StructuredBuffer<uint64_t> to StructuredBuffer<uint2>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:10:28 -07:00
Matthew Cong 5dddfa1fc0 Fix outdated MPL licensing
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:10:28 -07:00
Jonathan Swartz 759f8e59ee NanoVDB Math Mat classes' member function fix
---------

Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
2025-09-10 16:10:25 -07:00
Matthew Cong 5bf378f89f Modularize distributed radix sort code
Refactor the distributed radix sort code into a separate function so that it is more modular, testable, reusable. Furthermore, it fixes a small rounding error edge case in the merge code.

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:10:25 -07:00
Jonathan Swartz 372526723c Cumulative NanoVDB and PNanoVDB updates from feature/fvdb
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
2025-09-10 16:10:21 -07:00
Matthew Cong 2e576dbca8 Fix compiler warnings for NanoVDB
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:09:22 -07:00
Matthew Cong 4f6d0803b7 Suppress warning for GCC 13
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:09:22 -07:00
Matthew Cong 4e2546fe0c Spawn kernels from main thread in distributed grid building
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:09:22 -07:00
Matthew Cong 3b13980be2 Decouple prefix sums for distributed grid building
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:09:22 -07:00
Matthew Cong 90e84aa320 Swap inline to static for header file kernels
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:09:22 -07:00
Matthew Cong 39b16cdd6c Remove anonymous namespace and initialize device to invalid value
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:09:22 -07:00
Matthew Cong ac2e876b71 Fix prefix sum buffer overrun in DistributedPointsToGrid
* Fix prefix sum buffer overruns

* Add test

* Revise comment

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-09-10 16:09:22 -07:00
Nick Avramoussis 29dd88e5d9 Merge pull request #2077 from Idclip/ci_fixes
CI Fixes
2025-09-11 08:18:52 +12:00
Nick Avramoussis ef3f85da9a CI Fixes
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-09-02 22:35:56 +12:00
Nick Avramoussis cbe458cd8a Merge pull request #2072 from wetadigital/weta/public/ax_llvm_target_machine
Fixed missing host Target Machine when using LLVM 16+
2025-08-29 22:07:42 +12:00
Nick Avramoussis 166946c036 Merge pull request #2073 from wetadigital/weta/public/start_12.1.1
Bump development to 12.1.1
2025-08-22 04:11:52 +12:00
Nicholas Avramoussis f6b627cb09 Bump development to 12.1.1
Signed-off-by: Nicholas Avramoussis <navramoussis@wetafx.co.nz>
2025-08-13 13:57:08 +12:00
Nicholas Avramoussis 46371bbde9 Fixed missing host Target Machine when building and using the new LLVM PassManager with LLVM 16+
Signed-off-by: Nicholas Avramoussis <navramoussis@wetafx.co.nz>
2025-08-13 13:20:53 +12:00
Nick Avramoussis dd0ac130d0 Merge pull request #2071 from AcademySoftwareFoundation/v12.1.0_rc
V12.1.0
2025-08-08 21:59:46 +12:00
Nick Avramoussis e4621ae7b3 Bumped OpenVDB version to 12.1.0, updated CHANGES
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-08 17:24:31 +12:00
Nick Avramoussis 1fceec9c6b Minor build fix for C++20/GCC13 and weekly CI fixes
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-08 10:19:53 +12:00
Nick Avramoussis 9acc3af646 Merge pull request #2068 from Idclip/ax_gtest
Replace CppUnit with GoogleTest in AX tests
2025-08-08 08:12:52 +12:00
Nick Avramoussis 313af9a72a Merge pull request #2069 from ghurstunither/bugfix/tube_assert_failures
Update LevelSetTubesImpl.h
2025-08-07 14:13:25 +12:00
Nick Avramoussis 93b397cf81 Merge pull request #2067 from Idclip/anisotropic_surfacing
Introduced Anisotropic Surfacing Kernels and PCA methods
2025-08-07 12:51:58 +12:00
Nick Avramoussis cbda14ec8e Merge pull request #2070 from matthewdcong/master_develop_merge
Update NanoVDB with the latest round of changes
2025-08-07 09:49:27 +12:00
Nick Avramoussis 62b95b4115 Merge pull request #2046 from dreamworksanimation/ASWF-257
Remove unnecessary null pointer checks (ASWF issue #257)
2025-08-07 08:43:59 +12:00
Nick Avramoussis c2bc66e16d Improved formatting, spelling and comments
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-06 13:57:18 +12:00
Nick Avramoussis 92ea3888c5 Updated CI workflows and release notes
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-06 09:45:15 +12:00
Nick Avramoussis 691cf1bcd1 Merge branch 'master' into ax_gtest
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-06 09:44:56 +12:00
Matthew Cong 556a326c4a Update pendingchanges
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Matthew Cong 3966b9f991 Add clang build fixes
* Add clang build fixes

* Add/fix comments

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Matthew Cong 93549a75f2 Add support for CUDA 13.0
* Add initial support for CUDA 13

* More fixes for CUDA 13

* Fix comment

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Matthew Cong 8fc5038ecb Fix race condition in split/mergeGridHandles
* Fix race condition in split/mergeGridHandles

* Fix missing semicolon

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Matthew Cong c13dfc379c Add/restore support for custom resources in PointsToGrid
* Separate data from algorithm

* Add custom resource support

* Add docs

* Add support for cudax::async_resource

* Fix default alignment

* Add cleanup

* Split up DeviceResource into separate file

* Fix file

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Eftychios Sifakis c391f89af5 Adding kernel launch helpers with dynamic shared memory support to nanovdb::util
* adding launcher helpers for dynamic shared memory

* adding stream parameter

* adding code example in doxygen comments

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Ken Museth c4a3cb3643 Device buffer from host buffer
* minor cleanup

Signed-off-by: Ken <ken.museth@gmail.com>

* construct DeviceBuffer from HostBuffer

Signed-off-by: Ken <ken.museth@gmail.com>

* added HostBuffer::data<T>(int i)

Signed-off-by: Ken <ken.museth@gmail.com>

* improved unit-test

Signed-off-by: Ken <ken.museth@gmail.com>

* snapshot

Signed-off-by: Ken <ken.museth@gmail.com>

* removed whitespace

Signed-off-by: Ken <ken.museth@gmail.com>

---------

Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Ken Museth 3917418d99 Fix leak device buffer
* minor cleanup

Signed-off-by: Ken <ken.museth@gmail.com>

* fixed memory leak in move constructor of DeviceBuffer

Signed-off-by: Ken <ken.museth@gmail.com>

* fixed some typos

Signed-off-by: Ken <ken.museth@gmail.com>

---------

Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Eftychios SifakisandMatthew Cong c7d1408aa9 Add VoxelBlockManager
* Adding VoxelBlockManager with base functionality

* switching from deprecated CountOn implementation

* Unit test for basic VoxelBlockManager use

* fixing trailing whitespace

* fixing trailing whitespace

* Fixing bug in get() method, that prevents proper operation for other than leaf nodes

* Fix licenses

* Add stream parameter

* Verify linear access

* More cleanup

* Cleanup constants

* Remove header

* Add comment

* Expose NumThreads for VBM build

* Fix up comments

* Avoid reinterpret_cast and use cuda::atomic_ref

* Clean up params and add docs

* replaced or commented on cryptic constants

* Add named constants

* More cleanup

---------

Co-authored-by: Matthew Cong <mcong@nvidia.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Matthew Cong 8cd9a62f53 Use cudaMallocAsync and cudaMallocFree wrappers to fix vGPU compatibility
* Use cudaMallocAsync and cudaMallocFree wrappers to fix vGPU

* Add missing include

* Fix namespace

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Matthew Cong 77fc6d05f7 Fix class-memaccess warning in CreateNanoGrid.h
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Matthew Cong c5f5777e08 Fixes for signed/unsigned warnings
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
Matthew Cong ed5ad461d8 Templatize buffer type for distributed points to grid
* Templatize buffer type for distributed points to grid

* Add missing template parameter

* Add pool

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-08-05 11:49:08 -07:00
ghurstunither d3f37e7dfe Update LevelSetTubesImpl.h
Correct ScalarType computations.

Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com>
2025-08-05 09:55:53 -04:00
ghurstunither 952a86321c Update LevelSetTubesImpl.h
Preprocess level set tapered capsule data to handle negative radii inputs.

Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com>
2025-08-05 09:38:18 -04:00
Nick Avramoussis 36b6043f93 Minor fixes
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-05 14:57:26 +12:00
Nicholas Avramoussis 6a43628236 Updated surfacing pending changes
Signed-off-by: Nicholas Avramoussis <navramoussis@wetafx.co.nz>
2025-08-05 14:57:26 +12:00
Nick Avramoussis a7cc7208d2 Various changes:
- Added OPENVDB_HAS_INVOKABLE_MEMBER_FUNCTION and OPENVDB_HAS_MEMBER_FUNCTION
   macros for compile time checking of member functions
 - Updated points::rasterize signature to no longer accept interrupter or
   filters and instead delegate these to the transfer scheme. Added transfer
   modules to support them
 - Added support for transfer schemes to provide a rasterizePoints which takes
   a range of points
 - Updated PCA to support target and source point per voxel counts
 - Remove dependency on PCA in ellips rasterizer
 - Added projected ellips distance and improved axis-aligned ellips calculation kernels
   for better representations of SDF distance calculations.
 - Made the pws value to the ellips rasterizer optional
 - Removed templated interrupter type from all rasterize methods
 - Removed old ABI<=9 support from PCA methods
 - Move to new OPENVDB_ASSERT macro
 - Use #if instead of #ifdefs to ensure they are present

Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-05 14:57:26 +12:00
Nick Avramoussis 04a2340d2d Fixed an issue where smoothed/unsorted positions provided to the ellips rasterizer wouldn't be found by point transfer
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-03 22:02:09 +12:00
Nick Avramoussis 1dc3f9d206 Moved the old 'sphereScale' rasterizer param to be a uniform scale for non-anisotropic neighbourhoods on the PCA methods
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-03 22:02:09 +12:00
Nick Avramoussis fed0143846 Fixed an issue with smooth position rasterization which wouldn't always activate the correct topology if the search radius was larger than the point radius
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-03 22:02:09 +12:00
Nick Avramoussis 7885f4704f Switch to include self contribution during PCA
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-03 22:02:09 +12:00
Nick Avramoussis d7aea5d1a3 Fixed an issue where the sigma tolerance was being compared to exactly zero
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-03 22:02:09 +12:00
Nick Avramoussis 52406bf30f Initial implementation of anisotropic surfacing, including re-work of PointRasterize and other fixes
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-03 22:02:09 +12:00
Nick Avramoussis aaf0569678 Fixed some precision issues in Matrix methods
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-03 22:02:09 +12:00
Nick Avramoussis b341d3cc50 Removed unused AttributeHandle member
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-08-03 22:02:09 +12:00
Nick Avramoussis 8348da235c Merge pull request #2065 from Idclip/ax_backend_upgrade
AX backend restructure for LLVM opaque ptr support (LLVM 16+)
2025-08-03 21:57:37 +12:00
jmlait 7ebe25fe55 Merge pull request #2059 from sideeffects/send_upstream_houdini21sync
Synchronize with Houdini 21.
2025-08-02 10:52:21 -04:00
Nick Avramoussis d008d49eb3 Merge pull request #2034 from Idclip/win_delay_load
Removed delay loading last modified time warning logic
2025-08-01 08:35:09 +12:00
Nick Avramoussis 188b8aeb1c Add back mLastWriteTime guarded with ABI <= 12 for ABI compatibility with mapped file stream data
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-07-31 21:15:08 +12:00
Nicholas Avramoussis 286a197a80 AX backend restructure for LLVM opaque ptr support (LLVM 16+)
Signed-off-by: Nicholas Avramoussis <navramoussis@wetafx.co.nz>
2025-07-28 18:54:11 +12:00
Jeff Lait 6d238d412e Remove tabs.
Set the camera parameter to any camera to support LOPs.

Signed-off-by: Jeff Lait <jlait@andorra.sidefx.com>
2025-07-11 16:32:50 -04:00
Jeff Lait 584b2b5a1b Synchronize with Houdini 21.
Non trivial changes:
- Env flag to disable stream writing (backported to 19.50)
- H21 style camera options
- OP_InputIdx and OP_OutputIdx support.

Signed-off-by: Jeff Lait <jlait@andorra.sidefx.com>
2025-07-11 16:07:46 -04:00
John Mertic 786a33c2c7 Point to correct Technical Charter
Signed-off-by: John Mertic <jmertic@linuxfoundation.org>
2025-07-09 11:37:16 -04:00
Nick Avramoussis 5e1658e69b Merge pull request #2026 from Idclip/leaf_val_iters
Improved performance of LeafNode ValueIters
2025-07-05 17:27:34 +12:00
Nick Avramoussis 7b8f054f5f Added an assert to the ValueIter constructor to check if the Node ptr is valid
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-07-04 20:34:56 +12:00
jmlait 67964d1e15 Merge pull request #2042 from sideeffects/sendupstream_tscmeeting20250507
Meeting notes for May 7th 2025
2025-05-21 10:47:15 -04:00
Eric Powers 135616d177 Fix remove unnecessary null pointer checks, issue #257.
There only appeared to be one obvious case left.

Code search process:

    grep -C 3 delete .

... then look for if statements, in C-like code, conditioned on the same
variable, and no other obvious purpose that required the if statement.

Signed-off-by: Eric Powers <eric.powers@dreamworks.com>
2025-05-16 12:44:24 -07:00
Dan Bailey 7ac379d34b Merge pull request #2027 from danrbailey/tsc_260325
Meeting Notes 26th March 2025
2025-05-07 14:28:11 -07:00
Jeff Lait a58117a3df Meeting notes for May 7th 2025
Signed-off-by: Jeff Lait <jlait@andorra.sidefx.com>
2025-05-07 17:26:42 -04:00
Nick Avramoussis 528c470d06 Removed delay loading last modified time warning logic
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-04-24 17:39:40 +12:00
Nick Avramoussis 4ab3f27e48 Merge pull request #2008 from jjtParadox/patch-1
Slowdown fix - Avoid unnecessary getLastWriteTime calls for memory mapped files on windows
2025-04-24 17:21:07 +12:00
Nick Avramoussis 4b7328db67 Merge pull request #2017 from sideeffects/sendupstream_nodiscard
Add [[nodiscard]] to confusing math functions.
2025-04-24 17:20:23 +12:00
Jonathan Swartz 6e7ab0d49b Merge pull request #2031 from matthewdcong/sync/aswf_master
Update NanoVDB with the latest changes
2025-04-24 06:10:45 +12:00
Matthew Cong 2ba65fe8b4 Move away from deprecated 20.04 runners
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:50:03 -07:00
Matthew Cong 16fb13286c Force set _WIN32 flag
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:50:02 -07:00
Matthew Cong 5a0c07d06e Fix Windows warnings
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:50:01 -07:00
Ken Museth 1f35dfe61e Improve points to grid
* minor cleanup

Signed-off-by: Ken <ken.museth@gmail.com>

* minor improvements to nanovdb::tools::cuda::PointsToGrid

Signed-off-by: Ken <ken.museth@gmail.com>

---------

Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:59 -07:00
Matthew Cong f60217a439 Add optimizations to MGPU PointsToGrid
* Add timer for MGPU PointsToGrid

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Avoid blocking kernel launches due to cudaFree

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix overlapping of copies

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Pipeline pointsPerVoxelPrefix sum

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Cleanup and optimization

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Further improve pipelining

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Remove unnecessary sync point

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Revert "Remove unnecessary sync point"

This reverts commit f01b36ad852f32ec65517b3baa08ea267d5edc2d.

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Switch to GPU sync

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix event destruction race condition and reduce host thread latency

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Revert timer add

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* More fine-grained pipelining

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix event wait/destruction race condition

Signed-off-by: Matthew Cong <mcong@nvidia.com>

---------

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:56 -07:00
Ken Museth aba9a149eb Feature/fix get set methods
* minor cleanup

Signed-off-by: Ken <ken.museth@gmail.com>

* fixed issue in get/set random access methods

Signed-off-by: Ken <ken.museth@gmail.com>

* cleanup

Signed-off-by: Ken <ken.museth@gmail.com>

* deleted white space

Signed-off-by: Ken <ken.museth@gmail.com>

* cleanup

Signed-off-by: Ken <ken.museth@gmail.com>

* added unit tests

Signed-off-by: Ken <ken.museth@gmail.com>

* improved unit tests

Signed-off-by: Ken <ken.museth@gmail.com>

* improved unit tests

Signed-off-by: Ken <ken.museth@gmail.com>

---------

Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:52 -07:00
Matthew Cong ff943a30eb Fix race condition across different merge levels (and memory leak in corresponding tests)
* Fix memory leaks in PointsToGrid tests

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix leapfrogging across recursion levels

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Parallelize kernel dispatch for different levels

Signed-off-by: Matthew Cong <mcong@nvidia.com>

---------

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:49 -07:00
Matthew Cong ef94a3b184 Generalize MGPU grid construction to any power-of-two number of GPUs
* Start support for more than two GPUs in key merge

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Flip flop storage

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Generalize concurrent leftIntervals/rightIntervals usage

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix median search

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix rebalancing

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix recursive MGPU merge

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Update TODO

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Add guards for zero-tile GPUs

Signed-off-by: Matthew Cong <mcong@nvidia.com>

---------

Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:45 -07:00
Matthew Cong 5f1033dfd7 Suppress stringop-overflow warning due to GCC 11 regression
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:42 -07:00
Jonathan Swartz e48a8f8d5c Adding /bigobj to TestNanoVDB.cu compilation options to fix Windows build failures
* Adding /bigobj to TestNanoVDB.cu compilation options to fix Windows build failure
---------

Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:38 -07:00
Matthew CongandKen Museth b91cb6535a Silence CMake warning about FindBoost module deprecation
* Silence CMake warning about FindBoost module deprecation

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix typo

Signed-off-by: Matthew Cong <mcong@nvidia.com>

---------

Signed-off-by: Matthew Cong <mcong@nvidia.com>
Co-authored-by: Ken Museth <1495380+kmuseth@users.noreply.github.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:33 -07:00
Ken Museth 9b728cba18 minor cleanup
Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:29 -07:00
Ken Museth 88eb8c998b revert deprecation warning
Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:26 -07:00
Matthew Congand= 316e0b4d03 Add distributed implementation of PointsToGrid
* Add distributed implementation of PointsToGrid

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix race condition in merge and single GPU case

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix kernel/cudaFree race conditions

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Add DistributedPointsToGrid unittest

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Clean up example

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Use range-based for loops

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Use structured bindings inside for loop

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix Windows build and some warnings

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Update for refactored DeviceMesh

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix copyright/include

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Add parallelForEach helper

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Templatize kernels to align with CUB better

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Shorten TemporaryDevicePool to TempDevicePool

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Refactor to use TempDevicePool

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Switch to class

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Parallelize kernel dispatch in build

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix race condition wrt to root node processing

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Speed up synchronization

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Add comments and fix deprecated TransformInputIterator

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Address review comments

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Address more review comments

* Add more cudaCheck calls and restrict EqualityIndicator type

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix license identifiers and remove unused code

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Add missing include

Signed-off-by: Matthew Cong <mcong@nvidia.com>

---------

Signed-off-by: Matthew Cong <mcong@nvidia.com>
Co-authored-by: = <=>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:23 -07:00
Matthew Cong dd7f12eb23 Consolidate DeviceGuard into DeviceMesh header
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:20 -07:00
Ken Museth 72406ff08d fixed typos and improved documentation in NanoVDB
Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:17 -07:00
Ken Museth 4f96110d5a introducing new magic numbers for grids and files
* introducing new magic numbers for grids and files

Signed-off-by: Ken <ken.museth@gmail.com>

* removed two redundant magic numbers

Signed-off-by: Ken <ken.museth@gmail.com>

---------

Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:14 -07:00
Ken Museth d0e37dff19 minor simplification in CreateNanoGrid
* minor simplification in CreateNanoGrid

Signed-off-by: Ken <ken.museth@gmail.com>

* fixed typo

Signed-off-by: Ken <ken.museth@gmail.com>

---------

Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:11 -07:00
Ken Museth 8598155fcb major improvements to GridBlindMetaData
* major improvements to GridBlindMetaData

Signed-off-by: Ken <ken.museth@gmail.com>

* minor changes to fix clang issue

Signed-off-by: Ken <ken.museth@gmail.com>

---------

Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:07 -07:00
Matthew CongandKen Museth 462aafda79 Fix class-memaccess warning due to non-trivial copy-assignment in GridBlindMetaData
Signed-off-by: Matthew Cong <mcong@nvidia.com>
Co-authored-by: Ken Museth <1495380+kmuseth@users.noreply.github.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:04 -07:00
Ken Museth c945c36a09 removed dead code and updated the minor version number
Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:49:00 -07:00
Matthew CongandKen Museth 267da7d91d Add missing constructor for C++20 used in AddBlindData.cuh
Signed-off-by: Matthew Cong <mcong@nvidia.com>
Co-authored-by: Ken Museth <1495380+kmuseth@users.noreply.github.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:57 -07:00
Matthew Cong 74ae3a5989 Fix DeviceMesh.h header to be self-contained
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:54 -07:00
Ken Museth 1db28418eb Fixed issue in signedfloodfill
* Fixed issue in signedfloodfill

Signed-off-by: Ken <ken.museth@gmail.com>

* partially addressed review comments

Signed-off-by: Ken <ken.museth@gmail.com>

* added RootData::TileIterator

Signed-off-by: Ken <ken.museth@gmail.com>

* cleanup

Signed-off-by: Ken <ken.museth@gmail.com>

* fixed assert bug

Signed-off-by: Ken <ken.museth@gmail.com>

* snapshot

Signed-off-by: Ken <ken.museth@gmail.com>

* fixed bug and improved unit-test

Signed-off-by: Ken <ken.museth@gmail.com>

* improved unit test and documentation

Signed-off-by: Ken <ken.museth@gmail.com>

---------

Signed-off-by: Ken <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:51 -07:00
Matthew Cong 690104f408 Fix TBB template deduction in C++20
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:48 -07:00
Matthew Cong 4adbccaee1 Fix out-of-bounds device id
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:46 -07:00
Matthew Cong 22e17e7956 Fix namespace for AbsDiff and RelDiff ostream overloads
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:43 -07:00
Matthew Cong d130267347 Fix missing relocatable device code flag
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:38 -07:00
Matthew Cong 1d4cf67d80 Change UnifiedBuffer prefetch and advise to be const
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:35 -07:00
Matthew Cong 4d500e10d0 Fix invalid free in UnifiedBuffer move constructor
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:32 -07:00
Matthew Cong 1d1899bb0e Add missing NCCL ifdef
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:28 -07:00
Matthew Cong 242b573c75 Add missing inline for DeviceMesh functions
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:24 -07:00
Matthew Cong d21bec89dc Add missing const variant of deviceData(device)
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:21 -07:00
Matthew CongandMark Harris d71b7417ad Switch to DeviceMesh for MGPU sparse convolution example
* Convert MGPU convolution example to use DeviceMesh

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Fix typos and clarify

Co-authored-by: Mark Harris <783069+harrism@users.noreply.github.com>

* Check for errors and switch to for_each

Signed-off-by: Matthew Cong <mcong@nvidia.com>

---------

Signed-off-by: Matthew Cong <mcong@nvidia.com>
Co-authored-by: Mark Harris <783069+harrism@users.noreply.github.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:18 -07:00
Matthew CongandMark Harris 9329349461 Add DeviceMesh and (optional) NCCL CMake Support
* Add device mesh

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Restore current device in constructor and destructor

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Enable range-based for loops

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Use structured bindings inside for loop

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Remove uneeded accessors and modularize functions

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Statically initialize entry point

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Encapsulate

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Switch to lightweight wrapper class for DeviceNodes vector

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Cleanup

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Minor changes needed for DistributedPointsToGrid

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Add RAII DeviceGuard

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* More cleanup

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Clean up NCCL dependency

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Remove parallel_for to modularize

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Implement and test move constructor/assignment

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Add docs

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Expand test and documentation

Signed-off-by: Matthew Cong <mcong@nvidia.com>

* Move non-reinit comment to function description

Co-authored-by: Mark Harris <783069+harrism@users.noreply.github.com>

* Switch to using aliases and modern type names

Co-authored-by: Mark Harris <783069+harrism@users.noreply.github.com>

* Use size_type instead of int for return

Co-authored-by: Mark Harris <783069+harrism@users.noreply.github.com>

* Separate out DeviceGuard and clean up API

Signed-off-by: Matthew Cong <mcong@nvidia.com>

---------

Signed-off-by: Matthew Cong <mcong@nvidia.com>
Co-authored-by: Mark Harris <783069+harrism@users.noreply.github.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:14 -07:00
Ken Museth 40853821a1 Minor fix to unit-test of NanoVDB
* Minor fix to unit-test of NanoVDB

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* minor change

Signed-off-by: Ken Museth <ken.museth@gmail.com>

---------

Signed-off-by: Ken Museth <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:11 -07:00
Matthew Cong f96095f6cf Fix ODR violation due to anonymous namespace in SignedFloodFill
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:08 -07:00
Matthew Cong 97b229964c Fix format strings and Windows conversion warning
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:05 -07:00
Ken Museth 5f54d98080 improved nanovdb_convert
* improved nanovdb_convert

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* fixed typo

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* added unit tests

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* review feedback

Signed-off-by: Ken Museth <ken.museth@gmail.com>

---------

Signed-off-by: Ken Museth <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:48:01 -07:00
Matthew Cong a96cb41408 Add missing iomanip include for latest GTest
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:47:57 -07:00
Matthew Cong a79ec33944 Separate PointsToGrid kernel lambdas into functors
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:47:56 -07:00
Ken Museth 0c94550eaa improved change notes and updated version number
* improved change notes and updated version number

Signed-off-by: Ken Museth <ken.museth@gmail.com>

* improved documentation

Signed-off-by: Ken Museth <ken.museth@gmail.com>

---------

Signed-off-by: Ken Museth <ken.museth@gmail.com>
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:47:53 -07:00
Matthew Cong 92d689c3bd Fix missing OpenVDB dependency for NanoVDB-only build
Signed-off-by: Matthew Cong <mcong@nvidia.com>
2025-04-21 11:47:37 -07:00
Dan Bailey a05dc7a903 Meeting Notes
Signed-off-by: Dan Bailey <danbailey@ilm.com>
2025-04-08 22:16:10 -07:00
Nick Avramoussis 8b7c521f4d Added OPENVDB_ASSUME macros and applied them to Leaf ValueIters - fixes vectorization misses with clang
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-04-06 20:58:21 +12:00
Nick Avramoussis e2e0cb6d03 Improved performance of LeafNode ValueIters
Signed-off-by: Nick Avramoussis <4256455+Idclip@users.noreply.github.com>
2025-04-06 19:09:10 +12:00
Jeff Lait f8a1b1ef71 Add [[nodiscard]] to confusing math functions.
Signed-off-by: Jeff Lait <jlait@andorra.sidefx.com>
2025-03-26 14:50:05 -04:00
jjtParadox 2d7ae513ef Avoid unnecessary and expensive getLastWriteTime calls for memory mapped files on windows
Signed-off-by: jjtParadox <jturner@4nsi.com>
2025-02-27 09:25:54 -06:00
Tim Straubinger 692d63e3b8 Remove whitespace
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 14:43:40 -08:00
Tim Straubinger 109c3f2591 Remove all mentions of CppUnit from build dependencies and instructions
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:57:33 -08:00
Tim Straubinger ac56c2496d Remove CppUnit from AX test CMake requirements
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:51:25 -08:00
Tim Straubinger 68d9753437 Remove cppunit utilities from main AX test runner
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:51:25 -08:00
Tim Straubinger dcd6744424 Convert all of AX integration test suite to use gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:51:25 -08:00
Tim Straubinger 5df8871827 Convert all of AX frontend test suite to use gtest; remove temporary cppunit dual support from utils.h
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:51:24 -08:00
Tim Straubinger 748ace48cd Convert TestArrayPack.cc to use gtest; add temporary support for both gtest and cppunit to test/util.h
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:51:24 -08:00
Tim Straubinger 8a13cb2b02 Convert TestVolumeExecutable.cc from cppunit to gtest; add gtest friend declarations to VolumeExecutable
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:51:20 -08:00
Tim Straubinger ecb6e7b07c Convert TestPointExecutable.cc from cppunit to gtest; add gtest friend declarations to PointExecutable
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:46:21 -08:00
Tim Straubinger 5e6a0d260b Convert TestAXRun.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger 18c2c3a992 Convert TestTypes.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger 5beb2c5367 Convert TestSymbolTable.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger b44248b200 Convert TestStringIR.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger f25319edbb Convert TestLogger.cc from cppunit to gtest; add gtest friend declarations to Logger
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger 26ada31fa1 Convert TestFunctionTypes.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger 7aa4136886 Convert TestFunctionRegistry.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger efa5176137 Convert TestFunctionGroup.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger a065b5e3de Convert TestComputeGeneratorFailures.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger 5c2f8e80f9 Convert TestCodecs.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger 9bb34dff3f Convert TestAttributeBindings.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger 40a3ef4c05 Convert TestScanners.cc from cppunit to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger 29a17e36bc Revert deliberate test failure used for testing
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
Tim Straubinger 3ed5f6e93d Add GoogleTest to AX test; run cppunit and gtest in tandem for gradual conversion; convert TestPrinters to gtest
Signed-off-by: Tim Straubinger <tstraubinger@ilm.com>
2025-01-07 13:33:23 -08:00
345 changed files with 30211 additions and 13970 deletions
+22 -14
View File
@@ -67,17 +67,22 @@ jobs:
matrix:
# Only test unified builds (core+ax). weekly CI should test standalone
config:
- { image: '2023-clang15', cxx: 'clang++', build: 'Release', cmake: '' }
- { image: '2023-clang15', cxx: 'g++', build: 'Release', cmake: '' }
- { image: '2023-clang15', cxx: 'clang++', build: 'Debug', cmake: '' }
- { image: '2026', cxx: 'clang++', build: 'Release', cmake: '' }
- { image: '2026', cxx: 'g++', build: 'Release', cmake: '' }
- { image: '2026', cxx: 'clang++', build: 'Debug', cmake: '' }
- { image: '2025-clang19.1', cxx: 'clang++', build: 'Release', cmake: '' }
- { image: '2024-clang16.2', cxx: 'clang++', build: 'Release', cmake: '' }
# Needed to test LLVM 15.X
- { image: '2023-clang15', cxx: 'clang++', build: 'Release', cmake: '-DDISABLE_DEPENDENCY_VERSION_CHECKS=ON' }
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: nanobind
#if: contains(matrix.config.image, '2023') == false
run: ./ci/install_nanobind.sh 2.0.0
- name: install_gtest
run: ./ci/install_gtest.sh 1.15.2
run: ./ci/install_nanobind.sh 2.5.0
# 2026 images are broken with gtest/glfw installs
- name: deps
if: contains(matrix.config.image, '2026')
run: ./ci/install_gtest.sh latest
- name: timestamp
id: timestamp
run: echo "timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
@@ -94,12 +99,11 @@ jobs:
run: >
./ci/build.sh -v
--build-type=${{ matrix.config.build }}
--components='core,python,bin,axcore,axbin,axtest'
--components='core,python,axcore,axbin,axtest'
--cargs=\"
${{ matrix.config.cmake }}
-DOPENVDB_AX_TEST_CMD_DOWNLOADS=ON
-DUSE_EXPLICIT_INSTANTIATION=OFF
-DOPENVDB_CXX_STRICT=ON
\"
- name: test
run: cd build && ctest -V
@@ -122,9 +126,13 @@ jobs:
strategy:
matrix:
config:
# Last macos runner before M1 (macos-14)
- { runner: 'macos-13', cxx: 'clang++', build: 'Release', llvm: '15' }
- { runner: 'macos-13', cxx: 'clang++', build: 'Release', llvm: '13' }
- { runner: 'macos-15', cxx: 'clang++', build: 'Release', llvm: '21' }
- { runner: 'macos-14', cxx: 'clang++', build: 'Release', llvm: '21' }
- { runner: 'macos-14', cxx: 'clang++', build: 'Release', llvm: '20' }
- { runner: 'macos-14', cxx: 'clang++', build: 'Release', llvm: '19' }
- { runner: 'macos-14', cxx: 'clang++', build: 'Release', llvm: '18' }
- { runner: 'macos-14', cxx: 'clang++', build: 'Release', llvm: '17' }
- { runner: 'macos-14', cxx: 'clang++', build: 'Release', llvm: '16' }
fail-fast: false
steps:
- uses: actions/checkout@v3
@@ -136,7 +144,7 @@ jobs:
run: >
./ci/build.sh -v
--build-type=${{ matrix.config.build }}
--components="core,python,bin,axcore,axbin,axtest"
--components="core,python,axcore,axbin,axtest"
--cargs=\"
-DOPENVDB_AX_TEST_CMD_DOWNLOADS=ON
-DUSE_EXPLICIT_INSTANTIATION=OFF
@@ -154,7 +162,7 @@ jobs:
github.event.inputs.type == 'grammar'
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'ubuntu-22.04-8c-32g-300h') || 'ubuntu-latest' }}
container:
image: aswf/ci-openvdb:2023-clang15
image: aswf/ci-openvdb:2025-clang19.1
steps:
- uses: actions/checkout@v3
- name: build
+25 -24
View File
@@ -33,11 +33,8 @@ on:
- 'pendingchanges/**'
- '**.md'
schedule:
# run this workflow every day at 7am UTC except Monday
- cron: '0 7 * * 0,2-6'
# run this workflow Monday 7am UTC
# warning: This pattern is checked in various places below
- cron: '0 7 * * 1'
# run this workflow every day 7am UTC
- cron: '0 7 * * *'
workflow_dispatch:
inputs:
type:
@@ -64,10 +61,7 @@ jobs:
github.event.inputs.type == 'linux'
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'ubuntu-22.04-8c-32g-300h') || 'ubuntu-latest' }}
name: >
linux-vfx:${{ matrix.config.image }}-
abi:${{ matrix.config.abi }}-
cxx:${{ matrix.config.cxx }}-
type:${{ matrix.config.build }}
linux:${{ matrix.config.image }}-abi:${{ matrix.config.abi }}-cxx:${{ matrix.config.cxx }}-type:${{ matrix.config.build }}
container:
image: aswf/ci-openvdb:${{ matrix.config.image }}
env:
@@ -76,21 +70,22 @@ jobs:
strategy:
matrix:
config:
- { cxx: clang++, image: '2024', abi: '12', build: 'Release', cmake: '' }
- { cxx: g++, image: '2024', abi: '12', build: 'Release', cmake: '' }
- { cxx: clang++, image: '2024', abi: '12', build: 'Debug', cmake: '' }
- { cxx: clang++, image: '2023', abi: '11', build: 'Release', cmake: '-DDISABLE_DEPENDENCY_VERSION_CHECKS=ON' }
- { cxx: g++, image: '2023', abi: '11', build: 'Release', cmake: '-DDISABLE_DEPENDENCY_VERSION_CHECKS=ON' }
- { cxx: clang++, image: '2026', abi: '13', build: 'Release', cmake: '-DOPENVDB_USE_FUTURE_ABI_13=ON' }
- { cxx: g++, image: '2026', abi: '13', build: 'Release', cmake: '-DOPENVDB_USE_FUTURE_ABI_13=ON' }
- { cxx: clang++, image: '2026', abi: '13', build: 'Debug', cmake: '-DOPENVDB_USE_FUTURE_ABI_13=ON' }
- { cxx: clang++, image: '2025-clang19.1', abi: '12', build: 'Release', cmake: '' }
- { cxx: clang++, image: '2024-clang17.2', abi: '11', build: 'Release', cmake: '-DDISABLE_DEPENDENCY_VERSION_CHECKS=ON' }
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: nanobind
run: ./ci/install_nanobind.sh 2.0.0
- name: glfw
if: contains(matrix.config.image, '2023') == true
run: ./ci/install_glfw.sh 3.3.10
- name: install_gtest
run: ./ci/install_gtest.sh 1.15.2
run: ./ci/install_nanobind.sh 2.5.0
# 2026 images are broken with gtest/glfw installs
- name: deps
if: contains(matrix.config.image, '2026')
run: |
./ci/install_gtest.sh latest
./ci/install_glfw.sh latest
- name: timestamp
id: timestamp
run: echo "timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
@@ -114,9 +109,8 @@ jobs:
-DOPENVDB_ABI_VERSION_NUMBER=${{ matrix.config.abi }}
\"
- name: test
# Always run tests on weekly builds but skip Debug on commits as they take a while.
# https://github.community/t/distinct-job-for-each-schedule/17811/2
if: contains(github.event.schedule, '0 7 * * 1') || matrix.config.build == 'Release'
# Skip Debug on commits as they take a while.
if: github.event_name == 'schedule'|| matrix.config.build == 'Release'
run: |
cd build && ctest -V
cd - && ./ci/test_install.sh
@@ -172,9 +166,15 @@ jobs:
github.event_name != 'workflow_dispatch' ||
github.event.inputs.type == 'all' ||
github.event.inputs.type == 'mac'
runs-on: macos-13 # Last macos runner befor M1 (macos-14)
runs-on: ${{ matrix.config.image }}
name: ${{ matrix.config.image }}
env:
CXX: clang++
strategy:
matrix:
config:
- { image: macos-14 }
- { image: macos-15 }
steps:
- uses: actions/checkout@v3
- name: install
@@ -191,3 +191,4 @@ jobs:
\'
- name: test
run: cd build && ctest -V
+4 -13
View File
@@ -38,21 +38,15 @@ jobs:
env:
CXX: g++
container:
# @note We can't update this as epydoc doesn't support python3. We'll
# need to re-write the python docs to use sphinx
image: aswf/ci-openvdb:2024
image: aswf/ci-openvdb:2026
steps:
- uses: actions/checkout@v4
- name: install_doxygen
run: ./ci/install_doxygen.sh 1_8_11
- name: nanobind
#if: contains(matrix.config.image, '2023') == false
run: ./ci/install_nanobind.sh 2.0.0
# - name: install_epydoc
# run: pip install epydoc
run: ./ci/install_nanobind.sh 2.5.0
- name: install_latex
run: |
yum -y install texlive-latex-bin texlive-dvips texlive-collection-fontsrecommended texlive-collection-latexrecommended
run: ./ci/install_texlive.sh
- name: build
run: >
./ci/build.sh -v
@@ -65,9 +59,6 @@ jobs:
-DUSE_EXPLICIT_INSTANTIATION=OFF
-DDISABLE_DEPENDENCY_VERSION_CHECKS=ON
\"
# - name: epydoc
# run: |
# epydoc --html -o /usr/local/share/doc/OpenVDB/html/python pyopenvdb
- name: pre_deploy
# Overwrite global SSH configuration
# https://github.com/peaceiris/actions-gh-pages/issues/719
@@ -106,7 +97,7 @@ jobs:
github.event.inputs.deploy == 'coverage'
runs-on: ubuntu-latest
container:
image: aswf/ci-openvdb:2023
image: aswf/ci-openvdb:2025-clang19.1
env:
CXX: g++
steps:
+12 -20
View File
@@ -66,7 +66,7 @@ jobs:
${{ needs.checksecret.outputs.HOUDINI_SECRETS == 'true' ||
github.repository_owner == 'AcademySoftwareFoundation' }}
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'ubuntu-22.04-8c-32g-300h') || 'ubuntu-latest' }}
name: hou:${{ matrix.config.hou_hash }}-vfx:${{ matrix.config.image }}-cxx:${{ matrix.config.cxx }}
name: hou:${{ matrix.config.hou_hash }}-vfx:${{ matrix.config.image }}-cxx:${{ matrix.config.cxx }}-build:${{ matrix.config.build }}
container:
image: aswf/ci-base:${{ matrix.config.image }}
env:
@@ -75,26 +75,18 @@ jobs:
strategy:
matrix:
config:
- { cxx: clang++, image: '2024', hou_hash: '20_5', build: 'Release', components: 'core,hou,bin,view,render,python,test' }
- { cxx: clang++, image: '2023', hou_hash: '20_5', build: 'Debug', components: 'core,hou,bin,view,render,python,test,axcore,axbin,axtest' }
- { cxx: g++, image: '2023', hou_hash: '20_5', build: 'Release', components: 'core,hou,bin,view,render,python,test,axcore,axbin,axtest' }
- { cxx: clang++, image: '2023', hou_hash: '20_0-newabi', build: 'Release', components: 'core,hou,bin,view,render,python,test,axcore,axbin,axtest' }
- { cxx: g++, image: '2023', hou_hash: '20_0-newabi', build: 'Release', components: 'core,hou,bin,view,render,python,test,axcore,axbin,axtest' }
- { cxx: clang++, image: '2026', hou_hash: '21_0', build: 'Release', components: 'core,hou,bin,view,render,python,test,axcore,axbin,axtest' }
- { cxx: clang++, image: '2026', hou_hash: '21_0', build: 'Debug', components: 'core,hou,bin,view,render,python,test,axcore,axbin,axtest' }
- { cxx: g++, image: '2026', hou_hash: '21_0', build: 'Release', components: 'core,hou,bin,view,render,python,test,axcore,axbin,axtest' }
- { cxx: clang++, image: '2024', hou_hash: '20_5', build: 'Release', components: 'core,hou,bin,view,render,python,test,axcore,axbin,axtest' }
- { cxx: g++, image: '2024', hou_hash: '20_5', build: 'Release', components: 'core,hou,bin,view,render,python,test,axcore,axbin,axtest' }
fail-fast: false
steps:
- name: remove zstd
run: yum -y remove zstd
- uses: actions/checkout@v3
- name: nanobind
#if: contains(matrix.config.image, '2023') == false
run: ./ci/install_nanobind.sh 2.0.0
- name: glfw
if: contains(matrix.config.image, '2023') == true
run: ./ci/install_glfw.sh 3.3.10
- name: cppunit
run: ./ci/install_cppunit.sh 1.15.1
run: ./ci/install_nanobind.sh 2.5.0
- name: install_gtest
run: ./ci/install_gtest.sh 1.15.2
run: ./ci/install_gtest.sh latest
- name: timestamp
id: timestamp
run: echo "timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
@@ -125,7 +117,7 @@ jobs:
- name: build
run: |
./ci/build.sh -v \
--build-type=Release \
--build-type="${{ matrix.config.build }}" \
--components="${{ matrix.config.components }}" \
--cargs=\" \
-DHOUDINI_ROOT=$HOME/houdini_install/hou \
@@ -135,6 +127,7 @@ jobs:
-DDISABLE_DEPENDENCY_VERSION_CHECKS=ON \
\"
- name: test
if: matrix.config.build == 'Release'
run: cd build && ctest -V
# Keep ccache light by stripping out any caches not accessed in the last day
- name: ccache_clean
@@ -165,7 +158,7 @@ jobs:
cp hou/hou.tar.gz $HOME/houdini_install/hou.tar.gz
cd $HOME/houdini_install && tar -xzf hou.tar.gz && cd -
- name: install_deps
run: ./ci/install_macos.sh 15
run: ./ci/install_macos.sh 20
- name: build
run: |
./ci/build.sh -v \
@@ -178,8 +171,7 @@ jobs:
-DDISABLE_CMAKE_SEARCH_PATHS=ON \
-DDISABLE_DEPENDENCY_VERSION_CHECKS=ON \
-DUSE_EXPLICIT_INSTANTIATION=OFF \
-DTbb_INCLUDE_DIR=$HOME/houdini_install/hou/Frameworks/Houdini.framework/Versions/Current/Resources/toolkit/include/tbb \
-DLLVM_DIR=/opt/homebrew/opt/llvm@15/lib/cmake/llvm \
-DLLVM_DIR=/opt/homebrew/opt/llvm@20/lib/cmake/llvm \
-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \
\"
- name: test
+14 -13
View File
@@ -61,16 +61,15 @@ jobs:
strategy:
matrix:
config:
- { cxx: g++, image: '2024', build: 'Release' }
- { cxx: g++, image: '2024', build: 'Debug' }
- { cxx: clang++, image: '2024', build: 'Release' }
- { cxx: clang++, image: '2024', build: 'Debug' }
- { cxx: g++, image: '2024-clang17.2', build: 'Release' }
- { cxx: g++, image: '2024-clang17.2', build: 'Debug' }
- { cxx: clang++, image: '2024-clang17.2', build: 'Release' }
- { cxx: clang++, image: '2024-clang17.2', build: 'Debug' }
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: setup_cuda_12
run: |
yum -y install yum-utils
echo "/usr/local/cuda-12/bin" >> $GITHUB_PATH
echo "LD_LIBRARY_PATH=/usr/local/cuda-12/lib64:$LD_LIBRARY_PATH" >> $GITHUB_ENV
- name: build
@@ -90,7 +89,7 @@ jobs:
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
\'
- name: test
run: cd build && sudo ctest -V -E ".*cuda.*"
run: cd build && sudo ctest -V -E ".*cuda.*|.*mgpu.*"
windows-nanovdb:
if: |
@@ -118,6 +117,7 @@ jobs:
shell: powershell
run: .\ci\install_windows.ps1
- name: build
# nvcc doesn't set _WIN32 when run in bash so we need to set it manually
shell: bash
run: >
./ci/build.sh -v
@@ -129,13 +129,14 @@ jobs:
-DUSE_EXPLICIT_INSTANTIATION=OFF
-DNANOVDB_USE_CUDA=ON
-DCMAKE_CUDA_ARCHITECTURES="80"
-DCMAKE_CUDA_FLAGS="-D_WIN32"
-DNANOVDB_USE_OPENVDB=ON
-DVCPKG_TARGET_TRIPLET=${VCPKG_DEFAULT_TRIPLET}
-DCMAKE_TOOLCHAIN_FILE=\"${VCPKG_INSTALLATION_ROOT}\\scripts\\buildsystems\\vcpkg.cmake\"
\'
- name: test
shell: bash
run: cd build && ctest -V -E ".*cuda.*"
run: cd build && ctest -V -E ".*cuda.*|.*mgpu.*"
macos-nanovdb:
if: |
@@ -148,8 +149,8 @@ jobs:
strategy:
matrix:
config:
- { runner: 'macos-13', cxx: 'clang++', build: 'Release' }
- { runner: 'macos-13', cxx: 'clang++', build: 'Debug' }
- { runner: 'macos-14', cxx: 'clang++', build: 'Release' }
- { runner: 'macos-14', cxx: 'clang++', build: 'Debug' }
fail-fast: false
steps:
- uses: actions/checkout@v3
@@ -162,9 +163,9 @@ jobs:
./ci/build.sh -v
--build-type=${{ matrix.config.build }}
--components=core,nano,nanotest,nanoexam,nanobench,nanotool
--cargs=\'-DUSE_EXPLICIT_INSTANTIATION=OFF -DNANOVDB_USE_CUDA=OFF -DNANOVDB_USE_OPENVDB=ON\'
--cargs=\'-DUSE_EXPLICIT_INSTANTIATION=OFF -DNANOVDB_USE_CUDA=OFF -DNANOVDB_USE_OPENVDB=ON -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install \'
- name: test
run: cd build && ctest -V -E ".*cuda.*"
run: cd build && ctest -V -E ".*cuda.*|.*mgpu.*"
nanovdb-lite:
if: |
@@ -173,11 +174,11 @@ jobs:
github.event.inputs.type == 'linux'
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'ubuntu-22.04-8c-32g-300h') || 'ubuntu-latest' }}
container:
image: aswf/ci-openvdb:2024
image: aswf/ci-openvdb:2024-clang17.2
steps:
- uses: actions/checkout@v3
- name: install_gtest
run: ./ci/install_gtest.sh 1.15.2
run: ./ci/install_gtest.sh latest
- name: build_and_test
run: |
cd nanovdb/nanovdb
+61 -51
View File
@@ -70,23 +70,13 @@ jobs:
strategy:
matrix:
config:
- { houdini_version: '20.0', platform: 'linux_x86_64_gcc11.2', hou_hash: '20_0-newabi' }
- { houdini_version: '21.0', platform: 'linux_x86_64_gcc11.2', hou_hash: '21_0' }
- { houdini_version: '20.5', platform: 'linux_x86_64_gcc11.2', hou_hash: '20_5' }
fail-fast: false
container:
image: aswf/ci-base:2024
steps:
- uses: actions/checkout@v3
# We bumped from the 2021 CI image to 2023 here to fix some OpenSSL issues
# with the Houdini download script. In so doing we broke some of the caching
# between this job and the jobs in houdini.yml which _don't_ use the 2023
# image yet. The issue is that the cache action will use zstd if it's
# available to zip the cache and this causes it to be inserted with a unique
# hash which images without zstd (i.e. the 2021/2022 images don't have
# access to). For now, uninstall zstd here instead of installing it
# everywhere and ask the LF to add zstd to the older base images.
- name: remove zstd
run: yum -y remove zstd
- name: timestamp
id: timestamp
run: echo "timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
@@ -113,7 +103,7 @@ jobs:
# Note that macos-14 (current macos-latest) switches to M1. We could instead test
# the arm build here instead of the x86 one.
runs-on: macos-latest
name: macos-houdini-20
name: macos-houdini
env:
HOUDINI_CLIENT_ID: ${{ secrets.HOUDINI_CLIENT_ID }}
HOUDINI_SECRET_KEY: ${{ secrets.HOUDINI_SECRET_KEY }}
@@ -123,7 +113,7 @@ jobs:
id: timestamp
run: echo "timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
- name: download_houdini
run: ./ci/download_houdini.sh 20.0 macosx_arm64_clang14.0_13 --prod
run: ./ci/download_houdini.sh 21.0 macosx_arm64_clang15.0_14 --prod
- name: install_houdini
run: |
mkdir $HOME/houdini_install
@@ -149,28 +139,35 @@ jobs:
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'ubuntu-22.04-8c-32g-300h') || 'ubuntu-latest' }}
name: linux-extra:${{ matrix.config.name }}
container:
image: aswf/ci-openvdb:2024
image: aswf/ci-openvdb:${{ matrix.config.image }}
env:
CXX: clang++
strategy:
matrix:
config:
- { name: 'all', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DUSE_BLOSC=ON -DUSE_ZLIB=ON -DUSE_EXR=ON -DUSE_PNG=ON' }
- { name: 'lite', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DUSE_BLOSC=OFF -DUSE_ZLIB=OFF -DUSE_EXR=OFF -DUSE_PNG=OFF -DOPENVDB_USE_DELAYED_LOADING=OFF' }
- { name: 'half', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DUSE_BLOSC=OFF -DUSE_IMATH_HALF=ON' }
- { name: 'sse', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DOPENVDB_SIMD=SSE42' }
- { name: 'avx', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DOPENVDB_SIMD=AVX' }
- { name: 'numpy', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DUSE_NUMPY=ON -DOPENVDB_PYTHON_WRAP_ALL_GRID_TYPES=ON' }
- { name: 'asan', build: 'asan', components: 'core,test', cmake: '-DNANOVDB_USE_OPENVDB=ON -DOPENVDB_AX_STATIC=OFF -DOPENVDB_CORE_STATIC=OFF -DUSE_BLOSC=OFF' } # We never called blosc_destroy(), so disable blosc to silence these errors
- { name: 'ubsan', build: 'ubsan', components: 'core,test', cmake: '-DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" ' }
- { name: 'c++20', build: 'Release', components: 'core,test', cmake: '-DCMAKE_CXX_STANDARD=20' }
- { name: 'conf', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON' }
- { name: 'all', image: '2026', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DUSE_BLOSC=ON -DUSE_ZLIB=ON -DUSE_EXR=ON -DUSE_PNG=ON' }
- { name: 'lite', image: '2026', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DUSE_BLOSC=OFF -DUSE_ZLIB=OFF -DUSE_EXR=OFF -DUSE_PNG=OFF -DOPENVDB_USE_DELAYED_LOADING=OFF' }
- { name: 'half', image: '2026', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DUSE_BLOSC=OFF -DUSE_IMATH_HALF=ON' }
- { name: 'sse', image: '2026', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DOPENVDB_SIMD=SSE42' }
- { name: 'avx', image: '2026', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DOPENVDB_SIMD=AVX' }
- { name: 'pygrid', image: '2026', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DOPENVDB_PYTHON_WRAP_ALL_GRID_TYPES=ON' }
- { name: 'asan', image: '2026', build: 'asan', components: 'core,test', cmake: '-DDISABLE_DEPENDENCY_VERSION_CHECKS=ON -DNANOVDB_USE_OPENVDB=ON -DOPENVDB_AX_STATIC=OFF -DOPENVDB_CORE_STATIC=OFF -DUSE_BLOSC=OFF' } # We never called blosc_destroy(), so disable blosc to silence these errors
- { name: 'ubsan', image: '2026', build: 'ubsan', components: 'core,test', cmake: '-DDISABLE_DEPENDENCY_VERSION_CHECKS=ON -DCMAKE_CXX_FLAGS="-Wno-deprecated-declarations" ' }
- { name: 'c++20', image: '2026', build: 'Release', components: 'core,test', cmake: '-DCMAKE_CXX_STANDARD=20' }
- { name: 'conf', image: '2026', build: 'Release', components: 'core,python,bin,view,render,test', cmake: '-DCMAKE_FIND_PACKAGE_PREFER_CONFIG=ON' }
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: nanobind
#if: contains(container.image, '2023') == false
run: ./ci/install_nanobind.sh 2.0.0
run: ./ci/install_nanobind.sh 2.5.0
# 2026 images are broken with gtest/glfw/exr installs
- name: deps
if: contains(matrix.config.image, '2026')
run: |
./ci/install_gtest.sh latest
./ci/install_glfw.sh latest
./ci/install_imath.sh v3.2.2
./ci/install_exr.sh v3.2.2
- name: build
run: >
./ci/build.sh -v
@@ -192,21 +189,22 @@ jobs:
strategy:
matrix:
config:
- { runson: ubuntu-latest, cxx: g++, cmake: '' }
# llvm-17 is currently the default which has symbol issues with the python module
- { runson: ubuntu-latest, cxx: g++, cmake: '-DOPENVDB_PYTHON_USE_AX=OFF' }
# Disable the clang job for now. See https://github.com/actions/runner-images/issues/8659
# - { runson: ubuntu-latest, cxx: clang++, cmake: '' }
# @todo gcc on macos
- { runson: macos-latest, cxx: '', cmake: '-DCMAKE_CXX_COMPILER=/opt/homebrew/opt/llvm@15/bin/clang++ -DLLVM_DIR=/opt/homebrew/opt/llvm@15/lib/cmake/llvm' }
- { runson: macos-latest, cxx: '', cmake: '-DLLVM_DIR=/opt/homebrew/opt/llvm@20/lib/cmake/llvm' }
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: install_deps
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
sudo apt-get -q install -y libboost-dev libboost-iostreams-dev libtbb-dev libblosc-dev llvm-15-dev libgtest-dev libgmock-dev libcppunit-dev
./ci/install_nanobind.sh 2.0.0
sudo apt-get -q install -y libboost-dev libboost-iostreams-dev libtbb-dev libblosc-dev libgtest-dev libgmock-dev python3-numpy
./ci/install_nanobind.sh 2.5.0
elif [ "$RUNNER_OS" == "macOS" ]; then
./ci/install_macos.sh 15
./ci/install_macos.sh 20
./ci/install_tbb_macos.sh
else
echo "$RUNNER_OS not supported"; exit 1
@@ -216,7 +214,12 @@ jobs:
./ci/build.sh -v
--build-type=Release
--components=\"core,axcore,python,bin,render,test,axbin\"
--cargs=\"-DCMAKE_CXX_STANDARD=20 -DOPENVDB_USE_DELAYED_LOADING=OFF -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install ${{ matrix.config.cmake }}\"
--cargs=\'
-DCMAKE_CXX_STANDARD=20
-DOPENVDB_USE_DELAYED_LOADING=OFF
-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/install
${{ matrix.config.cmake }}
\'
- name: test
run: cd build && ctest -V
@@ -238,9 +241,9 @@ jobs:
# Boost as both shared and static libs are installed.
# USE_EXPLICIT_INSTANTIATION is disabled for debug static libraries
# due to disk space constraints
- { vc: 'x64-windows-static', components: 'core,bin,view,render,test', build: 'Release', cmake: '-A x64 -G \"Visual Studio 17 2022\" -DOPENVDB_CORE_SHARED=OFF -DUSE_STATIC_DEPENDENCIES=ON -DBLOSC_USE_EXTERNAL_SOURCES=ON' }
# - { vc: 'x64-windows-static', components: 'core,bin,view,render,test', build: 'Release', cmake: '-A x64 -G \"Visual Studio 17 2022\" -DOPENVDB_CORE_SHARED=OFF -DUSE_STATIC_DEPENDENCIES=ON -DBLOSC_USE_EXTERNAL_SOURCES=ON' }
- { vc: 'x64-windows', components: 'core,bin,view,render,python,test', build: 'Release', cmake: '-A x64 -G \"Visual Studio 17 2022\" -DOPENVDB_CORE_STATIC=OFF' }
- { vc: 'x64-windows', components: 'core,bin,view,render,python,test', build: 'Debug', cmake: '-A x64 -G \"Visual Studio 17 2022\" -DOPENVDB_CORE_STATIC=OFF' }
# - { vc: 'x64-windows', components: 'core,bin,view,render,python,test', build: 'Debug', cmake: '-A x64 -G \"Visual Studio 17 2022\" -DOPENVDB_CORE_STATIC=OFF' }
fail-fast: false
steps:
- uses: actions/checkout@v3
@@ -294,15 +297,14 @@ jobs:
strategy:
matrix:
config:
# Unified
- { image: '2023-clang15', cxx: 'clang++', build: 'Release', components: 'core,bin,axcore,axbin,axtest', cmake: '' }
- { image: '2023-clang15', cxx: 'g++', build: 'Release', components: 'core,bin,axcore,axbin,axtest', cmake: '' }
- { image: '2025-clang18.1', cxx: 'clang++', build: 'Release', components: 'core,axcore,axbin,axtest', cmake: '' }
# @note LLVM 17 builds are incompatible with the vdb python plugin on linux
- { image: '2024-clang17.2', cxx: 'clang++', build: 'Release', components: 'core,axcore,axbin,axtest', cmake: '-DOPENVDB_PYTHON_USE_AX=OFF' }
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: nanobind
#f: contains(matrix.config.image, '2023') == false
run: ./ci/install_nanobind.sh 2.0.0
run: ./ci/install_nanobind.sh 2.5.0
- name: build
run: >
./ci/build.sh -v
@@ -341,14 +343,14 @@ jobs:
github.event_name != 'workflow_dispatch' ||
github.event.inputs.type == 'all' ||
github.event.inputs.type == 'ax'
runs-on: macos-13
runs-on: ${{ matrix.config.runner }}
name: macos-cxx:${{ matrix.config.cxx }}-llvm:${{ matrix.config.llvm }}-${{ matrix.config.build }}
env:
CXX: ${{ matrix.config.cxx }}
strategy:
matrix:
config:
- { cxx: 'clang++', build: 'Release', llvm: '15' }
- { runner: 'macos-latest', cxx: 'clang++', build: 'Release', llvm: '20' }
fail-fast: false
steps:
- uses: actions/checkout@v3
@@ -378,7 +380,7 @@ jobs:
github.event.inputs.type == 'all' ||
github.event.inputs.type == 'ax'
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'windows-2022-8c-32g-300h') || 'windows-latest' }}
name: windows-vc:${{ matrix.config.vc }}-type:${{ matrix.config.build }}
name: ${{ matrix.config.vc }}-${{ matrix.config.crt }}-${{ matrix.config.build }}-llvm${{ matrix.config.llvm }}
env:
VCPKG_DEFAULT_TRIPLET: ${{ matrix.config.vc }}
# Export this with '' avoid bash treating \ as escape
@@ -392,17 +394,22 @@ jobs:
# @todo We don't currently run the axtests with shared builds of ax
# due to symbol issues using LLVM as a static lib (which is the only
# option on Windows).
- { vc: 'x64-windows', crt: 'MD', components: 'core,bin,axcore,axbin,python', build: 'Release', cmake: '-DOPENVDB_CORE_STATIC=OFF -DOPENVDB_AX_STATIC=OFF' }
- { vc: 'x64-windows-static', crt: 'MT', components: 'core,bin,axcore,axbin,axtest', build: 'Release', cmake: '-DOPENVDB_CORE_SHARED=OFF -DOPENVDB_AX_SHARED=OFF -DUSE_STATIC_DEPENDENCIES=ON -DBLOSC_USE_EXTERNAL_SOURCES=ON' }
- { vc: 'x64-windows-static', crt: 'MTd', components: 'core,bin,axcore,axbin,axtest', build: 'Debug', cmake: '-DOPENVDB_CORE_SHARED=OFF -DOPENVDB_AX_SHARED=OFF -DUSE_STATIC_DEPENDENCIES=ON -DBLOSC_USE_EXTERNAL_SOURCES=ON' }
# @note currently only test static builds with MT as building LLVM as
# a shared lib on windows is a bit dicey
- { vc: 'x64-windows-static', llvm: '18.1.0', crt: 'MultiThreaded', components: 'core,axcore,axbin,axtest', build: 'Release', cmake: '-DOPENVDB_CORE_SHARED=OFF -DOPENVDB_AX_SHARED=OFF -DUSE_STATIC_DEPENDENCIES=ON -DBLOSC_USE_EXTERNAL_SOURCES=ON' }
- { vc: 'x64-windows-static', llvm: '18.1.0', crt: 'MultiThreadedDebug', components: 'core,axcore,axbin,axtest', build: 'Debug', cmake: '-DOPENVDB_CORE_SHARED=OFF -DOPENVDB_AX_SHARED=OFF -DUSE_STATIC_DEPENDENCIES=ON -DBLOSC_USE_EXTERNAL_SOURCES=ON' }
- { vc: 'x64-windows', llvm: '18.1.0', crt: 'MultiThreadedDLL', components: 'core,axcore,axbin', build: 'Release', cmake: '-DOPENVDB_CORE_STATIC=OFF -DOPENVDB_AX_STATIC=OFF' }
- { vc: 'x64-windows-static', llvm: '15.0.0', crt: 'MultiThreaded', components: 'core,axcore,axbin,axtest', build: 'Release', cmake: '-DOPENVDB_CORE_SHARED=OFF -DOPENVDB_AX_SHARED=OFF -DUSE_STATIC_DEPENDENCIES=ON -DBLOSC_USE_EXTERNAL_SOURCES=ON' }
fail-fast: false
steps:
- uses: actions/checkout@v3
- name: llvm
run: ./ci/install_llvm_windows.sh ${{ matrix.config.crt }}
run: ./ci/install_llvm_windows.sh ${{ matrix.config.llvm }} ${{ matrix.config.crt }}
- name: install
shell: powershell
run: .\ci\install_windows.ps1
run: |
vcpkg update
vcpkg install tbb gtest
- name: build
run: >
./ci/build.sh -v
@@ -410,10 +417,13 @@ jobs:
--components="${{ matrix.config.components }}"
--cargs=\'
-A x64 -G \"Visual Studio 17 2022\"
-DOPENVDB_USE_DELAYED_LOADING=OFF
-DUSE_BLOSC=OFF \
-DUSE_ZLIB=OFF \
-DVCPKG_TARGET_TRIPLET=${VCPKG_DEFAULT_TRIPLET}
-DCMAKE_TOOLCHAIN_FILE=\"${VCPKG_INSTALLATION_ROOT}\\scripts\\buildsystems\\vcpkg.cmake\"
-DMSVC_COMPRESS_PDB=ON
-DOPENVDB_AX_TEST_CMD_DOWNLOADS=ON
-DOPENVDB_AX_TEST_CMD_DOWNLOADS=OFF
-DUSE_EXPLICIT_INSTANTIATION=OFF
-DLLVM_DIR=\"${HOME}\\llvm_install\\lib\\cmake\\llvm\"
-DCMAKE_INSTALL_PREFIX=\"${VDB_INSTALL_PREFIX}\"
@@ -436,7 +446,7 @@ jobs:
if: |
github.event_name != 'workflow_dispatch' ||
github.event.inputs.type == 'all' ||
github.event.inputs.type == 'win'
github.event.inputs.type == 'winnano'
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'windows-2022-8c-32g-300h') || 'windows-latest' }}
env:
VCPKG_DEFAULT_TRIPLET: ${{ matrix.config.vc }}
@@ -499,10 +509,10 @@ jobs:
runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'ubuntu-22.04-8c-32g-300h') || 'ubuntu-latest' }}
name: linux-blosc:${{ matrix.blosc }}
container:
image: aswf/ci-base:2023
image: aswf/ci-openvdb:2025-clang19.1
strategy:
matrix:
blosc: ['1.18.0','1.19.0','1.20.0','1.21.0']
blosc: ['1.21.0', 'latest']
fail-fast: false
steps:
- uses: actions/checkout@v3
+153 -6
View File
@@ -1,18 +1,163 @@
OpenVDB Version History
=======================
Version 12.0.2 - In development
Version 12.1.2 - In development
Version 12.1.1 - September 30, 2025
OpenVDB AX:
Fixes:
- Fixed an issue which was causing host specific optimization passes to be
omitted when using LLVM 16 and higher.
Houdini:
- Fixed a compilation issue with the OpenVDB Activate SOP with Houdini 21
[Reported by @callen128]
Version 12.1.0 - August 8, 2025
OpenVDB:
New features:
- New level set constructor tools::createLevelSetCapsule creates a grid
containing a narrow-band level set representation of a capsule (tube
with constant radius and sphere caps).
- New level set constructor tools::createLevelSetTaperedCapsule creates a
grid containing a narrow-band level set representation of a tapered
capsule (tube with sphere caps and different radii at both ends, or
equivalently the convex hull of two spheres with possibly different
centers and radii).
- New level set constructor tools::createLevelSetTubeComplex creates a grid
containing a narrow-band level set representation of a tube complex (a
collection of capsules or tapered capsules defined by endpoint coordinates
and segment indices).
- New level set constructor tools::createLevelSetDilatedMesh creates a grid
containing a narrow-band level set representation of a dilated surface
mesh (dilated by a radius in all directions).
- New class tools::ConvexVoxelizer, used by the above methods, which can be
inherited to implement a convex level set constructor.
- Introduced anisotropic surfacing tools in points/PointRasterizeSDF.h and
principal component analysis (PCA) methods in points/PrincipalComponentAnalysis.h.
The latter tool analyses point neighborhoods and computes affine
transformations representing elliptical distributions. The prior tool
takes rotational and stretch point attributes (optionally computed from
the prior PCA methods) and generates anisotropic surfaces.
Improvements:
- Significantly improved the performance of all LeafNode ValueIterators,
up to 5x on some platforms and up to 10x when delay loading is enabled.
Construction of a ValueIterator from a leaf node now requests the leaf
buffers ahead of iteration to avoid potentially expensive API calls.
- Improved the performance of PointRasterizeSDF.h tools with larger radii
with better bound computation approximations.
- Significant performance improvement to delay loaded files - the warning
logic which checks the underlying mapped file for the last modified time
has been removed, resulting in performance gains upwards of 10x when
loading in node data buffers. Note that the check that was removed is
entirely redundant on Windows and would only print a warning on other
system.
[Contributed by jjtParadox]
- Added OPENVDB_HAS_INVOKABLE_MEMBER_FUNCTION and OPENVDB_HAS_MEMBER_FUNCTION
macros to help determine the presence and compatibility of interface
member functions at compile time.
- Added OPENVDB_ASSUME macros to mimic builtin assume and C++23 assume
attributes.
- Improved read and write of PLY files with vdb_tool so they support Vec3d
and 64 bit attributes. Changed vdb_tool version number from 10.6.1 to
10.7.0.
API Changes:
- Mat3 and Mat4 functions have been marked [[nodiscard]] where it may be
confusing if they are operating in place or returning a result, in
particular tranpose, invert, adjoint, and timesDiagonal.
- Re-worked the API in PointRasterizeSDF.h with the addition of anisotropic
surfacing kernels. The new API exposes more concise free functions which
take structs of parameters. The old API remains supported but is
deprecated.
- Deprecated providing a filter or interrupter as arguments to
points::rasterize. These can instead be implemented on user transfer
schemes.
- Transfer schemes provided to points::rasterize can now optionally
implement a rasterizePoints callback that takes a range of points per
voxel.
Fixes:
- Fixed a bug in points::rasterizeSpheres and points::rasterizeSmoothSpheres
which could cause sections of the generated surface to incorrectly be
marked as negative interior values.
- Fixed a bug in points::rasterizeSmoothSpheres which could result in
incomplete surface reconstruction with significantly larger search
distances.
- Fixed some precision issues in various Matrix methods.
- Fixed bugs in vdb_tool that prevented build with newer namespaces in
nanovdb.
OpenVDB AX:
Improvements:
- Added support for LLVM versions greater than 15. Upgraded CI to test with
LLVM versions 16, 17, 18, 19 and 20.
- As of this release, LLVM 15 is the minimum required version for OpenVDB
AX. Note that there is an issue with LLVM 17 symbols which makes it
incompatible with the OpenVDB Python plugin. AS such, LLVM version 18 and
greater are the recommended LLVM versions to use, with LLVM 18 being the
new future minimum version.
- Significant performance improvement to the compilation of AX kernels when
using LLVM 18 and greater.
- Significant backend refactoring to AX IR generation to support newer
versions of LLVM. In particular, parts of the codegen::Function and
codegen::FunctionBuilder API require new usage from LLVM 16 due to
the LLVM opaque pointer changes. If you're not using the OpenVDB AX
Function codegen API then no client changes should be necessary.
- Migrated OpenVDB AX unit tests from cppunit to gtest
[Contributed by Tim Straubinger]
Fixes:
- Fixed a case of undefined behaviour when explicitly casting variables to
the same type as their storage type e.g:
double a; double b = double(a);
- Fixed an AX compiler error which would produce invalid IR when using 64bit
integer variables for matrix array offsetting e.g:
int64 a = 0; mat4f b = 1; float c = b[a,a];
NanoVDB:
- Added nanovdb::openToIndexVDB, which can convert an OpenVDB grid into a
NanoVDB IndexGrid.
- Added new options "--index" and "--onIndex" to the command-line tool
nanovdb_convert so it can produce IndexGrids.
- Fixed memory leak in DeviceBuffer.
- Added constructor for DeviceBuffer from an existing HostBuffer.
- Added VoxelBlockManager, an acceleration structure for sequential access
and stencil ops over active voxels.
- Added helper functions for kernel launches with shared memory.
- Added support for CUDA 13.0.
- Fix warnings and compilation errors for GCC and Clang.
- Fixed inconsistent usage of util::cuda::mallocAsync/freeAsync that
affected vGPUs.
- Added support for custom memory resources in PointsToGrid.
- Fixed race condition in split/mergeGridHandles.
- Fixed offset bug in nanovdb::tools::cuda::IndexToGrid related to non-POD
build types.
[Contributed by Andreas Henne]
Houdini:
- Define OP_InputIdx and OP_OutputIdx for Houdini 21.0 compatibility
- Add Houdini 21 style camera interface to support LOP cameras
- Include env flag for avoiding stream writing (was backported to 19.5)
Version 12.0.1 - April 3, 2025
OpenVDB:
New features:
- Added read and write support for OFF (Object File Format) files to vdb_tool
- Added read and write support for OFF (Object File Format) files to
vdb_tool
Build:
- Fixed a build issue with vdb_render on Windows with newer versions of glew not including glu.h by default
- Fixed issues with newer versions of clang refusing to accept older ::template syntax
- Fixed a build issue with vdb_render on Windows with newer versions of
glew not including glu.h by default
- Fixed issues with newer versions of clang refusing to accept older
::template syntax
NanoVDB:
New features:
@@ -20,10 +165,12 @@ Version 12.0.1 - April 3, 2025
- Added a UnifiedBuffer class that wraps CUDA unified memory
- Added example for multiGPU sparse convolution
- Added CUDA utility functions for device queries
- Added functions to independently stop and compute the elapsed time for timer classes
- Added functions to independently stop and compute the elapsed time for
timer classes
Improvements:
- Replaced CUB's CachingDeviceAllocator with the default asynchronous stream ordered allocator in PointsToGrid for improved performance
- Replaced CUB's CachingDeviceAllocator with the default asynchronous
stream ordered allocator in PointsToGrid for improved performance
Bug fix:
- Map is now properly transposed when converting NanoVDB to OpenVDB.
+23 -5
View File
@@ -42,7 +42,7 @@
# note: cmake_minimum_required must be called before project commands to
# ensure policy scope is set up correctly
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
# CMP0091 allows for MSVC ABI targetting via CMAKE_MSVC_RUNTIME_LIBRARY
# from CMake 3.15 and above. Must come before project().
@@ -53,7 +53,7 @@ endif()
###### Version
set(OpenVDB_MAJOR_VERSION 12)
set(OpenVDB_MINOR_VERSION 0)
set(OpenVDB_MINOR_VERSION 1)
set(OpenVDB_PATCH_VERSION 2)
set(OpenVDB_VERSION "${OpenVDB_MAJOR_VERSION}.${OpenVDB_MINOR_VERSION}.${OpenVDB_PATCH_VERSION}")
@@ -268,6 +268,13 @@ mark_as_advanced(
include(cmake/config/OpenVDBVersions.cmake)
if(OPENVDB_FUTURE_DEPRECATION AND FUTURE_MINIMUM_CMAKE_VERSION)
if(CMAKE_VERSION VERSION_LESS ${FUTURE_MINIMUM_CMAKE_VERSION})
message(DEPRECATION "Support for CMake versions < ${FUTURE_MINIMUM_CMAKE_VERSION} "
"is deprecated and will be removed.")
endif()
endif()
foreach(DEPRECATED_ABI ${OPENVDB_DEPRECATED_ABI_LIST})
option(OPENVDB_USE_DEPRECATED_ABI_${DEPRECATED_ABI} "Bypass minimum ABI requirement checks" OFF)
mark_as_advanced(OPENVDB_USE_DEPRECATED_ABI_${DEPRECATED_ABI})
@@ -478,10 +485,7 @@ endif()
# Locate Python and nanobind if necessary
if(OPENVDB_BUILD_PYTHON_MODULE OR (OPENVDB_BUILD_NANOVDB AND NANOVDB_BUILD_PYTHON_MODULE))
# Call find_package(Python ...)
find_package(Python ${MINIMUM_PYTHON_VERSION} REQUIRED COMPONENTS Development Interpreter)
find_package(nanobind ${MINIMUM_NANOBIND_VERSION} REQUIRED)
if(OPENVDB_FUTURE_DEPRECATION AND FUTURE_MINIMUM_PYTHON_VERSION)
if(Python_VERSION VERSION_LESS ${FUTURE_MINIMUM_PYTHON_VERSION})
message(DEPRECATION "Support for Python versions < ${FUTURE_MINIMUM_GLFW_VERSION} "
@@ -489,6 +493,20 @@ if(OPENVDB_BUILD_PYTHON_MODULE OR (OPENVDB_BUILD_NANOVDB AND NANOVDB_BUILD_PYTHO
endif()
endif()
find_package(nanobind REQUIRED CONFIG)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(nanobind
FOUND_VAR nanobind_FOUND
REQUIRED_VARS NB_DIR
VERSION_VAR nanobind_VERSION)
if(OPENVDB_FUTURE_DEPRECATION AND FUTURE_MINIMUM_NANOBIND_VERSION)
if(nanobind_VERSION VERSION_LESS ${FUTURE_MINIMUM_NANOBIND_VERSION})
message(DEPRECATION "Support for nanobind versions < ${FUTURE_MINIMUM_NANOBIND_VERSION} "
"is deprecated and will be removed.")
endif()
endif()
if(NOT DEFINED VDB_PYTHON_INSTALL_DIRECTORY)
if(DEFINED Python_SITELIB)
get_filename_component(Python_PACKAGES_DIR ${Python_SITELIB} NAME)
+16 -9
View File
@@ -33,8 +33,13 @@ PARMS[--target]=install
PARMS[--build-dir]=build
if [[ $RUNNER_NAME == *"8c-32g-300h"* ]]; then
# ASWF github actions runners have 8 threads
PARMS[-j]=8
if [[ $CXX == "g++" ]]; then
# GCC hits memory limits on runners, reduce threads
PARMS[-j]=6
else
# ASWF github actions runners have 8 threads
PARMS[-j]=8
fi
else
# Github actions runners have 2 threads
# https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners
@@ -51,7 +56,8 @@ declare -A COMPONENTS
COMPONENTS['core']='OPENVDB_BUILD_CORE'
COMPONENTS['python']='OPENVDB_BUILD_PYTHON_MODULE'
COMPONENTS['test']='OPENVDB_BUILD_UNITTESTS'
COMPONENTS['bin']='OPENVDB_BUILD_BINARIES'
# @todo split out vdb tool from this...
COMPONENTS['bin']='OPENVDB_BUILD_VDB_PRINT,OPENVDB_BUILD_VDB_LOD,OPENVDB_BUILD_VDB_TOOL,OPENVDB_BUILD_VDB_TOOL_UNITTESTS'
COMPONENTS['view']='OPENVDB_BUILD_VDB_VIEW'
COMPONENTS['render']='OPENVDB_BUILD_VDB_RENDER'
COMPONENTS['hou']='OPENVDB_BUILD_HOUDINI_PLUGIN'
@@ -150,7 +156,11 @@ for comp in "${!COMPONENTS[@]}"; do
setting="ON"; break
fi
done
CMAKE_EXTRA+=("-D${COMPONENTS[$comp]}=$setting")
OPENVDB_TOGGLES="${COMPONENTS[$comp]}"
OPENVDB_TOGGLES=(${OPENVDB_TOGGLES//,/ })
for toggle in "${!OPENVDB_TOGGLES[@]}"; do
CMAKE_EXTRA+=("-D${OPENVDB_TOGGLES[$toggle]}=$setting")
done
done
################################################
@@ -182,17 +192,14 @@ cd ${BUILD_DIR}
set -x
# Note:
# - print and lod binary options are always on and can be toggles with: OPENVDB_BUILD_BINARIES=ON/OFF
# - OPENVDB_BUILD_BINARIES is always ON, but each binary will be toggled depending on the provided comps
# - always enabled the python tests with OPENVDB_BUILD_PYTHON_UNITTESTS if the python module is in use,
# regardless of the 'test' component being enabled or not (see the OPENVDB_BUILD_PYTHON_UNITTESTS option).
cmake \
-DOPENVDB_USE_DEPRECATED_ABI_10=ON \
-DOPENVDB_USE_DEPRECATED_ABI_11=ON \
-DOPENVDB_BUILD_VDB_PRINT=ON \
-DOPENVDB_BUILD_VDB_LOD=ON \
-DOPENVDB_BUILD_VDB_TOOL=ON \
-DOPENVDB_BUILD_VDB_TOOL_UNITTESTS=ON \
-DOPENVDB_TOOL_USE_NANO=OFF \
-DOPENVDB_BUILD_BINARIES=ON \
-DOPENVDB_BUILD_PYTHON_UNITTESTS=ON \
-DMSVC_MP_THREAD_COUNT=${PARMS[-j]} \
"${CMAKE_EXTRA[@]}" \
+22 -16
View File
@@ -8,8 +8,14 @@ HOUDINI_MAJOR="$1"
PLATFORM="$2"
OTHER_ARGS="$3"
pip install --user requests
python ci/download_houdini.py $HOUDINI_MAJOR $PLATFORM $OTHER_ARGS
if [[ $PLATFORM =~ "linux" ]]; then
pip3 install --user requests
elif [[ $PLATFORM =~ "macos" ]]; then
# Tell the homebrew install of pip3 that we don't care about installing directly through pip3
pip3 install --user requests --break-system-packages
fi
python3 ci/download_houdini.py $HOUDINI_MAJOR $PLATFORM $OTHER_ARGS
if [[ $PLATFORM =~ "linux" ]]; then
# create dir hierarchy
@@ -31,20 +37,20 @@ if [[ $PLATFORM =~ "linux" ]]; then
ls -al dsolib/
# copy required libraries
cp -r toolkit/cmake ../hou/toolkit/.
cp -r toolkit/include ../hou/toolkit/.
cp -r dsolib/libHoudini* ../hou/dsolib/.
cp -r dsolib/libopenvdb_sesi* ../hou/dsolib/.
cp -r dsolib/libblosc* ../hou/dsolib/.
cp -r dsolib/libhboost* ../hou/dsolib/.
cp -r dsolib/libz* ../hou/dsolib/.
cp -r dsolib/libbz2* ../hou/dsolib/.
cp -r dsolib/libtbb* ../hou/dsolib/.
cp -r dsolib/libjemalloc* ../hou/dsolib/.
cp -r dsolib/liblzma* ../hou/dsolib/.
cp -r dsolib/libIex* ../hou/dsolib/.
cp -r dsolib/libImath* ../hou/dsolib/.
cp -r dsolib/libIlmThread* ../hou/dsolib/.
mv toolkit/cmake ../hou/toolkit/.
mv toolkit/include ../hou/toolkit/.
mv dsolib/libHoudini* ../hou/dsolib/.
mv dsolib/libopenvdb_sesi* ../hou/dsolib/.
mv dsolib/libblosc* ../hou/dsolib/.
mv dsolib/libhboost* ../hou/dsolib/.
mv dsolib/libz* ../hou/dsolib/.
mv dsolib/libbz2* ../hou/dsolib/.
mv dsolib/libtbb* ../hou/dsolib/.
mv dsolib/libjemalloc* ../hou/dsolib/.
mv dsolib/liblzma* ../hou/dsolib/.
mv dsolib/libIex* ../hou/dsolib/.
mv dsolib/libImath* ../hou/dsolib/.
mv dsolib/libIlmThread* ../hou/dsolib/.
cd ..
elif [[ $PLATFORM =~ "macos" ]]; then
+1 -1
View File
@@ -15,6 +15,6 @@ fi
mkdir build
cd build
cmake ../.
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ../.
make -j8
make install
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Copyright Contributors to the OpenVDB Project
# SPDX-License-Identifier: Apache-2.0
set -ex
EXR_VERSION="$1"
git clone https://github.com/AcademySoftwareFoundation/openexr.git
cd openexr
if [ "$EXR_VERSION" != "latest" ]; then
git checkout tags/${EXR_VERSION} -b ${EXR_VERSION}
fi
mkdir build
cd build
cmake ..
make -j$(nproc)
sudo make install
+7 -4
View File
@@ -3,16 +3,19 @@
# SPDX-License-Identifier: Apache-2.0
set -ex
GTEST_VERSION="$1"
git clone https://github.com/google/googletest.git -b v${GTEST_VERSION}
git clone https://github.com/google/googletest.git
cd googletest
if [ "$GTEST_VERSION" != "latest" ]; then
git checkout tags/${GTEST_VERSION} -b ${GTEST_VERSION}
fi
mkdir build
cd build
cmake ..
make -j$(nproc)
sudo make install
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Copyright Contributors to the OpenVDB Project
# SPDX-License-Identifier: Apache-2.0
set -ex
IMATH_VERSION="$1"
git clone https://github.com/AcademySoftwareFoundation/Imath.git
cd Imath
if [ "$IMATH_VERSION" != "latest" ]; then
git checkout tags/${IMATH_VERSION} -b ${IMATH_VERSION}
fi
mkdir build
cd build
cmake ..
make -j$(nproc)
sudo make install
+35 -6
View File
@@ -4,26 +4,55 @@
set -ex
LLVM_CRT="$1"
LLVM_VER="$1"
LLVM_CRT="$2"
git clone -b llvmorg-14.0.0 --depth 1 https://github.com/llvm/llvm-project.git llvm
# Legacy define for older versions of LLVM
LLVM_LEGACY_CRT_DEFINE=""
if [[ "${LLVM_CRT}" == "MultiThreaded" ]]; then
LLVM_LEGACY_CRT_DEFINE="MT"
elif [[ "${LLVM_CRT}" == "MultiThreadedDebug" ]]; then
LLVM_LEGACY_CRT_DEFINE="MTd"
elif [[ "${LLVM_CRT}" == "MultiThreadedDLL" ]]; then
LLVM_LEGACY_CRT_DEFINE="MD"
elif [[ "${LLVM_CRT}" == "MultiThreadedDebugDLL" ]]; then
LLVM_LEGACY_CRT_DEFINE="MDd"
fi
git clone -b llvmorg-${LLVM_VER} --depth 1 https://github.com/llvm/llvm-project.git llvm
cd llvm
mkdir .build
cd .build
# @note currently only test static builds with MT
cmake -B . -S ../llvm -A x64 -G "Visual Studio 17 2022" -Thost=x64 \
-DCMAKE_INSTALL_PREFIX="${HOME}/llvm_install" \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_ASSERTIONS=OFF \
-DLLVM_ENABLE_PROJECTS="" \
-DLLVM_ENABLE_RUNTIMES="" \
-DLLVM_ENABLE_UNWIND_TABLES=OFF \
-DLLVM_TARGETS_TO_BUILD="host" \
-DLLVM_BUILD_TOOLS=OFF \
-DLLVM_INCLUDE_BENCHMARKS=OFF \
-DLLVM_INCLUDE_EXAMPLES=OFF \
-DLLVM_INCLUDE_TESTS=OFF \
-DLLVM_INCLUDE_DOCS=OFF \
-DLLVM_INCLUDE_TOOLS=OFF \
-DLLVM_USE_CRT_RELEASE=${LLVM_CRT}
-DLLVM_INCLUDE_UTILS=OFF \
-DLLVM_BUILD_BENCHMARKS=OFF \
-DLLVM_BUILD_EXAMPLES=OFF \
-DLLVM_BUILD_TESTS=OFF \
-DLLVM_BUILD_DOCS=OFF \
-DLLVM_BUILD_TOOLS=OFF \
-DLLVM_BUILD_UTILS=OFF \
-DLLVM_ENABLE_BINDINGS=OFF \
-DLLVM_ENABLE_OCAMLDOC=OFF \
-DLLVM_PARALLEL_COMPILE_JOBS=4 \
-DLLVM_PARALLEL_LINK_JOBS=4 \
-DCMAKE_MSVC_RUNTIME_LIBRARY=${LLVM_CRT} \
-DLLVM_USE_CRT_RELEASE=${LLVM_LEGACY_CRT_DEFINE}
cmake --build . --config Release --target install
cmake --build . --parallel 4 --config Release --target install
cd ..
+1 -2
View File
@@ -22,6 +22,7 @@ brew install jq # for trivial parsing of brew json
brew install openexr
brew install nanobind # also installs the dependent python version
brew install robin-map # required for nanobind
brew install numpy # only for python tests
brew install zlib
brew install jemalloc
@@ -45,9 +46,7 @@ LLVM_VERSION=$1
if [ ! -z "$LLVM_VERSION" ]; then
if [ "$LLVM_VERSION" == "latest" ]; then
brew install llvm
brew install cppunit
else
brew install llvm@$LLVM_VERSION
brew install cppunit
fi
fi
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Copyright Contributors to the OpenVDB Project
# SPDX-License-Identifier: Apache-2.0
set -ex
# Download the TeX Live installer
wget https://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz
# Extract
tar xzf install-tl-unx.tar.gz
rm install-tl-unx.tar.gz
cd install-tl-*
# Run the installer with no docs or source installation
./install-tl --no-interaction --no-doc-install --no-src-install --scheme=minimal
# Export the texlive PATH to GITHUB_PATH
TEXLIVE_PATH=/usr/local/texlive/2025/bin/x86_64-linux
export PATH=${TEXLIVE_PATH}:${PATH}
echo "${TEXLIVE_PATH}" >> $GITHUB_PATH
# Install the required packages
tlmgr install latex-bin dvips collection-fontsrecommended collection-latexrecommended
# Validate that installing dvips and latex has been successful
dvips --version
latex --version
+1 -1
View File
@@ -10,7 +10,7 @@ set -e
# the expected VDB installation
cmakelists="
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
project(TestInstall LANGUAGES CXX)
find_package(OpenVDB REQUIRED COMPONENTS openvdb)
add_executable(test_vdb_print \"../openvdb_cmd/vdb_print/main.cc\")
+24
View File
@@ -0,0 +1,24 @@
# SPDX-License-Identifier: MIT
#
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
set(CPM_DOWNLOAD_VERSION 0.40.2)
set(CPM_HASH_SUM "c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d")
if(CPM_SOURCE_CACHE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
else()
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
endif()
# Expand relative path. This is important if the provided path contains a tilde (~)
get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE)
file(DOWNLOAD
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
)
include(${CPM_DOWNLOAD_LOCATION})
+1 -1
View File
@@ -88,7 +88,7 @@ may be provided to tell this module where to look.
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
include(GNUInstallDirs)
mark_as_advanced(
-320
View File
@@ -1,320 +0,0 @@
# Copyright Contributors to the OpenVDB Project
# SPDX-License-Identifier: Apache-2.0
#
#[=======================================================================[.rst:
FindCppUnit
-----------
Find CppUnit include dirs and libraries
Use this module by invoking find_package with the form::
find_package(CppUnit
[version] [EXACT] # Minimum or EXACT version
[REQUIRED] # Fail with error if CppUnit is not found
)
IMPORTED Targets
^^^^^^^^^^^^^^^^
``CppUnit::cppunit``
This module defines IMPORTED target CppUnit::cppunit, if CppUnit has been
found.
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables:
``CppUnit_FOUND``
True if the system has the CppUnit library.
``CppUnit_VERSION``
The version of the CppUnit library which was found.
``CppUnit_INCLUDE_DIRS``
Include directories needed to use CppUnit.
``CppUnit_RELEASE_LIBRARIES``
Libraries needed to link to the release version of CppUnit.
``CppUnit_RELEASE_LIBRARY_DIRS``
CppUnit release library directories.
``CppUnit_DEBUG_LIBRARIES``
Libraries needed to link to the debug version of CppUnit.
``CppUnit_DEBUG_LIBRARY_DIRS``
CppUnit debug library directories.
Deprecated - use [RELEASE|DEBUG] variants:
``CppUnit_LIBRARIES``
Libraries needed to link to CppUnit.
``CppUnit_LIBRARY_DIRS``
CppUnit library directories.
Cache Variables
^^^^^^^^^^^^^^^
The following cache variables may also be set:
``CppUnit_INCLUDE_DIR``
The directory containing ``cppunit/Portability.h``.
``CppUnit_LIBRARY``
The path to the CppUnit library.
Hints
^^^^^
Instead of explicitly setting the cache variables, the following variables
may be provided to tell this module where to look.
``CppUnit_ROOT``
Preferred installation prefix.
``CPPUNIT_INCLUDEDIR``
Preferred include directory e.g. <prefix>/include
``CPPUNIT_LIBRARYDIR``
Preferred library directory e.g. <prefix>/lib
``CPPUNIT_DEBUG_SUFFIX``
Suffix of the debug version of CppUnit libs. Defaults to "_d".
``SYSTEM_LIBRARY_PATHS``
Global list of library paths intended to be searched by and find_xxx call
``CPPUNIT_USE_STATIC_LIBS``
Only search for static cppunit libraries
``DISABLE_CMAKE_SEARCH_PATHS``
Disable CMakes default search paths for find_xxx calls in this module
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
include(GNUInstallDirs)
mark_as_advanced(
CppUnit_INCLUDE_DIR
CppUnit_LIBRARY
)
set(_FIND_CPPUNIT_ADDITIONAL_OPTIONS "")
if(DISABLE_CMAKE_SEARCH_PATHS)
set(_FIND_CPPUNIT_ADDITIONAL_OPTIONS NO_DEFAULT_PATH)
endif()
# Set _CPPUNIT_ROOT based on a user provided root var. Xxx_ROOT and ENV{Xxx_ROOT}
# are prioritised over the legacy capitalized XXX_ROOT variables for matching
# CMake 3.12 behaviour
# @todo deprecate -D and ENV CPPUNIT_ROOT from CMake 3.12
if(CppUnit_ROOT)
set(_CPPUNIT_ROOT ${CppUnit_ROOT})
elseif(DEFINED ENV{CppUnit_ROOT})
set(_CPPUNIT_ROOT $ENV{CppUnit_ROOT})
elseif(CPPUNIT_ROOT)
set(_CPPUNIT_ROOT ${CPPUNIT_ROOT})
elseif(DEFINED ENV{CPPUNIT_ROOT})
set(_CPPUNIT_ROOT $ENV{CPPUNIT_ROOT})
endif()
# Additionally try and use pkconfig to find cppunit
if(USE_PKGCONFIG)
if(NOT DEFINED PKG_CONFIG_FOUND)
find_package(PkgConfig)
endif()
pkg_check_modules(PC_CppUnit QUIET cppunit)
endif()
# ------------------------------------------------------------------------
# Search for CppUnit include DIR
# ------------------------------------------------------------------------
set(_CPPUNIT_INCLUDE_SEARCH_DIRS "")
list(APPEND _CPPUNIT_INCLUDE_SEARCH_DIRS
${CPPUNIT_INCLUDEDIR}
${_CPPUNIT_ROOT}
${PC_CppUnit_INCLUDEDIR}
${SYSTEM_LIBRARY_PATHS}
)
# Look for a standard cppunit header file.
find_path(CppUnit_INCLUDE_DIR cppunit/Portability.h
${_FIND_CPPUNIT_ADDITIONAL_OPTIONS}
PATHS ${_CPPUNIT_INCLUDE_SEARCH_DIRS}
PATH_SUFFIXES ${CMAKE_INSTALL_INCLUDEDIR} include
)
if(EXISTS "${CppUnit_INCLUDE_DIR}/cppunit/Portability.h")
file(STRINGS "${CppUnit_INCLUDE_DIR}/cppunit/Portability.h"
_cppunit_version_string REGEX "#define CPPUNIT_VERSION "
)
string(REGEX REPLACE "#define CPPUNIT_VERSION +\"(.+)\".*$" "\\1"
_cppunit_version_string "${_cppunit_version_string}"
)
string(STRIP "${_cppunit_version_string}" CppUnit_VERSION)
unset(_cppunit_version_string )
endif()
# ------------------------------------------------------------------------
# Search for CppUnit lib DIR
# ------------------------------------------------------------------------
if(NOT DEFINED CPPUNIT_DEBUG_SUFFIX)
set(CPPUNIT_DEBUG_SUFFIX "d")
endif()
set(_CPPUNIT_LIBRARYDIR_SEARCH_DIRS "")
list(APPEND _CPPUNIT_LIBRARYDIR_SEARCH_DIRS
${CPPUNIT_LIBRARYDIR}
${_CPPUNIT_ROOT}
${PC_CppUnit_LIBDIR}
${SYSTEM_LIBRARY_PATHS}
)
# Library suffix handling
set(_CPPUNIT_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
if(MSVC)
if(CPPUNIT_USE_STATIC_LIBS)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib")
else()
list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES "_dll.lib")
endif()
else()
if(CPPUNIT_USE_STATIC_LIBS)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
endif()
endif()
list(APPEND CPPUNIT_BUILD_TYPES RELEASE DEBUG)
foreach(BUILD_TYPE ${CPPUNIT_BUILD_TYPES})
set(_CPPUNIT_LIB_NAME cppunit)
if(BUILD_TYPE STREQUAL DEBUG)
set(_CPPUNIT_LIB_NAME "${_CPPUNIT_LIB_NAME}${CPPUNIT_DEBUG_SUFFIX}")
endif()
# Find the lib
find_library(CppUnit_LIBRARY_${BUILD_TYPE} ${_CPPUNIT_LIB_NAME}
${_FIND_CPPUNIT_ADDITIONAL_OPTIONS}
PATHS ${_CPPUNIT_LIBRARYDIR_SEARCH_DIRS}
PATH_SUFFIXES ${CMAKE_INSTALL_LIBDIR} lib64 lib
)
endforeach()
# Reset library suffix
set(CMAKE_FIND_LIBRARY_SUFFIXES ${_CPPUNIT_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
unset(_CPPUNIT_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES)
if(CppUnit_LIBRARY_DEBUG AND CppUnit_LIBRARY_RELEASE)
# if the generator is multi-config or if CMAKE_BUILD_TYPE is set for
# single-config generators, set optimized and debug libraries
get_property(_isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(_isMultiConfig OR CMAKE_BUILD_TYPE)
set(CppUnit_LIBRARY optimized ${CppUnit_LIBRARY_RELEASE} debug ${CppUnit_LIBRARY_DEBUG})
else()
# For single-config generators where CMAKE_BUILD_TYPE has no value,
# just use the release libraries
set(CppUnit_LIBRARY ${CppUnit_LIBRARY_RELEASE})
endif()
# FIXME: This probably should be set for both cases
set(CppUnit_LIBRARIES optimized ${CppUnit_LIBRARY_RELEASE} debug ${CppUnit_LIBRARY_DEBUG})
endif()
# if only the release version was found, set the debug variable also to the release version
if(CppUnit_LIBRARY_RELEASE AND NOT CppUnit_LIBRARY_DEBUG)
set(CppUnit_LIBRARY_DEBUG ${CppUnit_LIBRARY_RELEASE})
set(CppUnit_LIBRARY ${CppUnit_LIBRARY_RELEASE})
set(CppUnit_LIBRARIES ${CppUnit_LIBRARY_RELEASE})
endif()
# if only the debug version was found, set the release variable also to the debug version
if(CppUnit_LIBRARY_DEBUG AND NOT CppUnit_LIBRARY_RELEASE)
set(CppUnit_LIBRARY_RELEASE ${CppUnit_LIBRARY_DEBUG})
set(CppUnit_LIBRARY ${CppUnit_LIBRARY_DEBUG})
set(CppUnit_LIBRARIES ${CppUnit_LIBRARY_DEBUG})
endif()
# If the debug & release library ends up being the same, omit the keywords
if("${CppUnit_LIBRARY_RELEASE}" STREQUAL "${CppUnit_LIBRARY_DEBUG}")
set(CppUnit_LIBRARY ${CppUnit_LIBRARY_RELEASE} )
set(CppUnit_LIBRARIES ${CppUnit_LIBRARY_RELEASE} )
endif()
if(CppUnit_LIBRARY)
set(CppUnit_FOUND TRUE)
else()
set(CppUnit_FOUND FALSE)
endif()
# ------------------------------------------------------------------------
# Cache and set CppUnit_FOUND
# ------------------------------------------------------------------------
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(CppUnit
FOUND_VAR CppUnit_FOUND
REQUIRED_VARS
CppUnit_LIBRARY
CppUnit_INCLUDE_DIR
VERSION_VAR CppUnit_VERSION
)
if(NOT CppUnit_FOUND)
if(CppUnit_FIND_REQUIRED)
message(FATAL_ERROR "Unable to find CppUnit")
endif()
return()
endif()
# Partition release/debug lib vars
set(CppUnit_RELEASE_LIBRARIES ${CppUnit_LIBRARY_RELEASE})
get_filename_component(CppUnit_RELEASE_LIBRARY_DIRS ${CppUnit_RELEASE_LIBRARIES} DIRECTORY)
set(CppUnit_DEBUG_LIBRARIES ${CppUnit_LIBRARY_DEBUG})
get_filename_component(CppUnit_DEBUG_LIBRARY_DIRS ${CppUnit_DEBUG_LIBRARIES} DIRECTORY)
set(CppUnit_LIBRARIES ${CppUnit_RELEASE_LIBRARIES})
set(CppUnit_LIBRARY_DIRS ${CppUnit_RELEASE_LIBRARY_DIRS})
set(CppUnit_INCLUDE_DIRS ${CppUnit_INCLUDE_DIR})
# Configure lib type. If XXX_USE_STATIC_LIBS, we always assume a static
# lib is in use. If win32, we can't mark the import .libs as shared, so
# these are always marked as UNKNOWN. Otherwise, infer from extension.
set(CPPUNIT_LIB_TYPE UNKNOWN)
if(CPPUNIT_USE_STATIC_LIBS)
set(CPPUNIT_LIB_TYPE STATIC)
elseif(UNIX)
get_filename_component(_CPPUNIT_EXT ${CppUnit_LIBRARY_RELEASE} EXT)
if(_CPPUNIT_EXT STREQUAL ".a")
set(CPPUNIT_LIB_TYPE STATIC)
elseif(_CPPUNIT_EXT STREQUAL ".so" OR
_CPPUNIT_EXT STREQUAL ".dylib")
set(CPPUNIT_LIB_TYPE SHARED)
endif()
endif()
if(NOT TARGET CppUnit::cppunit)
add_library(CppUnit::cppunit ${CPPUNIT_LIB_TYPE} IMPORTED)
set_target_properties(CppUnit::cppunit PROPERTIES
INTERFACE_COMPILE_OPTIONS "${PC_CppUnit_CFLAGS_OTHER}"
INTERFACE_INCLUDE_DIRECTORIES "${CppUnit_INCLUDE_DIRS}")
# Standard location
set_target_properties(CppUnit::cppunit PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${CppUnit_LIBRARY}")
# Release location
if(EXISTS "${CppUnit_LIBRARY_RELEASE}")
set_property(TARGET CppUnit::cppunit APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE)
set_target_properties(CppUnit::cppunit PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "CXX"
IMPORTED_LOCATION_RELEASE "${CppUnit_LIBRARY_RELEASE}")
endif()
# Debug location
if(EXISTS "${CppUnit_LIBRARY_DEBUG}")
set_property(TARGET CppUnit::cppunit APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG)
set_target_properties(CppUnit::cppunit PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "CXX"
IMPORTED_LOCATION_DEBUG "${CppUnit_LIBRARY_DEBUG}")
endif()
endif()
+1 -1
View File
@@ -64,7 +64,7 @@ may be provided to tell this module where to look.
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
include(GNUInstallDirs)
+1 -1
View File
@@ -87,7 +87,7 @@ may be provided to tell this module where to look.
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
include(GNUInstallDirs)
+44
View File
@@ -0,0 +1,44 @@
include (FindPackageHandleStandardArgs)
if(NCCL_ROOT)
set(_NCCL_ROOT ${NCCL_ROOT})
elseif(DEFINED ENV{NCCL_ROOT})
set(_NCCL_ROOT $ENV{NCCL_ROOT})
endif()
set(_NCCL_INCLUDE_SEARCH_DIRS "")
list(APPEND _NCCL_INCLUDE_SEARCH_DIRS
${_NCCL_ROOT}/include
)
find_path(NCCL_INCLUDE_DIR
NAMES nccl.h
PATHS ${_NCCL_INCLUDE_SEARCH_DIRS}
)
set(_NCCL_LIBRARY_SEARCH_DIRS "")
list(APPEND _NCCL_LIBRARY_SEARCH_DIRS
${_NCCL_ROOT}/bin
)
find_library(NCCL_LIBRARY
NAMES nccl
PATHS ${_NCCL_LIBRARY_SEARCH_DIRS}
)
find_package_handle_standard_args(NCCL REQUIRED_VARS NCCL_INCLUDE_DIR NCCL_LIBRARY)
if (NCCL_FOUND)
mark_as_advanced(NCCL_INCLUDE_DIR)
mark_as_advanced(NCCL_LIBRARY)
endif()
if (NCCL_FOUND AND NOT TARGET NCCL::NCCL)
add_library(NCCL::NCCL SHARED IMPORTED)
if(MSVC)
string(REPLACE ".lib" ".dll" NCCL_DLL ${NCCL_LIBRARY})
set_property(TARGET NCCL::NCCL PROPERTY IMPORTED_LOCATION ${NCCL_DLL})
set_property(TARGET NCCL::NCCL PROPERTY IMPORTED_IMPLIB ${NCCL_LIBRARY})
else()
set_property(TARGET NCCL::NCCL PROPERTY IMPORTED_LOCATION ${NCCL_LIBRARY})
endif()
target_include_directories(NCCL::NCCL INTERFACE ${NCCL_INCLUDE_DIR})
endif()
+1 -1
View File
@@ -93,7 +93,7 @@ may be provided to tell this module where to look.
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
include(GNUInstallDirs)
+1 -1
View File
@@ -109,7 +109,7 @@ may be provided to tell this module where to look.
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
include(GNUInstallDirs)
+1 -1
View File
@@ -93,7 +93,7 @@ may be provided to tell this module where to look.
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
include(GNUInstallDirs)
+1 -1
View File
@@ -47,7 +47,7 @@ The following variables may be provided to tell this module where to look.
# Find the glfw3 installation and use glfw's CMake to initialize
# the glfw lib
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
set(_FIND_GLFW3_ADDITIONAL_OPTIONS "")
if(DISABLE_CMAKE_SEARCH_PATHS)
+1 -1
View File
@@ -73,7 +73,7 @@ may be provided to tell this module where to look.
# Find the Houdini installation and use Houdini's CMake to initialize
# the Houdini lib
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
# Include utility functions for version information
include(${CMAKE_CURRENT_LIST_DIR}/OpenVDBUtils.cmake)
+1 -1
View File
@@ -51,7 +51,7 @@ variables may be provided to tell this module where to look.
# Find the Maya installation and use Maya's CMake to initialize
# the Maya lib
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
set(_FIND_MAYA_ADDITIONAL_OPTIONS "")
+1 -1
View File
@@ -57,7 +57,7 @@ The following functions are provided:
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
function(OPENVDB_GET_VERSION_DEFINE HEADER KEY VALUE)
+1 -1
View File
@@ -16,7 +16,7 @@ existing from a previous run of cmake.
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
set(MANIFEST "${CMAKE_CURRENT_BINARY_DIR}/install_manifest.txt")
+1 -1
View File
@@ -7,7 +7,7 @@
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
###############################################################################
+23 -26
View File
@@ -8,7 +8,7 @@
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
###############################################################################
@@ -27,7 +27,9 @@ set(OPENVDB_DEPRECATED_ABI_LIST ${MINIMUM_OPENVDB_ABI_VERSION} ${_PREV_ABI})
unset(_PREV_ABI)
if(NOT DISABLE_DEPENDENCY_VERSION_CHECKS)
# @note Currently tracking CY2022 of the VFX platform where available
# @note Currently tracking CY2024 of the VFX platform where available
# (that is, our minimum requirements are for the current VFX platform)
# minus 2 years to be able to build with legacy ABI)
# @Note Compiler versions are not really a hard and fast rule, you just need
# a compiler with complete support for our MINIMUM_CXX_STANDARD (currently 17).
@@ -39,32 +41,27 @@ if(NOT DISABLE_DEPENDENCY_VERSION_CHECKS)
set(MINIMUM_ICC_VERSION 19)
set(MINIMUM_MSVC_VERSION 19.30) # 1928 (Visual Studio 2019 Version 16.8 + 16.9)
set(MINIMUM_BOOST_VERSION 1.80)
# Nanobind does not store the version in the CMake config file in 2.0.0.
# This issue was fixed in 2.1.0 so next time we bump the minimum version,
# we can define this.
#set(MINIMUM_NANOBIND_VERSION 2.0.0)
set(MINIMUM_IMATH_VERSION 3.1)
set(MINIMUM_OPENEXR_VERSION 3.1)
set(MINIMUM_BOOST_VERSION 1.82)
set(MINIMUM_OPENEXR_VERSION 3.2)
set(MINIMUM_IMATH_VERSION 3.2)
set(MINIMUM_ZLIB_VERSION 1.2.7)
set(MINIMUM_TBB_VERSION 2020.3)
set(MINIMUM_LLVM_VERSION 13.0.0)
set(MINIMUM_LLVM_VERSION 15.0.0)
set(MINIMUM_BLOSC_VERSION 1.17.0)
set(MINIMUM_GLFW_VERSION 3.1)
set(MINIMUM_GLFW_VERSION 3.3)
set(MINIMUM_PYTHON_VERSION 3.10)
set(MINIMUM_NUMPY_VERSION 1.23.0)
set(MINIMUM_PYTHON_VERSION 3.11)
set(MINIMUM_GOOGLETEST_VERSION 1.10)
set(MINIMUM_LOG4CPLUS_VERSION 1.1.2)
set(MINIMUM_HOUDINI_VERSION 19.5)
set(MINIMUM_LOG4CPLUS_VERSION 2.0)
set(MINIMUM_HOUDINI_VERSION 20.5)
# These always promote warnings rather than errors
set(MINIMUM_MAYA_VERSION 2017)
set(MINIMUM_DOXYGEN_VERSION 1.8.8)
endif()
# VFX 23 deprecations to transition to MINIMUM_* variables in OpenVDB 12.0.0
# VFX 24 deprecations to transition to MINIMUM_* variables in OpenVDB 14.0.0
# @note At the time of writing, any variables that are commented out don't
# have target transitional versions.
@@ -73,16 +70,16 @@ endif()
# set(FUTURE_MINIMUM_ICC_VERSION 19)
# set(FUTURE_MINIMUM_CXX_STANDARD 20)
set(FUTURE_MINIMUM_CMAKE_VERSION 3.24)
set(FUTURE_MINIMUM_OPENEXR_VERSION 3.2)
set(FUTURE_MINIMUM_BOOST_VERSION 1.82)
set(FUTURE_MINIMUM_GLFW_VERSION 3.3)
set(FUTURE_MINIMUM_LOG4CPLUS_VERSION 2.0)
# set(FUTURE_MINIMUM_NANOBIND_VERSION 2.1.0)
set(FUTURE_MINIMUM_CMAKE_VERSION 4.0)
set(FUTURE_MINIMUM_OPENEXR_VERSION 3.3)
set(FUTURE_MINIMUM_IMATH_VERSION 3.3)
set(FUTURE_MINIMUM_TBB_VERSION 2021)
set(FUTURE_MINIMUM_BOOST_VERSION 1.85)
#set(FUTURE_MINIMUM_GLFW_VERSION 3.3)
#set(FUTURE_MINIMUM_LOG4CPLUS_VERSION 2.0)
set(FUTURE_MINIMUM_NANOBIND_VERSION 2.5.0)
# set(FUTURE_MINIMUM_BLOSC_VERSION 1.17.0)
# set(FUTURE_MINIMUM_TBB_VERSION 2020.3)
set(FUTURE_MINIMUM_PYTHON_VERSION 3.11)
set(FUTURE_MINIMUM_NUMPY_VERSION 1.26.0)
# set(FUTURE_MINIMUM_PYTHON_VERSION 3.11)
# set(FUTURE_MINIMUM_HOUDINI_VERSION 20.0)
set(FUTURE_MINIMUM_LLVM_VERSION 15.0.0)
set(FUTURE_MINIMUM_LLVM_VERSION 18.0.0)
+1 -1
View File
@@ -7,7 +7,7 @@
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
project(OpenVDBDocs LANGUAGES NONE)
include(GNUInstallDirs)
+6 -8
View File
@@ -238,8 +238,8 @@ it's a good idea to read the above section on
DCC | Supported Version | OpenVDB ABI |
-------- | ----------------- | ----------- |
Houdini | 20.0 | 10 |
Houdini | 20.5 | 11 |
Houdini | 21.0 | 12 |
@subsection buildBuildHou Building Against Houdini
@@ -257,12 +257,10 @@ CMake | Cross-platform family of tools designed to help build software
C++17 Compiler | Matching Houdini compiler and version | All |
Boost | Components: iostreams | All |
GoogleTest | A unit testing framework module for C++ | Unit Tests |
CppUnit | A unit testing framework module for C++ | Unit Tests (AX) |
GLFW | Simple API for OpenGL development | OpenVDB View |
Doxygen | Documentation generation from C++ | Documentation |
Log4cplus | An optional dependency for improved OpenVDB Logging | Optional (All) |
nanobind | C++/python bindings | Optional (Python) |
NumPy | Scientific computing with Python | Optional (Python) |
LLVM | Target-independent code generation | OpenVDB AX |
At a minimum a matching C++17 compiler and CMake will be required. See
@@ -349,14 +347,12 @@ Boost | Components: iostreams
ZLIB | Compression library for disk serialization compression | All |
Blosc | Recommended dependency for improved disk compression | All* |
GoogleTest | A unit testing framework module for C++ | Unit Tests |
CppUnit | A unit testing framework module for C++ | Unit Tests (AX) |
GLFW | Simple API for OpenGL development | OpenVDB View |
Doxygen | Documentation generation from C++ | Documentation |
OpenEXR | EXR serialization support | OpenVDB Render |
Imath | Used half precision floating points and EXR serialization support | Optional (All) |
Log4cplus | An optional dependency for improved OpenVDB Logging | Optional (All) |
nanobind | C++/python bindings | Optional (Python) |
NumPy | Scientific computing with Python | Optional (Python) |
LLVM | Target-independent code generation | OpenVDB AX |
@@ -512,7 +508,7 @@ You can add the below CMake snippet to your main `CMakeLists.txt` to bring in
OpenVDB as a dependency:
@code{.cmake}
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
list(APPEND CMAKE_MODULE_PATH "/location/of/openvdb/install/lib/cmake/OpenVDB")
find_package(OpenVDB REQUIRED)
target_link_libraries(myapp OpenVDB::openvdb)
@@ -605,10 +601,12 @@ OpenVDB uses [imported targets](https://cmake.org/cmake/help/latest/command/add_
for all its dependencies. For imported Boost compatibility, the following
versions of CMake are required:
- Boost 1.80 requires CMake 3.24.2 or newer.
- Boost 1.81 requires CMake 3.25.2 or newer.
- Boost 1.82 requires CMake 3.27.0 or newer.
- Boost 1.83 requires CMake 3.27.4 or newer.
- Boost 1.84 requires CMake 3.28.2 or newer.
- Boost 1.85 requires CMake 3.29.3 or newer.
- Boost 1.86 requires CMake 3.30.6, 3.31.1, or newer.
- Boost 1.87 requires CMake 3.31.4 or newer.
Use the above list to identify the version of CMake you require for your version
of Boost. Note that this may be implemented into the OpenVDB CMake in the future.
+154 -2
View File
@@ -2,9 +2,161 @@
@page changes Release Notes
@htmlonly <a name="v12_0_2_changes"></a>@endhtmlonly
@htmlonly <a name="v12_1_2_changes"></a>@endhtmlonly
@par
<B>Version 12.0.2</B> - <I>In development</I>
<B>Version 12.1.2</B> - <I>In development</I>
@htmlonly <a name="v12_1_1_changes"></a>@endhtmlonly
@par
<B>Version 12.1.1</B> - <I>September 30, 2025</I>
@par
OpenVDB AX:
- Fixes:
- Fixed an issue which was causing host specific optimization passes to be
omitted when using LLVM 16 and higher.
@par
Houdini:
- Fixed a compilation issue with the OpenVDB Activate SOP with Houdini 21
[Reported by @callen128]
@htmlonly <a name="v12_1_0_changes"></a>@endhtmlonly
@par
<B>Version 12.1.0</B> - <I>August 8, 2025</I>
@par
OpenVDB:
- New features:
- New level set constructor tools::createLevelSetCapsule creates a grid
containing a narrow-band level set representation of a capsule (tube
with constant radius and sphere caps).
- New level set constructor tools::createLevelSetTaperedCapsule creates a
grid containing a narrow-band level set representation of a tapered
capsule (tube with sphere caps and different radii at both ends, or
equivalently the convex hull of two spheres with possibly different
centers and radii).
- New level set constructor tools::createLevelSetTubeComplex creates a grid
containing a narrow-band level set representation of a tube complex (a
collection of capsules or tapered capsules defined by endpoint coordinates
and segment indices).
- New level set constructor tools::createLevelSetDilatedMesh creates a grid
containing a narrow-band level set representation of a dilated surface
mesh (dilated by a radius in all directions).
- New class tools::ConvexVoxelizer, used by the above methods, which can be
inherited to implement a convex level set constructor.
- Introduced anisotropic surfacing tools in points/PointRasterizeSDF.h and
principal component analysis (PCA) methods in points/PrincipalComponentAnalysis.h.
The latter tool analyses point neighborhoods and computes affine
transformations representing elliptical distributions. The prior tool
takes rotational and stretch point attributes (optionally computed from
the prior PCA methods) and generates anisotropic surfaces.
- Improvements:
- Significantly improved the performance of all LeafNode ValueIterators,
up to 5x on some platforms and up to 10x when delay loading is enabled.
Construction of a ValueIterator from a leaf node now requests the leaf
buffers ahead of iteration to avoid potentially expensive API calls.
- Improved the performance of PointRasterizeSDF.h tools with larger radii
with better bound computation approximations.
- Significant performance improvement to delay loaded files - the warning
logic which checks the underlying mapped file for the last modified time
has been removed, resulting in performance gains upwards of 10x when
loading in node data buffers. Note that the check that was removed is
entirely redundant on Windows and would only print a warning on other
system.
[Contributed by jjtParadox]
- Added OPENVDB_HAS_INVOKABLE_MEMBER_FUNCTION and OPENVDB_HAS_MEMBER_FUNCTION
macros to help determine the presence and compatibility of interface
member functions at compile time.
- Added OPENVDB_ASSUME macros to mimic builtin assume and C++23 assume
attributes.
- Improved read and write of PLY files with vdb_tool so they support Vec3d
and 64 bit attributes. Changed vdb_tool version number from 10.6.1 to
10.7.0.
- API Changes:
- Mat3 and Mat4 functions have been marked [[nodiscard]] where it may be
confusing if they are operating in place or returning a result, in
particular tranpose, invert, adjoint, and timesDiagonal.
- Re-worked the API in PointRasterizeSDF.h with the addition of anisotropic
surfacing kernels. The new API exposes more concise free functions which
take structs of parameters. The old API remains supported but is
deprecated.
- Deprecated providing a filter or interrupter as arguments to
points::rasterize. These can instead be implemented on user transfer
schemes.
- Transfer schemes provided to points::rasterize can now optionally
implement a rasterizePoints callback that takes a range of points per
voxel.
- Fixes:
- Fixed a bug in points::rasterizeSpheres and points::rasterizeSmoothSpheres
which could cause sections of the generated surface to incorrectly be
marked as negative interior values.
- Fixed a bug in points::rasterizeSmoothSpheres which could result in
incomplete surface reconstruction with significantly larger search
distances.
- Fixed some precision issues in various Matrix methods.
- Fixed bugs in vdb_tool that prevented build with newer namespaces in
nanovdb.
@par
OpenVDB AX:
- Improvements:
- Added support for LLVM versions greater than 15. Upgraded CI to test with
LLVM versions 16, 17, 18, 19 and 20.
- As of this release, LLVM 15 is the minimum required version for OpenVDB
AX. Note that there is an issue with LLVM 17 symbols which makes it
incompatible with the OpenVDB Python plugin. AS such, LLVM version 18 and
greater are the recommended LLVM versions to use, with LLVM 18 being the
new future minimum version.
- Significant performance improvement to the compilation of AX kernels when
using LLVM 18 and greater.
- Significant backend refactoring to AX IR generation to support newer
versions of LLVM. In particular, parts of the codegen::Function and
codegen::FunctionBuilder API require new usage from LLVM 16 due to
the LLVM opaque pointer changes. If you're not using the OpenVDB AX
Function codegen API then no client changes should be necessary.
- Migrated OpenVDB AX unit tests from cppunit to gtest
[Contributed by Tim Straubinger]
- Fixes:
- Fixed a case of undefined behaviour when explicitly casting variables to
the same type as their storage type e.g:
double a; double b = double(a);
- Fixed an AX compiler error which would produce invalid IR when using 64bit
integer variables for matrix array offsetting e.g:
int64 a = 0; mat4f b = 1; float c = b[a,a];
@par
NanoVDB:
- Added nanovdb::openToIndexVDB, which can convert an OpenVDB grid into a
NanoVDB IndexGrid.
- Added new options "--index" and "--onIndex" to the command-line tool
nanovdb_convert so it can produce IndexGrids.
- Fixed memory leak in DeviceBuffer.
- Added constructor for DeviceBuffer from an existing HostBuffer.
- Added VoxelBlockManager, an acceleration structure for sequential access
and stencil ops over active voxels.
- Added helper functions for kernel launches with shared memory.
- Added support for CUDA 13.0.
- Fix warnings and compilation errors for GCC and Clang.
- Fixed inconsistent usage of util::cuda::mallocAsync/freeAsync that
affected vGPUs.
- Added support for custom memory resources in PointsToGrid.
- Fixed race condition in split/mergeGridHandles.
- Fixed offset bug in nanovdb::tools::cuda::IndexToGrid related to non-POD
build types.
[Contributed by Andreas Henne]
@par
Houdini:
- Define OP_InputIdx and OP_OutputIdx for Houdini 21.0 compatibility
- Add Houdini 21 style camera interface to support LOP cameras
- Include env flag for avoiding stream writing (was backported to 19.5)
@htmlonly <a name="v12_0_1_changes"></a>@endhtmlonly
+23 -24
View File
@@ -41,14 +41,14 @@ OpenVDB Print | Core Library dependencies | -
OpenVDB LOD | Core Library dependencies | -
OpenVDB Render | Core Library dependencies | OpenEXR, Imath::Imath, libpng
OpenVDB View | Core Library dependencies, OpenGL, GLFW3, GLEW* | -
OpenVDB Python | Core Library dependencies, Python, nanobind | NumPy
OpenVDB Python | Core Library dependencies, Python, nanobind | -
OpenVDB AX | Core Library dependencies, LLVM | Bison, Flex
NanoVDB | - | Core Library, CUDA, TBB, Blosc, ZLib
OpenVDB UnitTests | Core Library dependencies, GoogleTest | CppUnit**
OpenVDB UnitTests | Core Library dependencies, GoogleTest, numpy* | -
OpenVDB Documentation | Doxygen | -
- @b * GLEW is only required for building the vdb_view binary on Windows.
- @b ** CppUnit is only required for building the AX unit tests.
- @b * numpy only required for python binding unit tests
- @b Note - Bison and Flex are only required for re-generation of the AX
grammar which is not typically necessary for the majority of users.
@@ -56,41 +56,39 @@ OpenVDB Documentation | Doxygen | -
Package | Minimum | Recommended | Description | apt-get | Homebrew | Source
-------------- | ------- | ----------- | ----------------------------------------------------------------- | ------- | -------- | ------
CMake | 3.20 | Latest | Cross-platform family of tools designed to help build software | Y | Y | https://cmake.org
CMake | 3.24 | Latest | Cross-platform family of tools designed to help build software | Y | Y | https://cmake.org
GCC | 11.2.1 | 11.2.1 | C++ 17 Compiler: The GNU Compiler Collection | Y | Y | https://www.gnu.org/software/gcc
Clang | 5.0 | Latest | C++ 17 Compiler: A C language family frontend for LLVM | Y | Y | https://clang.llvm.org
Intel ICC | 19 | Latest | C++ 17 Compiler: Intels C++ Compiler | Y | Y | https://software.intel.com/en-us/c-compilers
MSVC | 19.30 | 19.30 | C++ 17 Compiler: Microsoft Visual C++ Compiler | Y | Y | https://visualstudio.microsoft.com/vs
Imath | 3.1 | Latest | Half precision floating points | Y | Y | http://www.openexr.com
OpenEXR | 3.1 | Latest | EXR serialization support | Y | Y | http://www.openexr.com
TBB | 2020.3 | 2020.3 | Threading Building Blocks - template library for task parallelism | Y | Y | https://www.threadingbuildingblocks.org
Imath | 3.2 | >= 3.3 | Half precision floating points | Y | Y | http://www.openexr.com
OpenEXR | 3.2 | >= 3.3 | EXR serialization support | Y | Y | http://www.openexr.com
TBB | 2020.3 | >= 2021 | Threading Building Blocks - template library for task parallelism | Y | Y | https://www.threadingbuildingblocks.org
ZLIB | 1.2.7 | Latest | Compression library for disk serialization compression | Y | Y | https://www.zlib.net
Boost | 1.80 | 1.82 | Components: iostreams | Y | Y | https://www.boost.org
LLVM | 13.0.0 | 15.0.0* | Target-independent code generation | Y | Y | https://llvm.org/
Boost | 1.82 | >= 1.85 | Components: iostreams | Y | Y | https://www.boost.org
LLVM | 15.0.0* | 18.0.0 | Target-independent code generation | Y | Y | https://llvm.org/
Bison | 3.7.0 | 3.7.0 | General-purpose parser generator | Y | Y | https://www.gnu.org/software/gcc
Flex | 2.6.4 | 2.6.4 | Fast lexical analyzer generator | Y | Y | https://github.com/westes/flex
Python | 3.10 | 3.11 | The python interpreter and libraries | Y | Y | https://www.python.org
nanobind | 2.0.0 | 2.1.0 | C++/python bindings | Y | Y | https://nanobind.readthedocs.io
NumPy | 1.23.0 | 1.26.0 | Scientific computing with Python | Y | Y | http://www.numpy.org
nanobind** | 2.0.0 | >= 2.5.0 | C++/python bindings | Y | Y | https://nanobind.readthedocs.io
GoogleTest | 1.10 | Latest | A unit testing framework module for C++ | Y | Y | https://github.com/google/googletest
CppUnit | 1.10 | Latest | A unit testing framework module for C++ | N | Y | https://freedesktop.org/wiki/Software/cppunit
Blosc | 1.17.0* | 1.17.0 | Recommended dependency for improved disk compression | Y | Y | https://github.com/Blosc/c-blosc/releases
Blosc | 1.17.0* | Latest | Recommended dependency for improved disk compression | Y | Y | https://github.com/Blosc/c-blosc/releases
Log4cplus | 1.1.2 | Latest | An optional dependency for improved OpenVDB Logging | Y | Y | https://github.com/log4cplus/log4cplus
libpng | - | Latest | Library for manipulating PNG images | Y | Y | http://www.libpng.org/pub/png/libpng.html
GLFW | 3.1 | > 3.3 | Simple API for OpenGL development | Y | Y | https://www.glfw.org
GLFW | 3.3 | Latest | Simple API for OpenGL development | Y | Y | https://www.glfw.org
OpenGL | 3.2 | Latest | Environment for developing portable graphics applications | Y | Y | https://www.opengl.org
GLEW | 1.0.0 | Latest | A cross-platform OpenGL extension loading library. | Y | Y | http://glew.sourceforge.net
CUDA | - | Latest | Parallel computing platform for graphical processing units. | Y | N | https://developer.nvidia.com/cuda-downloads
Doxygen | 1.8.8 | 1.8.11 | Documentation generation from C++ | Y | Y | http://www.doxygen.nl
- @b * See the note in [known issues](@ref depKnownIssues) regarding supported blosc/llvm versions.
- @b * See the note in [known issues](@ref depKnownIssues) regarding supported blosc/llvm versions.
- @b ** See the note in [known issues](@ref depKnownIssues) regarding supported nanobind build issues.
@subsection depKnownIssues Known Issues
- @b LLVM >= 15. LLVM has recently changed their IR pointer data types to be
completely opaque. Some support is provided in LLVM 15 but tests with AX show
that this is extremely unstable. AX currently only supports LLVM versions up
until 14.X.X. See https://llvm.org/docs/OpaquePointers.html for more details.
- @b On Linux, LLVM == 17 has symbol issues which makes it incompatible when
used with the OpenVDB Python plugin. LLVM versions greater than 18 should be
preferred.
- @b GLFW: Crash with GLFW 3.2.1 when used with EGL on Linux. OpenVDB View may
segfault on closure. Fixed in GLFW 3.3
@@ -112,6 +110,11 @@ Doxygen | 1.8.8 | 1.8.11 | Documentation generation from C++
- https://github.com/Blosc/c-blosc/issues/260
- https://github.com/Blosc/c-blosc/issues/261
- @b nanobind: Nanobind requires a fast-hash-map implementation library called
tsl-robin-map. If you're installing nanobind through homebrew, this library
may not be installed correctly and OpenVDB's CMake will only produce a warning.
Make sure to also: `brew install robin-map`.
@section depInstallingDependencies Installing Dependencies
This section details a number of ways to get you up and running with the
@@ -148,14 +151,12 @@ apt-get install libblosc-dev # Blosc
apt-get install llvm-15-dev # LLVM
# Python
apt-get install python-dev # Python
apt-get install python-numpy # NumPy
pip install nanobind # nanobind
# Optional
apt-get install libpng-dev # libpng
apt-get install libopenexr-dev # OpenEXR
apt-get install liblog4cplus-dev # Log4cplus
apt-get install googletest # GoogleTest
apt-get install libcppunit-dev # CppUnit
# vdb_view
apt-get install libglfw3-dev # GLFW
# Documentation
@@ -181,11 +182,9 @@ brew install c-blosc # blosc
brew install llvm # LLVM
# Python
brew install python # Python
brew install numpy # NumPy
brew install nanobind # nanobind
brew install nanobind robin-map # nanobind
# Unit Tests
brew install gtest # GoogleTest
brew install cppunit # CppUnit
# vdb_view
brew install glfw # GLFW
# vdb_render
+3 -5
View File
@@ -441,11 +441,9 @@ not on their active states or other attributes.
Large data sets are often handled in Python using
<a href="http://www.numpy.org/">NumPy</a>.
The OpenVDB Python module can optionally be compiled with NumPy support.
With NumPy enabled, the @b copyFromArray and @b copyToArray grid methods
can be used to exchange data efficiently between scalar-valued grids
and three-dimensional NumPy arrays and between vector-valued grids and
four-dimensional NumPy arrays.
The @b copyFromArray and @b copyToArray grid methods can be used to exchange
data efficiently between scalar-valued grids and three-dimensional NumPy
arrays and between vector-valued grids and four-dimensional NumPy arrays.
@code{.py}
>>> import pyopenvdb as vdb
>>> import numpy
+36 -3
View File
@@ -7,7 +7,7 @@
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
project(NanoVDB LANGUAGES C CXX)
include(GNUInstallDirs)
@@ -115,6 +115,18 @@ if(NANOVDB_USE_CUDA)
endif()
find_package(CUDAToolkit)
include(../../cmake/CPM.cmake)
# This will automatically clone CCCL from GitHub and make the exported cmake targets available
CPMAddPackage(
NAME CCCL
GITHUB_REPOSITORY nvidia/cccl
GIT_TAG main # Fetches the latest commit on the main branch
)
find_package(NCCL)
endif()
if(NANOVDB_USE_OPENVDB)
@@ -171,9 +183,12 @@ set(NANOVDB_INCLUDE_FILES
# NanoVDB cuda header files
set(NANOVDB_INCLUDE_CUDA_FILES
cuda/DeviceBuffer.h
cuda/DeviceMesh.h
cuda/DeviceResource.h
cuda/DeviceStreamMap.h
cuda/GridHandle.cuh
cuda/NodeManager.cuh
cuda/TempPool.h
cuda/UnifiedBuffer.h
)
@@ -202,6 +217,7 @@ set(NANOVDB_INCLUDE_TOOLS_FILES
tools/GridStats.h
tools/GridValidator.h
tools/NanoToOpenVDB.h
tools/VoxelBlockManager.h
)
# NanoVDB tools/cuda header files
@@ -213,6 +229,14 @@ set(NANOVDB_INCLUDE_TOOLS_CUDA_FILES
tools/cuda/IndexToGrid.cuh
tools/cuda/PointsToGrid.cuh
tools/cuda/SignedFloodFill.cuh
tools/cuda/MergeGrids.cuh
tools/cuda/TopologyBuilder.cuh
tools/cuda/RefineGrid.cuh
tools/cuda/PruneGrid.cuh
tools/cuda/DistributedPointsToGrid.cuh
tools/cuda/DilateGrid.cuh
tools/cuda/CoarsenGrid.cuh
tools/cuda/VoxelBlockManager.cuh
)
# NanoVDB util header files
@@ -241,6 +265,7 @@ set(NANOVDB_INCLUDE_UTIL_FILES
util/Stencils.h
util/Timer.h
util/Util.h
util/MorphologyHelpers.h
)
# NanoVDB util/cuda header files
@@ -259,6 +284,10 @@ set(NANOVDB_INCLUDE_UTIL_CUDA_FILES
util/cuda/CudaGridValidator.cuh
util/cuda/CudaPointsToGrid.cuh
util/cuda/GpuTimer.h
util/cuda/Morphology.cuh
util/cuda/MorphologyHelpers.cuh
util/cuda/DeviceGridTraits.cuh
util/cuda/Injection.cuh
)
add_library(nanovdb INTERFACE)
@@ -311,8 +340,12 @@ if(NANOVDB_USE_CUDA)
endif()
# Add CUDA includes to any C++ units which use NanoVDB
target_include_directories(nanovdb INTERFACE
${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
target_link_libraries(nanovdb INTERFACE CCCL::CCCL CUDA::cudart)
if(NCCL_FOUND)
target_compile_definitions(nanovdb INTERFACE -DNANOVDB_USE_NCCL)
target_link_libraries(nanovdb INTERFACE NCCL::NCCL)
endif()
endif()
if(NANOVDB_USE_MAGICAVOXEL)
+3
View File
@@ -5,6 +5,9 @@
// Simple C-wrapper for the nanovdb structure
// Meant for systems where you lack a C++ compiler.
//
/// @warning This header file will soon be deprecated in favor of PNanoVDB.h
#ifndef __CNANOVDB__
#define __CNANOVDB__
+8 -5
View File
@@ -111,6 +111,9 @@ public:
typename util::enable_if<BufferTraits<U>::hasDeviceDual, const void*>::type
deviceData() const { return mBuffer.deviceData(); }
template<typename U = BufferT>
typename util::enable_if<BufferTraits<U>::hasDeviceDual, const void*>::type
deviceData(int device) const { return mBuffer.deviceData(device); }
template<typename U = BufferT>
typename util::enable_if<BufferTraits<U>::hasDeviceDual, void*>::type
deviceData() { return mBuffer.deviceData(); }
template<typename U = BufferT>
@@ -173,7 +176,7 @@ public:
typename util::enable_if<BufferTraits<U>::hasDeviceDual, void>::type
deviceUpload(void* stream, bool sync = true) { mBuffer.deviceUpload(stream, sync); }
/// @brief Upload the host buffer to a specefic device buffer. It device buffer doesn't exist it's created first
/// @brief Upload the host buffer to a specific device buffer. It device buffer doesn't exist it's created first
/// @param device Device to upload host data to
/// @param stream cuda stream
/// @param sync if false the memory copy is asynchronous
@@ -212,7 +215,7 @@ public:
return sum;
}
/// @brief compute the size of unusedstorage in this buffer
/// @brief compute the size of unused storage in this buffer
/// @return the number of unused bytes in this buffer.
uint64_t freeSize() const {return mBuffer.size() - this->totalGridSize();}
@@ -286,12 +289,12 @@ public:
/// @param is input stream containing a raw grid buffer
/// @param gridName string name of the grid to be read
/// @param pool optional pool from which to allocate the new grid buffer
/// @throw Will throw a std::logic_error if the stream does not contain a valid raw grid with the speficied name
/// @throw Will throw a std::logic_error if the stream does not contain a valid raw grid with the specified name
void read(std::istream& is, const std::string &gridName, const BufferT& pool = BufferT());
/// @brief Read a raw grid buffer from a file
/// @param filename string name of the input file containing a raw grid buffer
/// @param pool optional pool from which to allocate the new grid buffe
/// @param pool optional pool from which to allocate the new grid buffer
void read(const std::string &fileName, const BufferT& pool = BufferT()) {
std::ifstream is(fileName, std::ios::in | std::ios::binary);
if (!is.is_open()) throw std::ios_base::failure("Unable to open file named \"" + fileName + "\" for input");
@@ -487,7 +490,7 @@ splitGrids(const GridHandle<BufferT> &handle, const BufferT* other = nullptr)
h = HandleT(std::move(buffer));
ptr = util::PtrAdd(ptr, src->mGridSize);
}
return std::move(handles);
return handles;
}// splitGrids
/// @brief Combines (or merges) multiple GridHandles into a single GridHandle containing all grids
+7 -6
View File
@@ -192,6 +192,13 @@ public:
void* data() { return mData; }
//@}
/// @brief Returns an offset pointer of a specific type from the allocated host memory
/// @tparam T Type of the pointer returned
/// @param count Numbers of elements of @c parameter type T to skip
/// @warning might return NULL
template <typename T>
T* data(ptrdiff_t count = 0) const {return mData ? reinterpret_cast<T*>(mData) + count : nullptr;}
//@{
/// @brief Returns the size in bytes associated with this buffer.
uint64_t bufferSize() const { return mSize; }
@@ -316,7 +323,6 @@ struct HostBuffer::Pool
<< mSize << " bytes of which\n\t" << (util::PtrDiff(alignedFree, mData) - mPadding)
<< " bytes are used by " << mRegister.size() << " other buffer(s). "
<< "Pool is " << (mManaged ? "internally" : "externally") << " managed.\n";
//std::cerr << ss.str();
throw std::runtime_error(ss.str());
}
buffer->mSize = size;
@@ -389,7 +395,6 @@ struct HostBuffer::Pool
}
for (HostBuffer* buffer : mRegister) { // update registered buffers
//buffer->mData = paddedData + ptrdiff_t(buffer->mData - (mData + mPadding));
buffer->mData = util::PtrAdd(paddedData, util::PtrDiff(buffer->mData, util::PtrAdd(mData, mPadding)));
}
mFree = util::PtrAdd(paddedData, memUsage); // update the free pointer
@@ -414,12 +419,8 @@ private:
static void* alloc(uint64_t size)
{
//#if (__cplusplus >= 201703L)
// return std::aligned_alloc(NANOVDB_DATA_ALIGNMENT, size);//C++17 or newer
//#else
// make sure we alloc enough space to align the result
return std::malloc(size + NANOVDB_DATA_ALIGNMENT);
//#endif
}
static void* realloc(void* const origData,
+633 -973
View File
File diff suppressed because it is too large Load Diff
+3 -7
View File
@@ -42,8 +42,8 @@ NodeManagerHandle<BufferT> createNodeManager(const NanoGrid<BuildT> &grid,
const BufferT& buffer = BufferT());
struct NodeManagerData
{// 48B = 6*8B
uint64_t mMagic;// 8B
{// 40B = 5*8B
__hostdev__ NodeManagerData(void *grid) : mPadding{0}, mGrid(grid), mPtr{0,0,0}{}
union {int64_t mPadding; uint8_t mLinear;};// 8B of which 1B is used for a binary flag
void *mGrid;// 8B pointer to either host or device grid
union {int64_t *mPtr[3], mOff[3];};// 24B, use mOff if mLinear!=0
@@ -288,11 +288,7 @@ NodeManagerHandle<BufferT> createNodeManager(const NanoGrid<BuildT> &grid,
auto *data = reinterpret_cast<NodeManagerData*>(handle.data());
NANOVDB_ASSERT(data && isAligned(data));
NANOVDB_ASSERT(toGridType<BuildT>() == grid.gridType());
#ifdef NANOVDB_USE_NEW_MAGIC_NUMBERS
*data = NodeManagerData{NANOVDB_MAGIC_NODE, {0u}, (void*)&grid, {{0u,0u,0u}}};
#else
*data = NodeManagerData{NANOVDB_MAGIC_NUMB, {0u}, (void*)&grid, {{0u,0u,0u}}};
#endif
*data = NodeManagerData((void*)&grid);
if (NodeManager<BuildT>::isLinear(grid)) {
data->mLinear = uint8_t(1u);
+345 -145
View File
@@ -198,47 +198,110 @@ PNANOVDB_BUF_FORCE_INLINE void pnanovdb_buf_write_uint64(pnanovdb_buf_t buf, uin
typedef uint32_t pnanovdb_grid_type_t;
#define PNANOVDB_GRID_TYPE_GET(grid_typeIn, nameIn) pnanovdb_grid_type_constants[grid_typeIn].nameIn
#elif defined(PNANOVDB_BUF_HLSL)
#if defined(PNANOVDB_ADDRESS_32)
#if defined(PNANOVDB_BUF_HLSL_RW)
#if defined(PNANOVDB_BUF_HLSL_64)
#define pnanovdb_buf_t RWStructuredBuffer<uint2>
#else
#define pnanovdb_buf_t RWStructuredBuffer<uint>
#endif
#else
#if defined(PNANOVDB_BUF_HLSL_64)
#define pnanovdb_buf_t StructuredBuffer<uint2>
#else
#define pnanovdb_buf_t StructuredBuffer<uint>
#endif
#endif
#if defined(PNANOVDB_ADDRESS_32)
uint pnanovdb_buf_read_uint32(pnanovdb_buf_t buf, uint byte_offset)
{
#if defined(PNANOVDB_BUF_HLSL_64)
uint2 val = buf[(byte_offset >> 3u)];
return ((byte_offset & 4u) == 0u) ? val.x : val.y;
#else
return buf[(byte_offset >> 2u)];
#endif
}
uint2 pnanovdb_buf_read_uint64(pnanovdb_buf_t buf, uint byte_offset)
{
uint2 ret;
#if defined(PNANOVDB_BUF_HLSL_64)
ret = buf[(byte_offset >> 3u)];
#else
ret.x = pnanovdb_buf_read_uint32(buf, byte_offset + 0u);
ret.y = pnanovdb_buf_read_uint32(buf, byte_offset + 4u);
#endif
return ret;
}
void pnanovdb_buf_write_uint32(pnanovdb_buf_t buf, uint byte_offset, uint value)
{
// NOP, by default no write in HLSL
#if defined(PNANOVDB_BUF_HLSL_RW)
#if defined(PNANOVDB_BUF_HLSL_64)
if ((byte_offset & 4u) == 0u) {buf[(byte_offset >> 3u)].x = value;}
else {buf[(byte_offset >> 3u)].y = value;}
#else
buf[(byte_offset >> 2u)] = value;
#endif
#endif
}
void pnanovdb_buf_write_uint64(pnanovdb_buf_t buf, uint byte_offset, uint2 value)
{
// NOP, by default no write in HLSL
#if defined(PNANOVDB_BUF_HLSL_RW)
#if defined(PNANOVDB_BUF_HLSL_64)
buf[(byte_offset >> 3u)] = value;
#else
pnanovdb_buf_write_uint32(buf, byte_offset + 0u, value.x);
pnanovdb_buf_write_uint32(buf, byte_offset + 4u, value.y);
#endif
#endif
}
#elif defined(PNANOVDB_ADDRESS_64)
#define pnanovdb_buf_t StructuredBuffer<uint>
uint pnanovdb_buf_read_uint32(pnanovdb_buf_t buf, uint64_t byte_offset)
{
#if defined(PNANOVDB_BUF_HLSL_64)
uint2 val = buf[uint(byte_offset >> 3u)];
return ((uint(byte_offset) & 4u) == 0u) ? val.x : val.y;
#else
return buf[uint(byte_offset >> 2u)];
#endif
}
uint64_t pnanovdb_buf_read_uint64(pnanovdb_buf_t buf, uint64_t byte_offset)
{
uint64_t ret;
#if defined(PNANOVDB_BUF_HLSL_64)
uint2 raw = buf[uint(byte_offset >> 3u)];
ret = uint64_t(raw.x) | (uint64_t(raw.y) << 32u);
#else
ret = pnanovdb_buf_read_uint32(buf, byte_offset + 0u);
ret = ret + (uint64_t(pnanovdb_buf_read_uint32(buf, byte_offset + 4u)) << 32u);
ret = ret | (uint64_t(pnanovdb_buf_read_uint32(buf, byte_offset + 4u)) << 32u);
#endif
return ret;
}
void pnanovdb_buf_write_uint32(pnanovdb_buf_t buf, uint64_t byte_offset, uint value)
{
// NOP, by default no write in HLSL
#if defined(PNANOVDB_BUF_HLSL_RW)
#if defined(PNANOVDB_BUF_HLSL_64)
if ((byte_offset & 4u) == 0u) {buf[uint(byte_offset >> 3u)].x = value;}
else {buf[uint(byte_offset >> 3u)].y = value;}
#else
buf[uint(byte_offset >> 2u)] = value;
#endif
#endif
}
void pnanovdb_buf_write_uint64(pnanovdb_buf_t buf, uint64_t byte_offset, uint64_t value)
{
// NOP, by default no write in HLSL
#if defined(PNANOVDB_BUF_HLSL_RW)
#if defined(PNANOVDB_BUF_HLSL_64)
uint2 raw = uint2(uint(value), uint(value >> 32u));
buf[uint(byte_offset >> 3u)] = raw;
#else
pnanovdb_buf_write_uint32(buf, byte_offset + 0u, uint(value));
pnanovdb_buf_write_uint32(buf, byte_offset + 4u, uint(value >> 32u));
#endif
#endif
}
#endif
#define pnanovdb_grid_type_t uint
@@ -343,14 +406,26 @@ typedef struct pnanovdb_vec3_t
{
float x, y, z;
}pnanovdb_vec3_t;
typedef struct pnanovdb_uvec4_t
{
pnanovdb_uint32_t x, y, z, w;
}pnanovdb_uvec4_t;
typedef struct pnanovdb_ivec4_t
{
pnanovdb_int32_t x, y, z, w;
}pnanovdb_ivec4_t;
typedef struct pnanovdb_vec4_t
{
float x, y, z, w;
}pnanovdb_vec4_t;
PNANOVDB_FORCE_INLINE pnanovdb_int32_t pnanovdb_uint32_as_int32(pnanovdb_uint32_t v) { return (pnanovdb_int32_t)v; }
PNANOVDB_FORCE_INLINE pnanovdb_int64_t pnanovdb_uint64_as_int64(pnanovdb_uint64_t v) { return (pnanovdb_int64_t)v; }
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_int64_as_uint64(pnanovdb_int64_t v) { return (pnanovdb_uint64_t)v; }
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_int32_as_uint32(pnanovdb_int32_t v) { return (pnanovdb_uint32_t)v; }
PNANOVDB_FORCE_INLINE float pnanovdb_uint32_as_float(pnanovdb_uint32_t v) { float vf; pnanovdb_memcpy(&vf, &v, sizeof(vf)); return vf; }
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_float_as_uint32(float v) { return *((pnanovdb_uint32_t*)(&v)); }
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_float_as_uint32(float v) { pnanovdb_uint32_t vu; pnanovdb_memcpy(&vu, &v, sizeof(vu)); return vu; }
PNANOVDB_FORCE_INLINE double pnanovdb_uint64_as_double(pnanovdb_uint64_t v) { double vf; pnanovdb_memcpy(&vf, &v, sizeof(vf)); return vf; }
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_double_as_uint64(double v) { return *((pnanovdb_uint64_t*)(&v)); }
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_double_as_uint64(double v) { pnanovdb_uint64_t vu; pnanovdb_memcpy(&vu, &v, sizeof(vu)); return vu; }
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_uint64_low(pnanovdb_uint64_t v) { return (pnanovdb_uint32_t)v; }
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_uint64_high(pnanovdb_uint64_t v) { return (pnanovdb_uint32_t)(v >> 32u); }
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_uint32_as_uint64(pnanovdb_uint32_t x, pnanovdb_uint32_t y) { return ((pnanovdb_uint64_t)x) | (((pnanovdb_uint64_t)y) << 32u); }
@@ -373,6 +448,9 @@ typedef bool pnanovdb_bool_t;
#define PNANOVDB_TRUE true
typedef int3 pnanovdb_coord_t;
typedef float3 pnanovdb_vec3_t;
typedef uint4 pnanovdb_uvec4_t;
typedef int4 pnanovdb_ivec4_t;
typedef float4 pnanovdb_vec4_t;
pnanovdb_int32_t pnanovdb_uint32_as_int32(pnanovdb_uint32_t v) { return int(v); }
pnanovdb_uint32_t pnanovdb_int32_as_uint32(pnanovdb_int32_t v) { return uint(v); }
float pnanovdb_uint32_as_float(pnanovdb_uint32_t v) { return asfloat(v); }
@@ -420,6 +498,9 @@ bool pnanovdb_int64_is_zero(pnanovdb_int64_t a) { return a == 0; }
#define pnanovdb_int64_t ivec2
#define pnanovdb_coord_t ivec3
#define pnanovdb_vec3_t vec3
#define pnanovdb_uvec4_t uvec4
#define pnanovdb_ivec4_t ivec4
#define pnanovdb_vec4_t vec4
pnanovdb_int32_t pnanovdb_uint32_as_int32(pnanovdb_uint32_t v) { return int(v); }
pnanovdb_int64_t pnanovdb_uint64_as_int64(pnanovdb_uint64_t v) { return ivec2(v); }
pnanovdb_uint64_t pnanovdb_int64_as_uint64(pnanovdb_int64_t v) { return uvec2(v); }
@@ -525,6 +606,35 @@ PNANOVDB_FORCE_INLINE pnanovdb_coord_t pnanovdb_coord_add(pnanovdb_coord_t a, pn
v.z = a.z + b.z;
return v;
}
PNANOVDB_FORCE_INLINE pnanovdb_uvec4_t pnanovdb_uvec4_add(pnanovdb_uvec4_t a, pnanovdb_uvec4_t b)
{
pnanovdb_uvec4_t ret;
ret.x = a.x + b.x;
ret.y = a.y + b.y;
ret.z = a.z + b.z;
ret.w = a.w + b.w;
return ret;
}
PNANOVDB_FORCE_INLINE pnanovdb_uvec4_t pnanovdb_uvec4_sub(pnanovdb_uvec4_t a, pnanovdb_uvec4_t b)
{
pnanovdb_uvec4_t ret;
ret.x = a.x - b.x;
ret.y = a.y - b.y;
ret.z = a.z - b.z;
ret.w = a.w - b.w;
return ret;
}
PNANOVDB_FORCE_INLINE pnanovdb_vec4_t pnanovdb_vec4_add(pnanovdb_vec4_t a, pnanovdb_vec4_t b)
{
pnanovdb_vec4_t ret;
ret.x = a.x + b.x;
ret.y = a.y + b.y;
ret.z = a.z + b.z;
ret.w = a.w + b.w;
return ret;
}
#elif defined(PNANOVDB_HLSL)
pnanovdb_vec3_t pnanovdb_vec3_uniform(float a) { return float3(a, a, a); }
pnanovdb_vec3_t pnanovdb_vec3_add(pnanovdb_vec3_t a, pnanovdb_vec3_t b) { return a + b; }
@@ -536,6 +646,9 @@ pnanovdb_vec3_t pnanovdb_vec3_max(pnanovdb_vec3_t a, pnanovdb_vec3_t b) { return
pnanovdb_vec3_t pnanovdb_coord_to_vec3(pnanovdb_coord_t coord) { return float3(coord); }
pnanovdb_coord_t pnanovdb_coord_uniform(pnanovdb_int32_t a) { return int3(a, a, a); }
pnanovdb_coord_t pnanovdb_coord_add(pnanovdb_coord_t a, pnanovdb_coord_t b) { return a + b; }
pnanovdb_uvec4_t pnanovdb_uvec4_add(pnanovdb_uvec4_t a, pnanovdb_uvec4_t b) { return a + b; }
pnanovdb_uvec4_t pnanovdb_uvec4_sub(pnanovdb_uvec4_t a, pnanovdb_uvec4_t b) { return a - b; }
pnanovdb_vec4_t pnanovdb_vec4_add(pnanovdb_vec4_t a, pnanovdb_vec4_t b) { return a + b; }
#elif defined(PNANOVDB_GLSL)
pnanovdb_vec3_t pnanovdb_vec3_uniform(float a) { return vec3(a, a, a); }
pnanovdb_vec3_t pnanovdb_vec3_add(pnanovdb_vec3_t a, pnanovdb_vec3_t b) { return a + b; }
@@ -547,6 +660,9 @@ pnanovdb_vec3_t pnanovdb_vec3_max(pnanovdb_vec3_t a, pnanovdb_vec3_t b) { return
pnanovdb_vec3_t pnanovdb_coord_to_vec3(const pnanovdb_coord_t coord) { return vec3(coord); }
pnanovdb_coord_t pnanovdb_coord_uniform(pnanovdb_int32_t a) { return ivec3(a, a, a); }
pnanovdb_coord_t pnanovdb_coord_add(pnanovdb_coord_t a, pnanovdb_coord_t b) { return a + b; }
pnanovdb_uvec4_t pnanovdb_uvec4_add(pnanovdb_uvec4_t a, pnanovdb_uvec4_t b) { return a + b; }
pnanovdb_uvec4_t pnanovdb_uvec4_sub(pnanovdb_uvec4_t a, pnanovdb_uvec4_t b) { return a - b; }
pnanovdb_vec4_t pnanovdb_vec4_add(pnanovdb_vec4_t a, pnanovdb_vec4_t b) { return a + b; }
#endif
// ------------------------------------------------ Uint64 Utils -----------------------------------------------------------
@@ -589,6 +705,19 @@ PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_uint64_offset(pnanovdb_uint64_t
return pnanovdb_uint32_as_uint64(low, high);
}
PNANOVDB_FORCE_INLINE pnanovdb_int64_t pnanovdb_uint64_diff(pnanovdb_uint64_t a, pnanovdb_uint64_t b)
{
pnanovdb_uint32_t low = pnanovdb_uint64_low(a);
pnanovdb_uint32_t high = pnanovdb_uint64_high(a);
low -= pnanovdb_uint64_low(b);
if (low > pnanovdb_uint64_low(a))
{
high -= 1u;
}
high -= pnanovdb_uint64_high(b);
return pnanovdb_uint64_as_int64(pnanovdb_uint32_as_uint64(low, high));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_uint64_dec(pnanovdb_uint64_t a)
{
pnanovdb_uint32_t low = pnanovdb_uint64_low(a);
@@ -636,6 +765,11 @@ PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_uint64_offset(pnanovdb_uint64_t
return a + b;
}
PNANOVDB_FORCE_INLINE pnanovdb_int64_t pnanovdb_uint64_diff(pnanovdb_uint64_t a, pnanovdb_uint64_t b)
{
return pnanovdb_uint64_as_int64(a - b);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_uint64_dec(pnanovdb_uint64_t a)
{
return a - 1u;
@@ -702,6 +836,10 @@ PNANOVDB_FORCE_INLINE pnanovdb_address_t pnanovdb_address_offset64_product(pnano
ret.byte_offset += pnanovdb_uint64_low(byte_offset) * multiplier;
return ret;
}
PNANOVDB_FORCE_INLINE pnanovdb_int64_t pnanovdb_address_diff(pnanovdb_address_t a, pnanovdb_address_t b)
{
return pnanovdb_uint64_diff(pnanovdb_uint32_as_uint64_low(a.byte_offset),pnanovdb_uint32_as_uint64_low(b.byte_offset));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_address_mask(pnanovdb_address_t address, pnanovdb_uint32_t mask)
{
return address.byte_offset & mask;
@@ -725,6 +863,12 @@ PNANOVDB_FORCE_INLINE pnanovdb_bool_t pnanovdb_address_in_interval(pnanovdb_addr
{
return address.byte_offset >= min_address.byte_offset && address.byte_offset < max_address.byte_offset;
}
PNANOVDB_FORCE_INLINE pnanovdb_address_t pnanovdb_address_add(pnanovdb_address_t a, pnanovdb_address_t b)
{
pnanovdb_address_t sum = a;
sum.byte_offset += b.byte_offset;
return sum;
}
#elif defined(PNANOVDB_ADDRESS_64)
struct pnanovdb_address_t
{
@@ -762,6 +906,10 @@ PNANOVDB_FORCE_INLINE pnanovdb_address_t pnanovdb_address_offset64_product(pnano
ret.byte_offset += byte_offset * pnanovdb_uint32_as_uint64_low(multiplier);
return ret;
}
PNANOVDB_FORCE_INLINE pnanovdb_int64_t pnanovdb_address_diff(pnanovdb_address_t a, pnanovdb_address_t b)
{
return pnanovdb_uint64_diff(a.byte_offset, b.byte_offset);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_address_mask(pnanovdb_address_t address, pnanovdb_uint32_t mask)
{
return pnanovdb_uint64_low(address.byte_offset) & mask;
@@ -785,6 +933,12 @@ PNANOVDB_FORCE_INLINE pnanovdb_bool_t pnanovdb_address_in_interval(pnanovdb_addr
{
return address.byte_offset >= min_address.byte_offset && address.byte_offset < max_address.byte_offset;
}
PNANOVDB_FORCE_INLINE pnanovdb_address_t pnanovdb_address_add(pnanovdb_address_t a, pnanovdb_address_t b)
{
pnanovdb_address_t sum = a;
sum.byte_offset += b.byte_offset;
return sum;
}
#endif
// ------------------------------------------------ High Level Buffer Read -----------------------------------------------------------
@@ -933,7 +1087,7 @@ PNANOVDB_FORCE_INLINE void pnanovdb_write_vec3(pnanovdb_buf_t buf, pnanovdb_addr
#define PNANOVDB_MAGIC_FILE 0x324244566f6e614eUL// "NanoVDB2" in hex - little endian (uint64_t)
#define PNANOVDB_MAJOR_VERSION_NUMBER 32// reflects changes to the ABI
#define PNANOVDB_MINOR_VERSION_NUMBER 7// reflects changes to the API but not ABI
#define PNANOVDB_MINOR_VERSION_NUMBER 9// reflects changes to the API but not ABI
#define PNANOVDB_PATCH_VERSION_NUMBER 0// reflects bug-fixes with no ABI or API changes
#define PNANOVDB_GRID_TYPE_UNKNOWN 0
@@ -957,14 +1111,16 @@ PNANOVDB_FORCE_INLINE void pnanovdb_write_vec3(pnanovdb_buf_t buf, pnanovdb_addr
#define PNANOVDB_GRID_TYPE_VEC4D 18
#define PNANOVDB_GRID_TYPE_INDEX 19
#define PNANOVDB_GRID_TYPE_ONINDEX 20
#define PNANOVDB_GRID_TYPE_INDEXMASK 21
#define PNANOVDB_GRID_TYPE_ONINDEXMASK 22
//#define PNANOVDB_GRID_TYPE_INDEXMASK 21 // retired - available for future use
//#define PNANOVDB_GRID_TYPE_ONINDEXMASK 22 // retired - available for future use
#define PNANOVDB_GRID_TYPE_POINTINDEX 23
#define PNANOVDB_GRID_TYPE_VEC3U8 24
#define PNANOVDB_GRID_TYPE_VEC3U16 25
#define PNANOVDB_GRID_TYPE_UINT8 26
#define PNANOVDB_GRID_TYPE_END 27
#define PNANOVDB_GRID_TYPE_CAP 32
#define PNANOVDB_GRID_CLASS_UNKNOWN 0
#define PNANOVDB_GRID_CLASS_LEVEL_SET 1 // narrow band level set, e.g. SDF
#define PNANOVDB_GRID_CLASS_FOG_VOLUME 2 // fog volume, e.g. density
@@ -989,22 +1145,22 @@ PNANOVDB_FORCE_INLINE void pnanovdb_write_vec3(pnanovdb_buf_t buf, pnanovdb_addr
#define PNANOVDB_LEAF_TYPE_LITE 1
#define PNANOVDB_LEAF_TYPE_FP 2
#define PNANOVDB_LEAF_TYPE_INDEX 3
#define PNANOVDB_LEAF_TYPE_INDEXMASK 4
//#define PNANOVDB_LEAF_TYPE_INDEXMASK 4 // retired - available for future use
#define PNANOVDB_LEAF_TYPE_POINTINDEX 5
// BuildType = Unknown, float, double, int16_t, int32_t, int64_t, Vec3f, Vec3d, Mask, ...
// bit count of values in leaf nodes, i.e. 8*sizeof(*nanovdb::LeafNode<BuildType>::mValues) or zero if no values are stored
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_value_strides_bits[PNANOVDB_GRID_TYPE_END] = { 0, 32, 64, 16, 32, 64, 96, 192, 0, 16, 32, 1, 32, 4, 8, 16, 0, 128, 256, 0, 0, 0, 0, 16, 24, 48, 8 };
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_value_strides_bits[PNANOVDB_GRID_TYPE_CAP] = { 0, 32, 64, 16, 32, 64, 96, 192, 0, 16, 32, 1, 32, 4, 8, 16, 0, 128, 256, 0, 0, 0, 0, 16, 24, 48, 8, 0 };
// bit count of the Tile union in InternalNodes, i.e. 8*sizeof(nanovdb::InternalData::Tile)
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_table_strides_bits[PNANOVDB_GRID_TYPE_END] = { 64, 64, 64, 64, 64, 64, 128, 192, 64, 64, 64, 64, 64, 64, 64, 64, 64, 128, 256, 64, 64, 64, 64, 64, 64, 64, 64 };
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_table_strides_bits[PNANOVDB_GRID_TYPE_CAP] = { 64, 64, 64, 64, 64, 64, 128, 192, 64, 64, 64, 64, 64, 64, 64, 64, 64, 128, 256, 64, 64, 64, 64, 64, 64, 64, 64, 64 };
// bit count of min/max values, i.e. 8*sizeof(nanovdb::LeafData::mMinimum) or zero if no min/max exists
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_minmax_strides_bits[PNANOVDB_GRID_TYPE_END] = { 0, 32, 64, 16, 32, 64, 96, 192, 8, 16, 32, 8, 32, 32, 32, 32, 32, 128, 256, 64, 64, 64, 64, 64, 24, 48, 8 };
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_minmax_strides_bits[PNANOVDB_GRID_TYPE_CAP] = { 0, 32, 64, 16, 32, 64, 96, 192, 8, 16, 32, 8, 32, 32, 32, 32, 32, 128, 256, 64, 64, 0, 0, 64, 24, 48, 8, 0 };
// bit alignment of the value type, controlled by the smallest native type, which is why it is always 0, 8, 16, 32, or 64, e.g. for Vec3f it is 32
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_minmax_aligns_bits[PNANOVDB_GRID_TYPE_END] = { 0, 32, 64, 16, 32, 64, 32, 64, 8, 16, 32, 8, 32, 32, 32, 32, 32, 32, 64, 64, 64, 64, 64, 64, 8, 16, 8 };
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_minmax_aligns_bits[PNANOVDB_GRID_TYPE_CAP] = { 0, 32, 64, 16, 32, 64, 32, 64, 8, 16, 32, 8, 32, 32, 32, 32, 32, 32, 64, 64, 64, 0, 0, 64, 8, 16, 8, 0 };
// bit alignment of the stats (avg/std-dev) types, e.g. 8*sizeof(nanovdb::LeafData::mAverage)
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_stat_strides_bits[PNANOVDB_GRID_TYPE_END] = { 0, 32, 64, 32, 32, 64, 32, 64, 8, 32, 32, 8, 32, 32, 32, 32, 32, 32, 64, 64, 64, 64, 64, 64, 32, 32, 32 };
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_stat_strides_bits[PNANOVDB_GRID_TYPE_CAP] = { 0, 32, 64, 32, 32, 64, 32, 64, 8, 32, 32, 8, 32, 32, 32, 32, 32, 32, 64, 64, 64, 0, 0, 64, 32, 32, 32, 0 };
// one of the 4 leaf types defined above, e.g. PNANOVDB_LEAF_TYPE_INDEX = 3
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_leaf_type[PNANOVDB_GRID_TYPE_END] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 2, 2, 2, 2, 0, 0, 3, 3, 4, 4, 5, 0, 0, 0 };
PNANOVDB_STATIC_CONST pnanovdb_uint32_t pnanovdb_grid_type_leaf_type[PNANOVDB_GRID_TYPE_CAP] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 2, 2, 2, 2, 0, 0, 3, 3, 0, 0, 5, 0, 0, 0, 0 };
struct pnanovdb_map_t
{
@@ -1099,7 +1255,9 @@ struct pnanovdb_grid_t
pnanovdb_uint32_t grid_type; // 4 bytes, 636
pnanovdb_int64_t blind_metadata_offset; // 8 bytes, 640
pnanovdb_uint32_t blind_metadata_count; // 4 bytes, 648
pnanovdb_uint32_t pad[5]; // 20 bytes, 652
pnanovdb_uint32_t data0; // 4 bytes, 652
pnanovdb_uint64_t data1; // 8 bytes, 656
pnanovdb_uint64_t data2; // 8 bytes, 664
};
PNANOVDB_STRUCT_TYPEDEF(pnanovdb_grid_t)
struct pnanovdb_grid_handle_t { pnanovdb_address_t address; };
@@ -1122,6 +1280,9 @@ PNANOVDB_STRUCT_TYPEDEF(pnanovdb_grid_handle_t)
#define PNANOVDB_GRID_OFF_GRID_TYPE 636
#define PNANOVDB_GRID_OFF_BLIND_METADATA_OFFSET 640
#define PNANOVDB_GRID_OFF_BLIND_METADATA_COUNT 648
#define PNANOVDB_GRID_OFF_DATA0 652
#define PNANOVDB_GRID_OFF_DATA1 656
#define PNANOVDB_GRID_OFF_DATA2 664
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_grid_get_magic(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p) {
return pnanovdb_read_uint64(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRID_OFF_MAGIC));
@@ -1170,6 +1331,15 @@ PNANOVDB_FORCE_INLINE pnanovdb_int64_t pnanovdb_grid_get_blind_metadata_offset(p
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_grid_get_blind_metadata_count(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p) {
return pnanovdb_read_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRID_OFF_BLIND_METADATA_COUNT));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_grid_get_data0(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p) {
return pnanovdb_read_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRID_OFF_DATA0));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_grid_get_data1(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p) {
return pnanovdb_read_uint64(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRID_OFF_DATA1));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_grid_get_data2(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p) {
return pnanovdb_read_uint64(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRID_OFF_DATA2));
}
PNANOVDB_FORCE_INLINE void pnanovdb_grid_set_magic(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p, pnanovdb_uint64_t magic) {
pnanovdb_write_uint64(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRID_OFF_MAGIC), magic);
@@ -1213,6 +1383,15 @@ PNANOVDB_FORCE_INLINE void pnanovdb_grid_set_blind_metadata_offset(pnanovdb_buf_
PNANOVDB_FORCE_INLINE void pnanovdb_grid_set_blind_metadata_count(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p, pnanovdb_uint32_t metadata_count) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRID_OFF_BLIND_METADATA_COUNT), metadata_count);
}
PNANOVDB_FORCE_INLINE void pnanovdb_grid_set_data0(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p, pnanovdb_uint32_t data0) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRID_OFF_DATA0), data0);
}
PNANOVDB_FORCE_INLINE void pnanovdb_grid_set_data1(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p, pnanovdb_uint64_t data1) {
pnanovdb_write_uint64(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRID_OFF_DATA1), data1);
}
PNANOVDB_FORCE_INLINE void pnanovdb_grid_set_data2(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p, pnanovdb_uint64_t data2) {
pnanovdb_write_uint64(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRID_OFF_DATA2), data2);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_make_version(pnanovdb_uint32_t major, pnanovdb_uint32_t minor, pnanovdb_uint32_t patch_num)
{
@@ -1232,6 +1411,25 @@ PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_version_get_patch(pnanovdb_uint
return version & ((1u << 10u) - 1u);
}
#define PNANOVDB_GRIDBLINDMETADATA_CLASS_UNKNOWN 0
#define PNANOVDB_GRIDBLINDMETADATA_CLASS_INDEX_ARRAY 1
#define PNANOVDB_GRIDBLINDMETADATA_CLASS_ATTRIBUTE_ARRAY 2
#define PNANOVDB_GRIDBLINDMETADATA_CLASS_GRID_NAME 3
#define PNANOVDB_GRIDBLINDMETADATA_CLASS_CHANNEL_ARRAY 4
#define PNANOVDB_GRIDBLINDMETADATA_CLASS_END 5
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_UNKNOWN 0
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_POSITION 1
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_COLOR 2
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_NORMAL 3
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_RADIUS 4
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_VELOCITY 5
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_POINT_ID 6
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_WORLD_COORDS 7
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_GRID_COORDS 8
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_VOXEL_COORDS 9
#define PNANOVDB_GRIDBLINDMETADATA_SEMANTIC_END 10
struct pnanovdb_gridblindmetadata_t
{
pnanovdb_int64_t data_offset; // 8 bytes, 0
@@ -1278,6 +1476,28 @@ PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_gridblindmetadata_get_name(pnan
return pnanovdb_read_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRIDBLINDMETADATA_OFF_NAME + 4u * index));
}
PNANOVDB_FORCE_INLINE void pnanovdb_gridblindmetadata_set_data_offset(pnanovdb_buf_t buf, pnanovdb_gridblindmetadata_handle_t p, pnanovdb_int64_t data_offset) {
pnanovdb_write_int64(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRIDBLINDMETADATA_OFF_DATA_OFFSET), data_offset);
}
PNANOVDB_FORCE_INLINE void pnanovdb_gridblindmetadata_set_value_count(pnanovdb_buf_t buf, pnanovdb_gridblindmetadata_handle_t p, pnanovdb_uint64_t value_count) {
pnanovdb_write_uint64(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRIDBLINDMETADATA_OFF_VALUE_COUNT), value_count);
}
PNANOVDB_FORCE_INLINE void pnanovdb_gridblindmetadata_set_value_size(pnanovdb_buf_t buf, pnanovdb_gridblindmetadata_handle_t p, pnanovdb_uint32_t value_size) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRIDBLINDMETADATA_OFF_VALUE_SIZE), value_size);
}
PNANOVDB_FORCE_INLINE void pnanovdb_gridblindmetadata_set_semantic(pnanovdb_buf_t buf, pnanovdb_gridblindmetadata_handle_t p, pnanovdb_uint32_t semantic) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRIDBLINDMETADATA_OFF_SEMANTIC), semantic);
}
PNANOVDB_FORCE_INLINE void pnanovdb_gridblindmetadata_set_data_class(pnanovdb_buf_t buf, pnanovdb_gridblindmetadata_handle_t p, pnanovdb_uint32_t data_class) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRIDBLINDMETADATA_OFF_DATA_CLASS), data_class);
}
PNANOVDB_FORCE_INLINE void pnanovdb_gridblindmetadata_set_data_type(pnanovdb_buf_t buf, pnanovdb_gridblindmetadata_handle_t p, pnanovdb_uint32_t data_type) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRIDBLINDMETADATA_OFF_DATA_TYPE), data_type);
}
PNANOVDB_FORCE_INLINE void pnanovdb_gridblindmetadata_set_name(pnanovdb_buf_t buf, pnanovdb_gridblindmetadata_handle_t p, pnanovdb_uint32_t index, pnanovdb_uint32_t name) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_GRIDBLINDMETADATA_OFF_NAME + 4u * index), name);
}
struct pnanovdb_tree_t
{
pnanovdb_uint64_t node_offset_leaf;
@@ -1287,9 +1507,9 @@ struct pnanovdb_tree_t
pnanovdb_uint32_t node_count_leaf;
pnanovdb_uint32_t node_count_lower;
pnanovdb_uint32_t node_count_upper;
pnanovdb_uint32_t tile_count_leaf;
pnanovdb_uint32_t tile_count_lower;
pnanovdb_uint32_t tile_count_upper;
pnanovdb_uint32_t tile_count_root;
pnanovdb_uint64_t voxel_count;
};
PNANOVDB_STRUCT_TYPEDEF(pnanovdb_tree_t)
@@ -1305,9 +1525,9 @@ PNANOVDB_STRUCT_TYPEDEF(pnanovdb_tree_handle_t)
#define PNANOVDB_TREE_OFF_NODE_COUNT_LEAF 32
#define PNANOVDB_TREE_OFF_NODE_COUNT_LOWER 36
#define PNANOVDB_TREE_OFF_NODE_COUNT_UPPER 40
#define PNANOVDB_TREE_OFF_TILE_COUNT_LEAF 44
#define PNANOVDB_TREE_OFF_TILE_COUNT_LOWER 48
#define PNANOVDB_TREE_OFF_TILE_COUNT_UPPER 52
#define PNANOVDB_TREE_OFF_TILE_COUNT_LOWER 44
#define PNANOVDB_TREE_OFF_TILE_COUNT_UPPER 48
#define PNANOVDB_TREE_OFF_TILE_COUNT_ROOT 52
#define PNANOVDB_TREE_OFF_VOXEL_COUNT 56
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_tree_get_node_offset_leaf(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p) {
@@ -1331,15 +1551,15 @@ PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_tree_get_node_count_lower(pnano
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_tree_get_node_count_upper(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p) {
return pnanovdb_read_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_NODE_COUNT_UPPER));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_tree_get_tile_count_leaf(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p) {
return pnanovdb_read_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_TILE_COUNT_LEAF));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_tree_get_tile_count_lower(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p) {
return pnanovdb_read_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_TILE_COUNT_LOWER));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_tree_get_tile_count_upper(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p) {
return pnanovdb_read_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_TILE_COUNT_UPPER));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_tree_get_tile_count_root(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p) {
return pnanovdb_read_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_TILE_COUNT_ROOT));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_tree_get_voxel_count(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p) {
return pnanovdb_read_uint64(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_VOXEL_COUNT));
}
@@ -1365,15 +1585,15 @@ PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_node_count_lower(pnanovdb_buf_t buf
PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_node_count_upper(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p, pnanovdb_uint32_t node_count_upper) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_NODE_COUNT_UPPER), node_count_upper);
}
PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_tile_count_leaf(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p, pnanovdb_uint32_t tile_count_leaf) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_TILE_COUNT_LEAF), tile_count_leaf);
}
PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_tile_count_lower(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p, pnanovdb_uint32_t tile_count_lower) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_TILE_COUNT_LOWER), tile_count_lower);
}
PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_tile_count_upper(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p, pnanovdb_uint32_t tile_count_upper) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_TILE_COUNT_UPPER), tile_count_upper);
}
PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_tile_count_root(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p, pnanovdb_uint32_t tile_count_root) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_TILE_COUNT_ROOT), tile_count_root);
}
PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_voxel_count(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p, pnanovdb_uint64_t voxel_count) {
pnanovdb_write_uint64(buf, pnanovdb_address_offset(p.address, PNANOVDB_TREE_OFF_VOXEL_COUNT), voxel_count);
}
@@ -1501,6 +1721,13 @@ PNANOVDB_FORCE_INLINE void pnanovdb_upper_set_bbox_min(pnanovdb_buf_t buf, pnano
PNANOVDB_FORCE_INLINE void pnanovdb_upper_set_bbox_max(pnanovdb_buf_t buf, pnanovdb_upper_handle_t p, PNANOVDB_IN(pnanovdb_coord_t) bbox_max) {
pnanovdb_write_coord(buf, pnanovdb_address_offset(p.address, PNANOVDB_UPPER_OFF_BBOX_MAX), bbox_max);
}
PNANOVDB_FORCE_INLINE void pnanovdb_upper_set_value_mask(pnanovdb_buf_t buf, pnanovdb_upper_handle_t p, pnanovdb_uint32_t bit_index, pnanovdb_bool_t value) {
pnanovdb_address_t addr = pnanovdb_address_offset(p.address, PNANOVDB_UPPER_OFF_VALUE_MASK + 4u * (bit_index >> 5u));
pnanovdb_uint32_t valueMask = pnanovdb_read_uint32(buf, addr);
if (!value) { valueMask &= ~(1u << (bit_index & 31u)); }
if (value) valueMask |= (1u << (bit_index & 31u));
pnanovdb_write_uint32(buf, addr, valueMask);
}
PNANOVDB_FORCE_INLINE void pnanovdb_upper_set_child_mask(pnanovdb_buf_t buf, pnanovdb_upper_handle_t p, pnanovdb_uint32_t bit_index, pnanovdb_bool_t value) {
pnanovdb_address_t addr = pnanovdb_address_offset(p.address, PNANOVDB_UPPER_OFF_CHILD_MASK + 4u * (bit_index >> 5u));
pnanovdb_uint32_t valueMask = pnanovdb_read_uint32(buf, addr);
@@ -1556,6 +1783,13 @@ PNANOVDB_FORCE_INLINE void pnanovdb_lower_set_bbox_min(pnanovdb_buf_t buf, pnano
PNANOVDB_FORCE_INLINE void pnanovdb_lower_set_bbox_max(pnanovdb_buf_t buf, pnanovdb_lower_handle_t p, PNANOVDB_IN(pnanovdb_coord_t) bbox_max) {
pnanovdb_write_coord(buf, pnanovdb_address_offset(p.address, PNANOVDB_LOWER_OFF_BBOX_MAX), bbox_max);
}
PNANOVDB_FORCE_INLINE void pnanovdb_lower_set_value_mask(pnanovdb_buf_t buf, pnanovdb_lower_handle_t p, pnanovdb_uint32_t bit_index, pnanovdb_bool_t value) {
pnanovdb_address_t addr = pnanovdb_address_offset(p.address, PNANOVDB_LOWER_OFF_VALUE_MASK + 4u * (bit_index >> 5u));
pnanovdb_uint32_t valueMask = pnanovdb_read_uint32(buf, addr);
if (!value) { valueMask &= ~(1u << (bit_index & 31u)); }
if (value) valueMask |= (1u << (bit_index & 31u));
pnanovdb_write_uint32(buf, addr, valueMask);
}
PNANOVDB_FORCE_INLINE void pnanovdb_lower_set_child_mask(pnanovdb_buf_t buf, pnanovdb_lower_handle_t p, pnanovdb_uint32_t bit_index, pnanovdb_bool_t value) {
pnanovdb_address_t addr = pnanovdb_address_offset(p.address, PNANOVDB_LOWER_OFF_CHILD_MASK + 4u * (bit_index >> 5u));
pnanovdb_uint32_t valueMask = pnanovdb_read_uint32(buf, addr);
@@ -1604,6 +1838,13 @@ PNANOVDB_FORCE_INLINE void pnanovdb_leaf_set_bbox_min(pnanovdb_buf_t buf, pnanov
PNANOVDB_FORCE_INLINE void pnanovdb_leaf_set_bbox_dif_and_flags(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t p, pnanovdb_uint32_t bbox_dif_and_flags) {
pnanovdb_write_uint32(buf, pnanovdb_address_offset(p.address, PNANOVDB_LEAF_OFF_BBOX_DIF_AND_FLAGS), bbox_dif_and_flags);
}
PNANOVDB_FORCE_INLINE void pnanovdb_leaf_set_value_mask(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t p, pnanovdb_uint32_t bit_index, pnanovdb_bool_t value) {
pnanovdb_address_t addr = pnanovdb_address_offset(p.address, PNANOVDB_LEAF_OFF_VALUE_MASK + 4u * (bit_index >> 5u));
pnanovdb_uint32_t valueMask = pnanovdb_read_uint32(buf, addr);
if (!value) { valueMask &= ~(1u << (bit_index & 31u)); }
if (value) valueMask |= (1u << (bit_index & 31u));
pnanovdb_write_uint32(buf, addr, valueMask);
}
struct pnanovdb_grid_type_constants_t
{
@@ -1639,7 +1880,7 @@ struct pnanovdb_grid_type_constants_t
PNANOVDB_STRUCT_TYPEDEF(pnanovdb_grid_type_constants_t)
// The following table with offsets will nedd to be updates as new GridTypes are added in NanoVDB.h
PNANOVDB_STATIC_CONST pnanovdb_grid_type_constants_t pnanovdb_grid_type_constants[PNANOVDB_GRID_TYPE_END] =
PNANOVDB_STATIC_CONST pnanovdb_grid_type_constants_t pnanovdb_grid_type_constants[PNANOVDB_GRID_TYPE_CAP] =
{
{28, 28, 28, 28, 28, 32, 0, 8, 20, 32, 8224, 8224, 8224, 8224, 8224, 270368, 1056, 1056, 1056, 1056, 1056, 33824, 80, 80, 80, 80, 96, 96},
{28, 32, 36, 40, 44, 64, 32, 8, 20, 32, 8224, 8228, 8232, 8236, 8256, 270400, 1056, 1060, 1064, 1068, 1088, 33856, 80, 84, 88, 92, 96, 2144},
@@ -1662,12 +1903,13 @@ PNANOVDB_STATIC_CONST pnanovdb_grid_type_constants_t pnanovdb_grid_type_constant
{32, 64, 96, 128, 136, 160, 256, 32, 24, 64, 8224, 8256, 8288, 8296, 8320, 1056896, 1056, 1088, 1120, 1128, 1152, 132224, 80, 112, 144, 152, 160, 16544},
{32, 40, 48, 56, 64, 96, 0, 8, 24, 32, 8224, 8232, 8240, 8248, 8256, 270400, 1056, 1064, 1072, 1080, 1088, 33856, 80, 80, 80, 80, 80, 96},
{32, 40, 48, 56, 64, 96, 0, 8, 24, 32, 8224, 8232, 8240, 8248, 8256, 270400, 1056, 1064, 1072, 1080, 1088, 33856, 80, 80, 80, 80, 80, 96},
{32, 40, 48, 56, 64, 96, 0, 8, 24, 32, 8224, 8232, 8240, 8248, 8256, 270400, 1056, 1064, 1072, 1080, 1088, 33856, 80, 80, 80, 80, 80, 160},
{32, 40, 48, 56, 64, 96, 0, 8, 24, 32, 8224, 8232, 8240, 8248, 8256, 270400, 1056, 1064, 1072, 1080, 1088, 33856, 80, 80, 80, 80, 80, 160},
{28, 28, 28, 28, 28, 32, 0, 8, 20, 32, 8224, 8224, 8224, 8224, 8224, 270368, 1056, 1056, 1056, 1056, 1056, 33824, 80, 80, 80, 80, 96, 96},
{28, 28, 28, 28, 28, 32, 0, 8, 20, 32, 8224, 8224, 8224, 8224, 8224, 270368, 1056, 1056, 1056, 1056, 1056, 33824, 80, 80, 80, 80, 96, 96},
{32, 40, 48, 56, 64, 96, 16, 8, 24, 32, 8224, 8232, 8240, 8248, 8256, 270400, 1056, 1064, 1072, 1080, 1088, 33856, 80, 88, 96, 96, 96, 1120},
{28, 31, 34, 40, 44, 64, 24, 8, 20, 32, 8224, 8227, 8232, 8236, 8256, 270400, 1056, 1059, 1064, 1068, 1088, 33856, 80, 83, 88, 92, 96, 1632},
{28, 34, 40, 48, 52, 64, 48, 8, 20, 32, 8224, 8230, 8236, 8240, 8256, 270400, 1056, 1062, 1068, 1072, 1088, 33856, 80, 86, 92, 96, 128, 3200},
{28, 29, 30, 32, 36, 64, 8, 8, 20, 32, 8224, 8225, 8228, 8232, 8256, 270400, 1056, 1057, 1060, 1064, 1088, 33856, 80, 81, 84, 88, 96, 608},
{28, 28, 28, 28, 28, 32, 0, 8, 20, 32, 8224, 8224, 8224, 8224, 8224, 270368, 1056, 1056, 1056, 1056, 1056, 33824, 80, 80, 80, 80, 96, 96},
};
// ------------------------------------------------ Basic Lookup -----------------------------------------------------------
@@ -1719,6 +1961,11 @@ PNANOVDB_FORCE_INLINE pnanovdb_root_tile_handle_t pnanovdb_root_get_tile_zero(pn
return tile;
}
PNANOVDB_FORCE_INLINE pnanovdb_bool_t pnanovdb_root_tile_get_child_mask(pnanovdb_buf_t buf, pnanovdb_root_tile_handle_t tile)
{
return !pnanovdb_int64_is_zero(pnanovdb_root_tile_get_child(buf, tile));
}
PNANOVDB_FORCE_INLINE pnanovdb_upper_handle_t pnanovdb_root_get_child(pnanovdb_grid_type_t grid_type, pnanovdb_buf_t buf, pnanovdb_root_handle_t root, pnanovdb_root_tile_handle_t tile)
{
pnanovdb_upper_handle_t upper = { root.address };
@@ -1726,6 +1973,11 @@ PNANOVDB_FORCE_INLINE pnanovdb_upper_handle_t pnanovdb_root_get_child(pnanovdb_g
return upper;
}
PNANOVDB_FORCE_INLINE void pnanovdb_root_set_child(pnanovdb_grid_type_t grid_type, pnanovdb_buf_t buf, pnanovdb_root_handle_t root, pnanovdb_root_tile_handle_t tile, pnanovdb_upper_handle_t upper)
{
pnanovdb_root_tile_set_child(buf, tile, pnanovdb_address_diff(upper.address, root.address));
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_coord_to_key(PNANOVDB_IN(pnanovdb_coord_t) ijk)
{
#if defined(PNANOVDB_NATIVE_64)
@@ -1760,6 +2012,27 @@ PNANOVDB_FORCE_INLINE pnanovdb_root_tile_handle_t pnanovdb_root_find_tile(pnanov
return null_handle;
}
// ----------------------------- Grid Type Safe Set ---------------------------------------
PNANOVDB_FORCE_INLINE void pnanovdb_grid_set_first_gridblindmetadata(pnanovdb_buf_t buf, pnanovdb_grid_handle_t p, pnanovdb_gridblindmetadata_handle_t gridblindmetadata) {
pnanovdb_grid_set_blind_metadata_offset(buf, p, pnanovdb_address_diff(gridblindmetadata.address, p.address));
}
// ----------------------------- Tree Type Safe Set ---------------------------------------
PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_first_leaf(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p, pnanovdb_leaf_handle_t leaf) {
pnanovdb_tree_set_node_offset_leaf(buf, p, pnanovdb_address_diff(leaf.address, p.address));
}
PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_first_lower(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p, pnanovdb_lower_handle_t lower) {
pnanovdb_tree_set_node_offset_lower(buf, p, pnanovdb_address_diff(lower.address, p.address));
}
PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_first_upper(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p, pnanovdb_upper_handle_t upper) {
pnanovdb_tree_set_node_offset_upper(buf, p, pnanovdb_address_diff(upper.address, p.address));
}
PNANOVDB_FORCE_INLINE void pnanovdb_tree_set_first_root(pnanovdb_buf_t buf, pnanovdb_tree_handle_t p, pnanovdb_root_handle_t root) {
pnanovdb_tree_set_node_offset_root(buf, p, pnanovdb_address_diff(root.address, p.address));
}
// ----------------------------- Leaf Node ---------------------------------------
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_leaf_coord_to_offset(PNANOVDB_IN(pnanovdb_coord_t) ijk)
@@ -1880,57 +2153,6 @@ PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_index_get_value_index(pnan
return pnanovdb_uint64_offset(offset, n);
}
// ----------------------------- Leaf IndexMask Specialization ---------------------------------------
PNANOVDB_FORCE_INLINE pnanovdb_bool_t pnanovdb_leaf_indexmask_has_stats(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf)
{
return pnanovdb_leaf_index_has_stats(buf, leaf);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_indexmask_get_min_index(pnanovdb_buf_t buf, pnanovdb_address_t min_address)
{
return pnanovdb_leaf_index_get_min_index(buf, min_address);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_indexmask_get_max_index(pnanovdb_buf_t buf, pnanovdb_address_t max_address)
{
return pnanovdb_leaf_index_get_max_index(buf, max_address);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_indexmask_get_ave_index(pnanovdb_buf_t buf, pnanovdb_address_t ave_address)
{
return pnanovdb_leaf_index_get_ave_index(buf, ave_address);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_indexmask_get_dev_index(pnanovdb_buf_t buf, pnanovdb_address_t dev_address)
{
return pnanovdb_leaf_index_get_dev_index(buf, dev_address);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_indexmask_get_value_index(pnanovdb_buf_t buf, pnanovdb_address_t value_address, PNANOVDB_IN(pnanovdb_coord_t) ijk)
{
return pnanovdb_leaf_index_get_value_index(buf, value_address, ijk);
}
PNANOVDB_FORCE_INLINE pnanovdb_bool_t pnanovdb_leaf_indexmask_get_mask_bit(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf, pnanovdb_uint32_t n)
{
pnanovdb_uint32_t word_idx = n >> 5;
pnanovdb_uint32_t bit_idx = n & 31;
pnanovdb_uint32_t val_mask =
pnanovdb_read_uint32(buf, pnanovdb_address_offset(leaf.address, 96u + 4u * word_idx));
return (val_mask & (1u << bit_idx)) != 0u;
}
PNANOVDB_FORCE_INLINE void pnanovdb_leaf_indexmask_set_mask_bit(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf, pnanovdb_uint32_t n, pnanovdb_bool_t v)
{
pnanovdb_uint32_t word_idx = n >> 5;
pnanovdb_uint32_t bit_idx = n & 31;
pnanovdb_uint32_t val_mask =
pnanovdb_read_uint32(buf, pnanovdb_address_offset(leaf.address, 96u + 4u * word_idx));
if (v)
{
val_mask = val_mask | (1u << bit_idx);
}
else
{
val_mask = val_mask & ~(1u << bit_idx);
}
pnanovdb_write_uint32(buf, pnanovdb_address_offset(leaf.address, 96u + 4u * word_idx), val_mask);
}
// ----------------------------- Leaf OnIndex Specialization ---------------------------------------
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_leaf_onindex_get_value_count(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf)
@@ -2022,65 +2244,6 @@ PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_onindex_get_value_index(pn
return value_index;
}
// ----------------------------- Leaf OnIndexMask Specialization ---------------------------------------
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_leaf_onindexmask_get_value_count(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf)
{
return pnanovdb_leaf_onindex_get_value_count(buf, leaf);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_onindexmask_get_last_offset(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf)
{
return pnanovdb_leaf_onindex_get_last_offset(buf, leaf);
}
PNANOVDB_FORCE_INLINE pnanovdb_bool_t pnanovdb_leaf_onindexmask_has_stats(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf)
{
return pnanovdb_leaf_onindex_has_stats(buf, leaf);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_onindexmask_get_min_index(pnanovdb_buf_t buf, pnanovdb_address_t min_address)
{
return pnanovdb_leaf_onindex_get_min_index(buf, min_address);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_onindexmask_get_max_index(pnanovdb_buf_t buf, pnanovdb_address_t max_address)
{
return pnanovdb_leaf_onindex_get_max_index(buf, max_address);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_onindexmask_get_ave_index(pnanovdb_buf_t buf, pnanovdb_address_t ave_address)
{
return pnanovdb_leaf_onindex_get_ave_index(buf, ave_address);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_onindexmask_get_dev_index(pnanovdb_buf_t buf, pnanovdb_address_t dev_address)
{
return pnanovdb_leaf_onindex_get_dev_index(buf, dev_address);
}
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_onindexmask_get_value_index(pnanovdb_buf_t buf, pnanovdb_address_t value_address, PNANOVDB_IN(pnanovdb_coord_t) ijk)
{
return pnanovdb_leaf_onindex_get_value_index(buf, value_address, ijk);
}
PNANOVDB_FORCE_INLINE pnanovdb_bool_t pnanovdb_leaf_onindexmask_get_mask_bit(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf, pnanovdb_uint32_t n)
{
pnanovdb_uint32_t word_idx = n >> 5;
pnanovdb_uint32_t bit_idx = n & 31;
pnanovdb_uint32_t val_mask =
pnanovdb_read_uint32(buf, pnanovdb_address_offset(leaf.address, 96u + 4u * word_idx));
return (val_mask & (1u << bit_idx)) != 0u;
}
PNANOVDB_FORCE_INLINE void pnanovdb_leaf_onindexmask_set_mask_bit(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf, pnanovdb_uint32_t n, pnanovdb_bool_t v)
{
pnanovdb_uint32_t word_idx = n >> 5;
pnanovdb_uint32_t bit_idx = n & 31;
pnanovdb_uint32_t val_mask =
pnanovdb_read_uint32(buf, pnanovdb_address_offset(leaf.address, 96u + 4u * word_idx));
if (v)
{
val_mask = val_mask | (1u << bit_idx);
}
else
{
val_mask = val_mask & ~(1u << bit_idx);
}
pnanovdb_write_uint32(buf, pnanovdb_address_offset(leaf.address, 96u + 4u * word_idx), val_mask);
}
// ----------------------------- Leaf PointIndex Specialization ---------------------------------------
PNANOVDB_FORCE_INLINE pnanovdb_uint64_t pnanovdb_leaf_pointindex_get_offset(pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf)
@@ -2210,6 +2373,17 @@ PNANOVDB_FORCE_INLINE pnanovdb_address_t pnanovdb_lower_get_value_address(pnanov
return pnanovdb_lower_get_value_address_and_level(grid_type, buf, lower, ijk, PNANOVDB_REF(level));
}
PNANOVDB_FORCE_INLINE void pnanovdb_lower_set_table_child(pnanovdb_grid_type_t grid_type, pnanovdb_buf_t buf, pnanovdb_lower_handle_t node, pnanovdb_uint32_t n, pnanovdb_int64_t child)
{
pnanovdb_address_t table_address = pnanovdb_lower_get_table_address(grid_type, buf, node, n);
pnanovdb_write_int64(buf, table_address, child);
}
PNANOVDB_FORCE_INLINE void pnanovdb_lower_set_child(pnanovdb_grid_type_t grid_type, pnanovdb_buf_t buf, pnanovdb_lower_handle_t lower, pnanovdb_uint32_t n, pnanovdb_leaf_handle_t leaf)
{
pnanovdb_lower_set_table_child(grid_type, buf, lower, n, pnanovdb_address_diff(leaf.address, lower.address));
}
// ------------------------------------------------ Upper Node -----------------------------------------------------------
PNANOVDB_FORCE_INLINE pnanovdb_uint32_t pnanovdb_upper_coord_to_offset(PNANOVDB_IN(pnanovdb_coord_t) ijk)
@@ -2291,8 +2465,19 @@ PNANOVDB_FORCE_INLINE void pnanovdb_upper_set_table_child(pnanovdb_grid_type_t g
pnanovdb_write_int64(buf, bufAddress, child);
}
PNANOVDB_FORCE_INLINE void pnanovdb_upper_set_child(pnanovdb_grid_type_t grid_type, pnanovdb_buf_t buf, pnanovdb_upper_handle_t upper, pnanovdb_uint32_t n, pnanovdb_lower_handle_t lower)
{
pnanovdb_upper_set_table_child(grid_type, buf, upper, n, pnanovdb_address_diff(lower.address, upper.address));
}
// ------------------------------------------------ Root -----------------------------------------------------------
PNANOVDB_FORCE_INLINE pnanovdb_address_t pnanovdb_root_get_background_address(pnanovdb_grid_type_t grid_type, pnanovdb_buf_t buf, pnanovdb_root_handle_t root)
{
pnanovdb_uint32_t byte_offset = PNANOVDB_GRID_TYPE_GET(grid_type, root_off_background);
return pnanovdb_address_offset(root.address, byte_offset);
}
PNANOVDB_FORCE_INLINE pnanovdb_address_t pnanovdb_root_get_min_address(pnanovdb_grid_type_t grid_type, pnanovdb_buf_t buf, pnanovdb_root_handle_t root)
{
pnanovdb_uint32_t byte_offset = PNANOVDB_GRID_TYPE_GET(grid_type, root_off_min);
@@ -2591,12 +2776,6 @@ PNANOVDB_FORCE_INLINE pnanovdb_address_t pnanovdb_lower_get_value_address_and_ca
return pnanovdb_lower_get_value_address_and_level_and_cache(grid_type, buf, lower, ijk, acc, PNANOVDB_REF(level));
}
PNANOVDB_FORCE_INLINE void pnanovdb_lower_set_table_child(pnanovdb_grid_type_t grid_type, pnanovdb_buf_t buf, pnanovdb_lower_handle_t node, pnanovdb_uint32_t n, pnanovdb_int64_t child)
{
pnanovdb_address_t table_address = pnanovdb_lower_get_table_address(grid_type, buf, node, n);
pnanovdb_write_int64(buf, table_address, child);
}
PNANOVDB_FORCE_INLINE pnanovdb_address_t pnanovdb_upper_get_value_address_and_level_and_cache(pnanovdb_grid_type_t grid_type, pnanovdb_buf_t buf, pnanovdb_upper_handle_t upper, PNANOVDB_IN(pnanovdb_coord_t) ijk, PNANOVDB_INOUT(pnanovdb_readaccessor_t) acc, PNANOVDB_INOUT(pnanovdb_uint32_t) level)
{
pnanovdb_uint32_t n = pnanovdb_upper_coord_to_offset(ijk);
@@ -3227,6 +3406,19 @@ PNANOVDB_FORCE_INLINE pnanovdb_bool_t pnanovdb_hdda_update(PNANOVDB_INOUT(pnanov
{
return PNANOVDB_FALSE;
}
// compute valid voxel range
pnanovdb_coord_t voxel_max = {
(PNANOVDB_DEREF(hdda).voxel.x + PNANOVDB_DEREF(hdda).dim - 1) & (~(dim - 1)),
(PNANOVDB_DEREF(hdda).voxel.y + PNANOVDB_DEREF(hdda).dim - 1) & (~(dim - 1)),
(PNANOVDB_DEREF(hdda).voxel.z + PNANOVDB_DEREF(hdda).dim - 1) & (~(dim - 1))
};
pnanovdb_coord_t voxel_min = {
PNANOVDB_DEREF(hdda).voxel.x & (~(dim - 1)),
PNANOVDB_DEREF(hdda).voxel.y & (~(dim - 1)),
PNANOVDB_DEREF(hdda).voxel.z & (~(dim - 1))
};
PNANOVDB_DEREF(hdda).dim = dim;
pnanovdb_vec3_t pos = pnanovdb_vec3_add(
@@ -3237,6 +3429,14 @@ PNANOVDB_FORCE_INLINE pnanovdb_bool_t pnanovdb_hdda_update(PNANOVDB_INOUT(pnanov
PNANOVDB_DEREF(hdda).voxel = pnanovdb_hdda_pos_to_voxel(PNANOVDB_REF(pos), dim);
// clamp voxel to valid range
PNANOVDB_DEREF(hdda).voxel.x = PNANOVDB_DEREF(hdda).voxel.x < voxel_max.x ? PNANOVDB_DEREF(hdda).voxel.x : voxel_max.x;
PNANOVDB_DEREF(hdda).voxel.y = PNANOVDB_DEREF(hdda).voxel.y < voxel_max.y ? PNANOVDB_DEREF(hdda).voxel.y : voxel_max.y;
PNANOVDB_DEREF(hdda).voxel.z = PNANOVDB_DEREF(hdda).voxel.z < voxel_max.z ? PNANOVDB_DEREF(hdda).voxel.z : voxel_max.z;
PNANOVDB_DEREF(hdda).voxel.x = PNANOVDB_DEREF(hdda).voxel.x > voxel_min.x ? PNANOVDB_DEREF(hdda).voxel.x : voxel_min.x;
PNANOVDB_DEREF(hdda).voxel.y = PNANOVDB_DEREF(hdda).voxel.y > voxel_min.y ? PNANOVDB_DEREF(hdda).voxel.y : voxel_min.y;
PNANOVDB_DEREF(hdda).voxel.z = PNANOVDB_DEREF(hdda).voxel.z > voxel_min.z ? PNANOVDB_DEREF(hdda).voxel.z : voxel_min.z;
if (PNANOVDB_DEREF(hdda).step.x != 0)
{
PNANOVDB_DEREF(hdda).next.x = PNANOVDB_DEREF(hdda).tmin + (PNANOVDB_DEREF(hdda).voxel.x - pos.x) * dir_inv.x;
+1 -1
View File
@@ -7,7 +7,7 @@
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
project(NanoVDBExamples LANGUAGES CXX)
include(GNUInstallDirs)
+40 -30
View File
@@ -35,6 +35,8 @@ void usage [[noreturn]] (const std::string& progName, int exitStatus = EXIT_FAIL
<< "--fp8\tQuantize float grids to 8 bits\n"
<< "--fp16\tQuantize float grids to 16 bits\n"
<< "--fpN\tQuantize float grids to variable bit depth (use -a or -r to specify a tolerance)\n"
<< "--index\tProduce an IndexGrid where all float values are encoded as sidecar data\n"
<< "--onIndex\tProduce an IndexGrid where active float values are encoded as sidecar data\n"
<< "-g,--grid name\tConvert all grids matching the specified string name\n"
<< "-h,--help\tPrints this message\n"
<< "-r,--rel-error float\t Relative error tolerance used for variable bit depth quantization\n"
@@ -57,14 +59,14 @@ int main(int argc, char* argv[])
{
int exitStatus = EXIT_SUCCESS;
nanovdb::io::Codec codec = nanovdb::io::Codec::NONE;// compression codec for the file
nanovdb::tools::StatsMode sMode = nanovdb::tools::StatsMode::Default;
nanovdb::CheckMode cMode = nanovdb::CheckMode::Default;
nanovdb::GridType qMode = nanovdb::GridType::Unknown;//specify the quantization mode
bool verbose = false, overwrite = false, dither = false, absolute = true;
float tolerance = -1.0f;
std::string gridName;
std::vector<std::string> fileNames;
nanovdb::io::Codec codec = nanovdb::io::Codec::NONE;// compression codec for the file
nanovdb::tools::StatsMode sMode = nanovdb::tools::StatsMode::Default;
nanovdb::CheckMode cMode = nanovdb::CheckMode::Default;
nanovdb::GridType qMode = nanovdb::GridType::Unknown;//specify the quantization mode
bool verbose = false, overwrite = false, dither = false, absolute = true;
float tolerance = -1.0f;
std::string gridName;
std::vector<std::string> fileNames;
auto toLowerCase = [](std::string &str) {
std::transform(str.begin(), str.end(), str.begin(),[](unsigned char c){return std::tolower(c);});
};
@@ -85,6 +87,10 @@ int main(int argc, char* argv[])
qMode = nanovdb::GridType::Fp16;
} else if (arg == "--fpN") {
qMode = nanovdb::GridType::FpN;
} else if (arg == "--index") {
qMode = nanovdb::GridType::Index;
} else if (arg == "--onIndex") {
qMode = nanovdb::GridType::OnIndex;
} else if (arg == "-h" || arg == "--help") {
usage(argv[0], EXIT_SUCCESS);
} else if (arg == "-b" || arg == "--blosc") {
@@ -169,17 +175,22 @@ int main(int argc, char* argv[])
std::cerr << "Expected at least one input file followed by exactly one output file\n" << std::endl;
usage(argv[0]);
}
const std::string outputFile = fileNames.back();
const std::string outputFile = fileNames.back();// last file is always the output file
const std::string ext = outputFile.substr(outputFile.find_last_of(".") + 1);
bool toNanoVDB = false;
if (ext == "nvdb") {
toNanoVDB = true;
} else if (ext != "vdb") {
} else if (ext == "vdb") {
if (qMode != nanovdb::GridType::Unknown) {
std::cerr << "The options are incompatible an output of type OpenVDB" << std::endl;
usage(argv[0]);
}
} else {
std::cerr << "Unrecognized file extension: \"" << ext << "\"\n" << std::endl;
usage(argv[0]);
}
fileNames.pop_back();
fileNames.pop_back();// remove the output file name
if (!overwrite) {
std::ifstream is(outputFile, std::ios::in | std::ios::binary);
@@ -203,9 +214,16 @@ int main(int argc, char* argv[])
auto openToNano = [&](const openvdb::GridBase::Ptr& base)
{
using SrcGridT = openvdb::FloatGrid;
if (auto floatGrid = openvdb::GridBase::grid<SrcGridT>(base)) {
nanovdb::tools::CreateNanoGrid<SrcGridT> s(*floatGrid);
const bool includeStats = false, includeTiles = false;
const int verb = verbose ? 1 : 0;
if (qMode == nanovdb::GridType::OnIndex) {
return nanovdb::tools::openToIndexVDB<nanovdb::ValueOnIndex>(base, 1u, includeStats, includeTiles, verb);
} else if (qMode == nanovdb::GridType::Index) {
return nanovdb::tools::openToIndexVDB<nanovdb::ValueIndex>(base, 1u, includeStats, includeTiles, verb);
}
if (auto floatGrid = openvdb::GridBase::grid<openvdb::FloatGrid>(base)) {
nanovdb::tools::CreateNanoGrid<openvdb::FloatGrid> s(*floatGrid);
s.setStats(sMode);
s.setChecksum(cMode);
s.enableDithering(dither);
@@ -227,7 +245,7 @@ int main(int argc, char* argv[])
break;
}// end of switch
}
return nanovdb::tools::openToNanoVDB(base, sMode, cMode, verbose ? 1 : 0);
return nanovdb::tools::openToNanoVDB(base, sMode, cMode, verb);
};
try {
if (toNanoVDB) { // OpenVDB -> NanoVDB
@@ -237,26 +255,21 @@ int main(int argc, char* argv[])
std::cerr << "Since the last file has extension .nvdb the remaining input files were expected to have extensions .vdb\n" << std::endl;
usage(argv[0]);
}
if (verbose)
std::cout << "Opening OpenVDB file named \"" << inputFile << "\"" << std::endl;
if (verbose) std::cout << "Opening OpenVDB file named \"" << inputFile << "\"" << std::endl;
openvdb::io::File file(inputFile);
file.open(false); //disable delayed loading
if (gridName.empty()) {// convert all grid in the file
auto grids = file.getGrids();
std::vector<nanovdb::GridHandle<nanovdb::HostBuffer> > handles;
for (auto& grid : *grids) {
if (verbose) {
std::cout << "Converting OpenVDB grid named \"" << grid->getName() << "\" to NanoVDB" << std::endl;
}
if (verbose) std::cout << "Converting OpenVDB grid named \"" << grid->getName() << "\" to NanoVDB" << std::endl;
handles.push_back(openToNano(grid));
} // loop over OpenVDB grids in file
auto handle = nanovdb::mergeGrids<nanovdb::HostBuffer, std::vector>(handles);
nanovdb::io::writeGrid(os, handle, codec);
} else {// convert only grid with matching name
auto grid = file.readGrid(gridName);
if (verbose) {
std::cout << "Converting OpenVDB grid named \"" << grid->getName() << "\" to NanoVDB" << std::endl;
}
if (verbose) std::cout << "Converting OpenVDB grid named \"" << grid->getName() << "\" to NanoVDB" << std::endl;
auto handle = openToNano(grid);
nanovdb::io::writeGrid(os, handle, codec);
}
@@ -269,15 +282,13 @@ int main(int argc, char* argv[])
std::cerr << "Since the last file has extension .vdb the remaining input files were expected to have extensions .nvdb\n" << std::endl;
usage(argv[0]);
}
if (verbose)
std::cout << "Opening NanoVDB file named \"" << inputFile << "\"" << std::endl;
if (verbose) std::cout << "Opening NanoVDB file named \"" << inputFile << "\"" << std::endl;
if (gridName.empty()) {
auto handles = nanovdb::io::readGrids(inputFile, verbose);
for (auto &h : handles) {
for (uint32_t i = 0; i < h.gridCount(); ++i) {
if (verbose)
std::cout << "Converting NanoVDB grid named \"" << h.gridMetaData(i)->shortGridName() << "\" to OpenVDB" << std::endl;
grids->push_back(nanovdb::tools::nanoToOpenVDB(h, 0, i));
if (verbose) std::cout << "Converting NanoVDB grid named \"" << h.gridMetaData(i)->shortGridName() << "\" to OpenVDB" << std::endl;
grids->push_back(nanovdb::tools::nanoToOpenVDB(h, i));
}
}
} else {
@@ -286,8 +297,7 @@ int main(int argc, char* argv[])
std::cerr << "File did not contain a NanoVDB grid named \"" << gridName << "\"\n" << std::endl;
usage(argv[0]);
}
if (verbose)
std::cout << "Converting NanoVDB grid named \"" << handle.gridMetaData()->shortGridName() << "\" to OpenVDB" << std::endl;
if (verbose) std::cout << "Converting NanoVDB grid named \"" << handle.gridMetaData()->shortGridName() << "\" to OpenVDB" << std::endl;
grids->push_back(nanovdb::tools::nanoToOpenVDB(handle));
}
} // loop over input files
+9 -5
View File
@@ -166,7 +166,7 @@ int main(int argc, char* argv[])
auto classWidth = std::string("Class").length() + padding;
auto codecWidth = std::string("Codec").length() + padding;
auto ibboxWidth = std::string("Index Bounding Box").length() + padding;
auto wbboxWidth = std::string("World BBox").length() + padding;
auto wbboxWidth = std::string("World Bounding Box").length() + padding;
auto sizeWidth = std::string("Size").length() + padding;
auto fileWidth = std::string("File").length() + padding;
auto voxelsWidth = std::string("# Voxels").length() + padding;
@@ -175,6 +175,7 @@ int main(int argc, char* argv[])
auto configWidth = std::string("32^3->16^3->8^3").length() + padding;
auto tileWidth = std::string("# Active tiles").length() + padding;
auto resWidth = std::string("Resolution").length() + padding;
auto blindWidth = std::string("Blind data count").length() + padding;
for (auto& m : list) {
width(nameWidth, m.gridName);
width(typeWidth, nanovdb::toStr(str, m.gridType));
@@ -190,6 +191,7 @@ int main(int argc, char* argv[])
width(tileWidth, nodesToStr(m.tileCount));
width(voxelsWidth, std::to_string(m.voxelCount));
width(voxelSizeWidth, Vec3dToStr(m.voxelSize));
width(blindWidth, std::to_string(m.blindDataCount));
}
std::cout << "\nThe file \"" << file << "\" contains the following ";
if (list.size()>1) {
@@ -214,7 +216,8 @@ int main(int argc, char* argv[])
std::cout << std::left << std::setw(configWidth) << "32^3->16^3->8^3"
<< std::left << std::setw(tileWidth) << "# Active tiles"
<< std::left << std::setw(ibboxWidth) << "Index Bounding Box"
<< std::left << std::setw(wbboxWidth) << "World Bounding Box";
<< std::left << std::setw(wbboxWidth) << "World Bounding Box"
<< std::left << std::setw(blindWidth) << "Blind data count";
}
std::cout << std::endl;
int n = 0;
@@ -238,11 +241,12 @@ int main(int argc, char* argv[])
std::cout << std::left << std::setw(configWidth) << nodesToStr(m.nodeCount)
<< std::left << std::setw(tileWidth) << nodesToStr(m.tileCount)
<< std::left << std::setw(ibboxWidth) << ibboxToStr(m.indexBBox)
<< std::left << std::setw(wbboxWidth) << wbboxToStr(m.worldBBox);
<< std::left << std::setw(wbboxWidth) << wbboxToStr(m.worldBBox)
<< std::left << std::setw(blindWidth) << m.blindDataCount;
}
std::cout << std::endl;
}
}
}// loop over FileGridMetaData in list from a single file
}// loop over files
if (verbose) {
size_t w = 0;
switch (mode) {
+62 -24
View File
@@ -41,7 +41,7 @@ class DeviceBuffer
/// @brief Initialize buffer
/// @param size byte size of buffer to be initialized
/// @param host If true buffer is initialized only on the host/CPU, else on the device/GPU
/// @param device id of the device on which to initialize the buffer
/// @note All existing buffers are first cleared
/// @warning size is expected to be non-zero. Use clear() clear buffer!
void init(uint64_t size, int device, cudaStream_t stream);
@@ -127,6 +127,20 @@ public:
other.mSize = other.mDeviceCount = other.mManaged = 0;
}
/// @brief Copy-constructor from a HostBuffer
/// @param buffer host buffer from which to copy data
/// @param device id of the device on which to initialize the buffer
/// @param stream cuda stream
DeviceBuffer(const HostBuffer& buffer, int device = cudaCpuDeviceId, cudaStream_t stream = 0)
: DeviceBuffer(buffer.size(), device, stream)
{
if (mCpuData) {
cudaCheck(cudaMemcpy(mCpuData, buffer.data(), mSize, cudaMemcpyHostToHost));
} else if (mGpuData[device]) {
cudaCheck(cudaMemcpyAsync(mGpuData[device], buffer.data(), mSize, cudaMemcpyHostToDevice, stream));
}
}
/// @brief Destructor frees memory on both the host and device
~DeviceBuffer() { this->clear(); };
@@ -138,6 +152,11 @@ public:
/// @return An instance of this class using move semantics
static DeviceBuffer create(uint64_t size, const DeviceBuffer* dummy, bool host, void* stream){return DeviceBuffer(size, host, stream);}
/// @brief Static factory method that returns an instance of this buffer
/// @param size byte size of buffer to be initialized
/// @param dummy this argument is currently ignored but required to match the API of the HostBuffer
/// @param device id of the device on which to initialize the buffer
/// @param stream cuda stream
static DeviceBuffer create(uint64_t size, const DeviceBuffer* dummy = nullptr, int device = cudaCpuDeviceId, cudaStream_t stream = 0){return DeviceBuffer(size, device, stream);}
/// @brief Static factory method that returns an instance of this buffer that wraps externally managed memory
@@ -153,14 +172,21 @@ public:
/// @param list list of device IDs and device memory pointers
static DeviceBuffer create(uint64_t size, void* cpuData, std::initializer_list<std::pair<int,void*>> list) {return DeviceBuffer(size, cpuData, list);}
/// @brief Static factory method that returns an instance of this buffer constructed from a HostBuffer
/// @param buffer host buffer from which to copy data
/// @param device id of the device on which to initialize the buffer
/// @param stream cuda stream
static DeviceBuffer create(const HostBuffer& buffer, int device = cudaCpuDeviceId, cudaStream_t stream = 0) {return DeviceBuffer(buffer, device, stream);}
///////////////////////////////////////////////////////////////////////
//@{
/// @{
/// @brief Factory methods that create a shared pointer to an DeviceBuffer instance
static PtrT createPtr(uint64_t size, const DeviceBuffer* = nullptr, int device = cudaCpuDeviceId, cudaStream_t stream = 0) {return std::make_shared<DeviceBuffer>(size, device, stream);}
static PtrT createPtr(uint64_t size, void* cpuData, void* gpuData) {return std::make_shared<DeviceBuffer>(size, cpuData, gpuData);}
static PtrT createPtr(uint64_t size, void* cpuData, std::initializer_list<std::pair<int,void*>> list) {return std::make_shared<DeviceBuffer>(size, cpuData, list);}
///@}
static PtrT createPtr(const HostBuffer& buffer, int device = cudaCpuDeviceId, cudaStream_t stream = 0) {return std::make_shared<DeviceBuffer>(buffer, device, stream);}
/// @}
///////////////////////////////////////////////////////////////////////
@@ -168,18 +194,7 @@ public:
DeviceBuffer& operator=(const DeviceBuffer&) = delete;
/// @brief Move copy assignment operation
DeviceBuffer& operator=(DeviceBuffer&& other) noexcept
{
mSize = other.mSize;
mCpuData = other.mCpuData;
delete [] mGpuData;
mGpuData = other.mGpuData;
mDeviceCount = other.mDeviceCount;
mManaged = other.mManaged;
other.mCpuData = other.mGpuData = nullptr;
other.mSize = other.mDeviceCount = other.mManaged = 0;
return *this;
}
DeviceBuffer& operator=(DeviceBuffer&& other) noexcept;
///////////////////////////////////////////////////////////////////////
@@ -190,7 +205,7 @@ public:
/// @brief Returns an offset pointer of a specific type from the allocated host memory
/// @tparam T Type of the pointer returned
/// @param count Numbers of elements of @c parameter type T to skip
/// @warning assumes that this instance is not empty!
/// @warning might return NULL
template <typename T>
T* data(ptrdiff_t count = 0, int device = cudaCpuDeviceId) const
{
@@ -279,11 +294,11 @@ public:
int deviceCount() const {return mDeviceCount;}
//@{
/// @{
/// @brief Returns true if this allocator is empty, i.e. has no allocated memory
bool empty() const { return mSize == 0; }
bool isEmpty() const { return this->empty(); }
//@}
/// @}
/// @brief De-allocate all memory managed by this allocator and set all pointers to NULL
void clear(cudaStream_t stream = 0);
@@ -293,6 +308,26 @@ public:
// --------------------------> Implementations below <------------------------------------
inline DeviceBuffer& DeviceBuffer::operator=(DeviceBuffer&& other) noexcept
{
if (mManaged) {// first free all the managed data buffers
cudaCheck(cudaFreeHost(mCpuData));
for (int i=0; i<mDeviceCount; ++i) cudaCheck(util::cuda::freeAsync(mGpuData[i], 0));
}
delete [] mGpuData;
mSize = other.mSize;
mCpuData = other.mCpuData;
mGpuData = other.mGpuData;
mDeviceCount = other.mDeviceCount;
mManaged = other.mManaged;
other.mCpuData = nullptr;
other.mGpuData = nullptr;
other.mSize = 0;
other.mDeviceCount = 0;
other.mManaged = 0;
return *this;
}
inline void DeviceBuffer::init(uint64_t size, int device, cudaStream_t stream)
{
if (size==0) return;
@@ -303,7 +338,7 @@ inline void DeviceBuffer::init(uint64_t size, int device, cudaStream_t stream)
cudaCheck(cudaMallocHost((void**)&mCpuData, size)); // un-managed pinned memory on the host (can be slow to access!). Always 32B aligned
checkPtr(mCpuData, "cuda::DeviceBuffer::init: failed to allocate host buffer");
} else {
cudaCheck(cudaMallocAsync(mGpuData+device, size, stream)); // un-managed memory on the device, always 32B aligned!
cudaCheck(util::cuda::mallocAsync(mGpuData+device, size, stream)); // un-managed memory on the device, always 32B aligned!
checkPtr(mGpuData[device], "cuda::DeviceBuffer::init: failed to allocate device buffer");
}
mSize = size;
@@ -316,7 +351,7 @@ inline void DeviceBuffer::deviceUpload(int device, cudaStream_t stream, bool syn
checkPtr(mCpuData, "uninitialized cpu source data");
if (mGpuData[device] == nullptr) {
if (mManaged==0) throw std::runtime_error("DeviceBuffer::deviceUpload called on externally managed memory that wasn\'t allocated.");
cudaCheck(cudaMallocAsync(mGpuData+device, mSize, stream)); // un-managed memory on the device, always 32B aligned!
cudaCheck(util::cuda::mallocAsync(mGpuData+device, mSize, stream)); // un-managed memory on the device, always 32B aligned!
}
checkPtr(mGpuData[device], "uninitialized gpu destination data");
cudaCheck(cudaMemcpyAsync(mGpuData[device], mCpuData, mSize, cudaMemcpyHostToDevice, stream));
@@ -352,13 +387,16 @@ inline void DeviceBuffer::deviceDownload(void* stream, bool sync)
inline void DeviceBuffer::clear(cudaStream_t stream)
{
if (mManaged!=0) {// free all the managed data buffers
if (mManaged) {// free all the managed data buffers
cudaCheck(cudaFreeHost(mCpuData));
for (int i=0; i<mDeviceCount; ++i) cudaCheck(cudaFreeAsync(mGpuData[i], stream));
for (int i=0; i<mDeviceCount; ++i) cudaCheck(util::cuda::freeAsync(mGpuData[i], stream));
}
delete [] mGpuData;
mCpuData = mGpuData = nullptr;
mSize = mDeviceCount = mManaged = 0;
mCpuData = nullptr;
mGpuData = nullptr;
mSize = 0;
mDeviceCount = 0;
mManaged = 0;
} // DeviceBuffer::clear
}// namespace cuda
+229
View File
@@ -0,0 +1,229 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
/*!
\file DeviceMesh.h
\brief nanovdb::cuda::DeviceMesh encapsulates device IDs, CUDA streams,
and NCCL communicators in order to facilitate multi-GPU applications.
*/
#ifndef NANOVDB_CUDA_DEVICEMESH_H_HAS_BEEN_INCLUDED
#define NANOVDB_CUDA_DEVICEMESH_H_HAS_BEEN_INCLUDED
#include <vector>
#include <algorithm>
#include <thread>
#include <nanovdb/util/cuda/Util.h>
#ifdef NANOVDB_USE_NCCL
#include <nccl.h>
#endif
namespace nanovdb {
namespace cuda {
namespace detail {
/// @brief RAII class that caches/restores the current device at construction/destruction
class DeviceGuard {
public:
DeviceGuard() { cudaGetDevice(&deviceId); }
~DeviceGuard() { cudaSetDevice(deviceId); }
/// @{
/// @brief DeviceGuard is not copyable nor movable
DeviceGuard(const DeviceGuard&) = delete;
DeviceGuard& operator=(const DeviceGuard&) = delete;
DeviceGuard(DeviceGuard&& other) = delete;
DeviceGuard& operator=(DeviceGuard&& other) = delete;
/// @}
private:
int deviceId = -1;
};
}
/// @brief POD struct representing a device id and a stream on that device
struct DeviceNode
{
int id = -1;
cudaStream_t stream = cudaStream_t(0);
};
/// @brief This class wraps a vector of per-device IDs and CUDA streams and holds inter-device connectivity information and NCCL comms.
class DeviceMesh
{
using DeviceVecT = std::vector<DeviceNode>;
using const_iterator = DeviceVecT::const_iterator;
using size_type = DeviceVecT::size_type;
public:
/// @brief Constructs a device mesh for all devices on the host and initializes a CUDA stream (and NCCL communicator) for each device
DeviceMesh();
/// @brief Destroys the per-device CUDA stream and finalizes and destroys the per-device NCCL communicators
~DeviceMesh();
/// @brief Disallow copy-construction
DeviceMesh(const DeviceMesh&) = delete;
/// @brief Move constructor. Underlying CUDA streams and NCCL communicators are not reinitialized.
/// @param DeviceMesh instance that will be moved into this DeviceMesh.
DeviceMesh(DeviceMesh&&) noexcept;
/// @brief Disallow copy-assignment
DeviceMesh& operator=(const DeviceMesh&) = delete;
/// @brief Move assignment. Underlying CUDA streams and NCCL communicators are not reinitialized.
/// @param DeviceMesh instance that will be moved into this DeviceMesh.
DeviceMesh& operator=(DeviceMesh&&) noexcept;
/// @brief Returns the number of devices
size_type deviceCount() const { return mDeviceNodes.size(); };
/// @brief Returns a const reference to the DeviceNode for a particular device
const DeviceNode& operator [](int deviceId) const { return mDeviceNodes[deviceId]; }
/// @brief Returns an iterator to the first device node
const_iterator begin() const { return mDeviceNodes.begin(); };
/// @brief Returns an iterator past the last device node
const_iterator end() const { return mDeviceNodes.end(); };
#ifdef NANOVDB_USE_NCCL
/// @brief Returns the NCCL communicator for a particular device
ncclComm_t comm(int deviceId) const { return mComms[deviceId]; }
#endif
/// @brief Returns whether or not peer to peer access is supported between two devices.
bool canAccessPeer(int deviceId, int peerId) const { return mConnectivity[deviceId][peerId] & (1 << cudaDevP2PAttrAccessSupported); }
private:
/// @brief Returns whether or not a device supports managed memory
bool hasManagedMemory(int deviceId) const {
int managedMemoryValue = 0;
cudaDeviceGetAttribute(&managedMemoryValue, cudaDevAttrManagedMemory, deviceId);
return static_cast<bool>(managedMemoryValue);
}
DeviceVecT mDeviceNodes;
#ifdef NANOVDB_USE_NCCL
std::vector<ncclComm_t> mComms;
#endif
std::vector<std::vector<uint32_t>> mConnectivity;
};
inline DeviceMesh::DeviceMesh()
{
detail::DeviceGuard deviceGuard;
int deviceCount = -1;
cudaGetDeviceCount(&deviceCount);
for (int deviceId = 0; deviceId < deviceCount; ++deviceId) {
NANOVDB_ASSERT(hasManagedMemory(deviceId));
cudaSetDevice(deviceId);
cudaStream_t stream;
cudaStreamCreate(&stream);
mDeviceNodes.push_back({deviceId, stream});
}
#ifdef NANOVDB_USE_NCCL
mComms.resize(deviceCount);
ncclCommInitAll(mComms.data(), deviceCount, nullptr);
#endif
mConnectivity.resize(deviceCount);
for (const auto& deviceNode : mDeviceNodes) {
mConnectivity[deviceNode.id].resize(deviceCount);
for (const auto& peerNode : mDeviceNodes) {
mConnectivity[deviceNode.id][peerNode.id] = 0;
if (deviceNode.id != peerNode.id) {
int peerAccessSupportedValue = 0;
cudaDeviceGetP2PAttribute(&peerAccessSupportedValue, cudaDevP2PAttrAccessSupported, deviceNode.id, peerNode.id);
if (peerAccessSupportedValue) {
mConnectivity[deviceNode.id][peerNode.id] |= (1 << cudaDevP2PAttrAccessSupported);
}
}
}
}
}
inline DeviceMesh::DeviceMesh(DeviceMesh&& other) noexcept
: mDeviceNodes(std::move(other.mDeviceNodes)),
#ifdef NANOVDB_USE_NCCL
mComms(std::move(other.mComms)),
#endif
mConnectivity(std::move(other.mConnectivity))
{
}
inline DeviceMesh::~DeviceMesh()
{
detail::DeviceGuard deviceGuard;
#ifdef NANOVDB_USE_NCCL
std::for_each(mComms.begin(), mComms.end(), [](ncclComm_t comm) {
ncclCommFinalize(comm);
ncclCommDestroy(comm);
});
#endif
std::for_each(mDeviceNodes.begin(), mDeviceNodes.end(), [](DeviceNode& deviceNode) {
cudaSetDevice(deviceNode.id);
cudaStreamDestroy(deviceNode.stream);
});
}
inline DeviceMesh& DeviceMesh::operator=(DeviceMesh&& other) noexcept
{
mDeviceNodes = std::move(other.mDeviceNodes);
#ifdef NANOVDB_USE_NCCL
mComms = std::move(other.mComms);
#endif
mConnectivity = std::move(other.mConnectivity);
return *this;
}
namespace detail {
inline void* queryAllocationGranularityEntryPoint()
{
void* entryPoint = nullptr;
#if CUDART_VERSION >= 12500 // cudaGetDriverEntryPointByVersion was added in CUDA 12.5 with cudaGetDriverEntryPoint being potentially deprecated
cudaDriverEntryPointQueryResult queryResult = cudaDriverEntryPointSymbolNotFound;
cudaCheck(cudaGetDriverEntryPointByVersion("cuMemGetAllocationGranularity", &entryPoint, 12000, cudaEnableDefault, &queryResult));
NANOVDB_ASSERT(queryResult == cudaDriverEntryPointSuccess);
#elif CUDART_VERSION >= 12000 // queryResult argument was added in CUDA 12
cudaDriverEntryPointQueryResult queryResult = cudaDriverEntryPointSymbolNotFound;
cudaCheck(cudaGetDriverEntryPoint("cuMemGetAllocationGranularity", &entryPoint, cudaEnableDefault, &queryResult));
NANOVDB_ASSERT(queryResult == cudaDriverEntryPointSuccess);
#else
cudaCheck(cudaGetDriverEntryPoint("cuMemGetAllocationGranularity", &entryPoint, cudaEnableDefault));
#endif
return entryPoint;
}// queryAllocationGranularityEntryPoint
}
/// @brief Returns the minimum page size (in bytes) across all devices on the system
inline size_t minDevicePageSize(const DeviceMesh& mesh)
{
using FuncT = CUresult(size_t*, const CUmemAllocationProp*, CUmemAllocationGranularity_flags);
static FuncT* functPtr = reinterpret_cast<FuncT*>(detail::queryAllocationGranularityEntryPoint());
NANOVDB_ASSERT(functPtr);
CUmemAllocationProp prop = {};
prop.type = CU_MEM_ALLOCATION_TYPE_PINNED;
prop.location.type = CU_MEM_LOCATION_TYPE_DEVICE;
size_t minGranularity = 0;
for (const auto& node : mesh) {
prop.location.id = node.id;
size_t granularity = 0;
(*functPtr)(&granularity, &prop, CU_MEM_ALLOC_GRANULARITY_MINIMUM);
if (minGranularity < granularity) minGranularity = granularity;
}
return minGranularity;
}// minDevicePageSize
} // namespace cuda
} // namespace nanovdb
#endif // end of NANOVDB_CUDA_DEVICEMESH_H_HAS_BEEN_INCLUDED
+35
View File
@@ -0,0 +1,35 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
//
#ifndef NANOVDB_CUDA_DEVICERESOURCE_H_HAS_BEEN_INCLUDED
#define NANOVDB_CUDA_DEVICERESOURCE_H_HAS_BEEN_INCLUDED
#include <cuda_runtime_api.h>
#include <nanovdb/util/cuda/Util.h>
namespace nanovdb {
namespace cuda {
class DeviceResource
{
public:
// cudaMalloc aligns memory to 256 bytes by default
static constexpr size_t DEFAULT_ALIGNMENT = 256;
static void* allocateAsync(size_t bytes, size_t, cudaStream_t stream) {
void* p = nullptr;
cudaCheck(util::cuda::mallocAsync(&p, bytes, stream));
return p;
}
static void deallocateAsync(void *p, size_t, size_t, cudaStream_t stream) {
cudaCheck(util::cuda::freeAsync(p, stream));
}
};
}
} // namespace nanovdb::cuda
#endif // end of NANOVDB_CUDA_DEVICERESOURCE_H_HAS_BEEN_INCLUDED
+6 -2
View File
@@ -37,7 +37,7 @@ public:
/// @brief Initiates a map between CUDA device IDs and corresponding streams that satisfy certain constraints.
/// All devices should be able to access memory on all the other devices!
/// @param t Type of device to include in map. Any means all available devices, PeerToPeer means only devices that
/// access all aother devices are included, and Unified means all devices support unified memory, concurrent access,
/// access all another devices are included, and Unified means all devices support unified memory, concurrent access,
/// and can be access by all other devices.
/// @param exclude optional list of device IDs to exclude from the map
/// @param verbose 0 means quiet, 1 means print if a device is ignores and 2 means print is a device is included
@@ -77,7 +77,11 @@ DeviceStreamMap::DeviceStreamMap(DeviceType t, std::vector<int> exclude, int ver
cudaCheck(cudaSetDevice(current));// reset to the previous device
void* entryPoint = nullptr;
#if CUDART_VERSION >= 12000// queryResult argument was added in CUDA 12
#if CUDART_VERSION >= 13000
cudaDriverEntryPointQueryResult queryResult;
cudaCheck(cudaGetDriverEntryPointByVersion("cuMemGetAllocationGranularity", &entryPoint, 13000, cudaEnableDefault, &queryResult));
NANOVDB_ASSERT(queryResult == cudaDriverEntryPointSuccess);
#elif CUDART_VERSION >= 12000// queryResult argument was added in CUDA 12
cudaDriverEntryPointQueryResult queryResult;
cudaCheck(cudaGetDriverEntryPoint("cuMemGetAllocationGranularity", &entryPoint, cudaEnableDefault, &queryResult));
NANOVDB_ASSERT(queryResult == cudaDriverEntryPointSuccess);
+16 -13
View File
@@ -25,22 +25,25 @@ namespace nanovdb {
namespace cuda {
namespace {// anonymous namespace
__global__ void cpyGridHandleMeta(const GridData *d_data, GridHandleMetaData *d_meta)
namespace detail {
static __global__ void cpyGridHandleMeta(const GridData *d_data, GridHandleMetaData *d_meta)
{
nanovdb::cpyGridHandleMeta(d_data, d_meta);
}
__global__ void updateGridCount(GridData *d_data, uint32_t gridIndex, uint32_t gridCount, bool *d_dirty)
static __global__ void updateGridCount(GridData *d_data, uint32_t gridIndex, uint32_t gridCount, bool *d_dirty)
{
NANOVDB_ASSERT(gridIndex < gridCount);
if (*d_dirty = d_data->mGridIndex != gridIndex || d_data->mGridCount != gridCount) {
*d_dirty = (d_data->mGridIndex != gridIndex) || (d_data->mGridCount != gridCount);
if (*d_dirty) {
d_data->mGridIndex = gridIndex;
d_data->mGridCount = gridCount;
if (d_data->mChecksum.isEmpty()) *d_dirty = false;// no need to update checksum if it didn't already exist
}
}
}// anonymous namespace
}// namespace detail
template<typename BufferT, template <class, class...> class VectorT = std::vector>
inline typename util::enable_if<BufferTraits<BufferT>::hasDeviceDual, VectorT<GridHandle<BufferT>>>::type
@@ -51,22 +54,22 @@ splitGridHandles(const GridHandle<BufferT> &handle, const BufferT* other = nullp
VectorT<GridHandle<BufferT>> handles(handle.gridCount());
bool dirty, *d_dirty;// use this to check if the checksum needs to be recomputed
cudaCheck(util::cuda::mallocAsync((void**)&d_dirty, sizeof(bool), stream));
int device = 0;
cudaCheck(cudaGetDevice(&device));
int device = util::cuda::currentDevice();
for (uint32_t n=0; n<handle.gridCount(); ++n) {
auto buffer = BufferT::create(handle.gridSize(n), other, device, stream);
GridData *dst = reinterpret_cast<GridData*>(buffer.deviceData());
const GridData *src = reinterpret_cast<const GridData*>(ptr);
cudaCheck(cudaMemcpyAsync(dst, src, handle.gridSize(n), cudaMemcpyDeviceToDevice, stream));
updateGridCount<<<1, 1, 0, stream>>>(dst, 0u, 1u, d_dirty);
detail::updateGridCount<<<1, 1, 0, stream>>>(dst, 0u, 1u, d_dirty);
cudaCheckError();
cudaCheck(cudaMemcpyAsync(&dirty, d_dirty, sizeof(bool), cudaMemcpyDeviceToHost, stream));
cudaCheck(cudaStreamSynchronize(stream));
if (dirty) tools::cuda::updateChecksum(dst, CheckMode::Partial, stream);
handles[n] = nanovdb::GridHandle<BufferT>(std::move(buffer));
ptr = util::PtrAdd(ptr, handle.gridSize(n));
}
cudaCheck(util::cuda::freeAsync(d_dirty, stream));
return std::move(handles);
return handles;
}// cuda::splitGridHandles
template<typename BufferT, template <class, class...> class VectorT>
@@ -79,8 +82,7 @@ mergeGridHandles(const VectorT<GridHandle<BufferT>> &handles, const BufferT* oth
gridCount += h.gridCount();
for (uint32_t n=0; n<h.gridCount(); ++n) size += h.gridSize(n);
}
int device = 0;
cudaCheck(cudaGetDevice(&device));
int device = util::cuda::currentDevice();
auto buffer = BufferT::create(size, other, device, stream);
void *dst = buffer.deviceData();
bool dirty, *d_dirty;// use this to check if the checksum needs to be recomputed
@@ -90,9 +92,10 @@ mergeGridHandles(const VectorT<GridHandle<BufferT>> &handles, const BufferT* oth
for (uint32_t n=0; n<h.gridCount(); ++n) {
cudaCheck(cudaMemcpyAsync(dst, src, h.gridSize(n), cudaMemcpyDeviceToDevice, stream));
GridData *data = reinterpret_cast<GridData*>(dst);
updateGridCount<<<1, 1, 0, stream>>>(data, counter++, gridCount, d_dirty);
detail::updateGridCount<<<1, 1, 0, stream>>>(data, counter++, gridCount, d_dirty);
cudaCheckError();
cudaCheck(cudaMemcpyAsync(&dirty, d_dirty, sizeof(bool), cudaMemcpyDeviceToHost, stream));
cudaCheck(cudaStreamSynchronize(stream));
if (dirty) tools::cuda::updateChecksum(data, CheckMode::Partial, stream);
dst = util::PtrAdd(dst, h.gridSize(n));
src = util::PtrAdd(src, h.gridSize(n));
@@ -133,7 +136,7 @@ GridHandle<BufferT>::GridHandle(T&& buffer)
if (!tmp.isValid()) throw std::runtime_error("GridHandle was constructed with an invalid device buffer");
GridHandleMetaData *d_metaData;
cudaMalloc((void**)&d_metaData, tmp.mGridCount*sizeof(GridHandleMetaData));
cuda::cpyGridHandleMeta<<<1,1>>>(d_data, d_metaData);
cuda::detail::cpyGridHandleMeta<<<1,1>>>(d_data, d_metaData);
mMetaData.resize(tmp.mGridCount);
cudaCheck(cudaMemcpy(mMetaData.data(), d_metaData,tmp.mGridCount*sizeof(GridHandleMetaData), cudaMemcpyDeviceToHost));
cudaCheck(cudaFree(d_metaData));
+1 -5
View File
@@ -44,11 +44,7 @@ createNodeManager(const NanoGrid<BuildT> *d_grid,
size_t size = 0u, *d_size;
cudaCheck(util::cuda::mallocAsync((void**)&d_size, sizeof(size_t), stream));
util::cuda::lambdaKernel<<<1, 1, 0, stream>>>(1, [=] __device__(size_t) {
#ifdef NANOVDB_USE_NEW_MAGIC_NUMBERS
*d_data = NodeManagerData{NANOVDB_MAGIC_NODE, 0u, (void*)d_grid, {0u,0u,0u}};
#else
*d_data = NodeManagerData{NANOVDB_MAGIC_NUMB, 0u, (void*)d_grid, {0u,0u,0u}};
#endif
*d_data = NodeManagerData((void*)d_grid);
*d_size = sizeof(NodeManagerData);
auto &tree = d_grid->tree();
if (NodeManager<BuildT>::FIXED_SIZE && d_grid->isBreadthFirst()) {
+66
View File
@@ -0,0 +1,66 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
//
/// @brief Defines a simple memory pool used to call cub functions that use dynamic temporary storage
///
/// @details See nanovdb/tools/cuda/PointToGrid.cuh and nanovdb/tools/cuda/DistributedPointToGrid.cuh
/// for examples. Also note that this explains the somewhat unusual API with direct access to
/// protected member data.
//
#ifndef NANOVDB_CUDA_TEMPPOOL_H_HAS_BEEN_INCLUDED
#define NANOVDB_CUDA_TEMPPOOL_H_HAS_BEEN_INCLUDED
#include <nanovdb/cuda/DeviceResource.h>
namespace nanovdb {
namespace cuda {
template <class Resource>
class TempPool {
public:
/// @brief Default c-tor of an empty memory pool.
TempPool() : mData(nullptr), mSize(0), mRequestedSize(0) {}
/// @brief Destructor
~TempPool() {
mRequestedSize = 0;
Resource::deallocateAsync(mData, mSize, Resource::DEFAULT_ALIGNMENT, nullptr);
mData = nullptr;
mSize = 0;
}
/// @brief Returns a non-const void pointer to the data managed by this instance.
void* data() {return mData;}
/// @brief Returns a non-const reference to the actual size of the data managed by this instance.
size_t& size() {return mSize;}
/// @brief Returns a non-const reference to the requested size of the data managed by this instance.
/// @note This requested size should always be less than or smaller than the actual size().
size_t& requestedSize() {return mRequestedSize;}
/// @brief Re-allocation of the data managed by this instance. Only has affect if the pool in empty or
/// the requested memory is larger than the existing size.
/// @param stream cuda stream used for asynchronous de-allocation and allocation.
void reallocate(cudaStream_t stream) {
if (!mData || mRequestedSize > mSize) {
Resource::deallocateAsync(mData, mSize, Resource::DEFAULT_ALIGNMENT, stream);
mData = Resource::allocateAsync(mRequestedSize, Resource::DEFAULT_ALIGNMENT, stream);
mSize = mRequestedSize;
}
}
private:
void *mData;
size_t mSize;
size_t mRequestedSize;
};// TempPool<Resource> class
using TempDevicePool = TempPool<DeviceResource>;
} // namespace cuda
} // namespace nanovdb
#endif // end of NANOVDB_CUDA_TEMPPOOL_H_HAS_BEEN_INCLUDED
+32 -33
View File
@@ -40,17 +40,17 @@ public:
UnifiedBuffer() : mPtr(nullptr), mSize(0), mCapacity(0){}
/// @brief Constructor that specifies both the size and capacity
/// @param size size of the bufffer in bytes, indication what is actually used
/// @param capacity number of bytes in the vitual page table, i.e max size for growing
/// @note Capacity can be over-estimated to allow for future groth. Memory is not allocated
/// with this constructor, only a page table. Allocation happend on use or when calling prefetch
/// @param size size of the buffer in bytes, indication what is actually used
/// @param capacity number of bytes in the virtual page table, i.e max size for growing
/// @note Capacity can be over-estimated to allow for future growth. Memory is not allocated
/// with this constructor, only a page table. Allocation happens on usage or when calling prefetch
UnifiedBuffer(size_t size, size_t capacity) : mPtr(nullptr), mSize(size), mCapacity(capacity)
{
assert(mSize <= mCapacity);
cudaCheck(cudaMallocManaged(&mPtr, mCapacity, cudaMemAttachGlobal));
}
/// @brief Simular to the constructor above except the size and capacity are equal, so no future growth is supported
/// @brief Similar to the constructor above except the size and capacity are equal, so no future growth is supported
UnifiedBuffer(size_t size) : UnifiedBuffer(size, size){}
/// @brief Constructor that specifies the size, capacity, and device (for prefetching)
@@ -60,9 +60,10 @@ public:
/// @param stream
UnifiedBuffer(uint64_t size, uint64_t capacity, int device, cudaStream_t stream = 0) : mPtr(nullptr), mSize(size), mCapacity(size)
{
assert(mSize <= mCapacity);
cudaCheck(cudaMallocManaged(&mPtr, mCapacity, cudaMemAttachGlobal));
cudaCheck(cudaMemAdvise(mPtr, size, cudaMemAdviseSetPreferredLocation, device));
cudaCheck(cudaMemPrefetchAsync(mPtr, size, device, stream));
cudaCheck(util::cuda::memAdvise(mPtr, size, cudaMemAdviseSetPreferredLocation, device));
cudaCheck(util::cuda::memPrefetchAsync(mPtr, size, device, stream));
}
/// @brief Constructor with a specified device
@@ -76,11 +77,10 @@ public:
/// @brief Move copy-constructor
UnifiedBuffer(UnifiedBuffer&& other) noexcept
: mSize(other.mSize)
: mPtr(other.mPtr)
, mSize(other.mSize)
, mCapacity(other.mCapacity)
{
cudaCheck(cudaFree(mPtr));
mPtr = other.mPtr;
other.mPtr = nullptr;
other.mSize = other.mCapacity = 0;
}
@@ -105,7 +105,7 @@ public:
/// @brief Legacy factory method that mirrors DeviceBuffer. It creates a UnifiedBuffer from a size and a reference buffer.
/// If a reference buffer is provided and its non-empty, it is used to defined the capacity of the new buffer
/// @param size Size on bytes of the new buffer
/// @param reference reference buffer optionally used to define the capcity
/// @param reference reference buffer optionally used to define the capacity
/// @param host Ignored for now
/// @param stream cuda stream
/// @return An instance of a new UnifiedBuffer using move semantics
@@ -113,22 +113,22 @@ public:
{
const size_t capacity = (reference && reference->capacity()) ? reference->capacity() : size;
UnifiedBuffer buffer(size, capacity);
cudaCheck(cudaMemAdvise(buffer.mPtr, size, cudaMemAdviseSetPreferredLocation, device));
cudaCheck(cudaMemPrefetchAsync(buffer.mPtr, size, device, stream));
cudaCheck(util::cuda::memAdvise(buffer.mPtr, size, cudaMemAdviseSetPreferredLocation, device));
cudaCheck(util::cuda::memPrefetchAsync(buffer.mPtr, size, device, stream));
return buffer;
}
/// @brief Factory method that created a buffer on the host of the specified size. If the
/// reference buffer has a capacity it is used. Also the buffer is prefethed to the host
/// reference buffer has a capacity it is used. Also the buffer is prefetched to the host
/// @param size byte size of buffer initiated on the host
/// @param reference optional reference buffer from which the capacity is derived
static UnifiedBuffer create(size_t size, const UnifiedBuffer* reference){return create(size, reference, cudaCpuDeviceId, (cudaStream_t)0);}
/// @brief Factory method that created a buffer on the host or device of the specified size. If the
/// reference buffer has a capacity it is used. Also the buffer is prefethed to the host or (current) device
/// reference buffer has a capacity it is used. Also the buffer is prefetched to the host or (current) device
/// @param size byte size of buffer initiated on the device or host
/// @param reference optional reference buffer from which the capacity is derived
/// @param host If true the buffer will be prefectched to the host, else to the current device
/// @param host If true the buffer will be prefetched to the host, else to the current device
/// @param stream optional cuda stream
static UnifiedBuffer create(size_t size, const UnifiedBuffer* reference, bool host, void* stream = nullptr)
{
@@ -184,7 +184,7 @@ public:
} else {
void *ptr = 0;
cudaCheck(cudaMallocManaged(&ptr, size, cudaMemAttachGlobal));
if (dev > -2) for (auto a : list) cudaCheck(cudaMemAdvise(ptr, size, a, dev));
if (dev > -2) for (auto a : list) cudaCheck(util::cuda::memAdvise(ptr, size, a, dev));
if (mSize > 0) {// copy over data from the old memory block
cudaCheck(cudaMemcpy(ptr, mPtr, std::min(mSize, size), cudaMemcpyDefault));
cudaCheck(cudaFree(mPtr));
@@ -195,51 +195,51 @@ public:
}
/// @brief Apply a single advise to a memory block
/// @param byteOffset offset in bytes marking the begenning of the memory block to be advised
/// @param byteOffset offset in bytes marking the beginning of the memory block to be advised
/// @param size size in bytes of the memory block to be advised.
/// @param dev the device ID on which to apply the advice provided in adv, cudaCpuDeviceId = -1, 0, 1, ...
/// @param adv advice to be applied to the resized range
void advise(ptrdiff_t byteOffset, size_t size, int dev, cudaMemoryAdvise adv)
void advise(ptrdiff_t byteOffset, size_t size, int dev, cudaMemoryAdvise adv) const
{
cudaCheck(cudaMemAdvise(util::PtrAdd(mPtr, byteOffset), size, adv, dev));
cudaCheck(util::cuda::memAdvise(util::PtrAdd(mPtr, byteOffset), size, adv, dev));
}
/// @brief Apply a list of advices to a memory block
/// @param byteOffset offset in bytes marking the begenning of the memory block to be advised
/// @param byteOffset offset in bytes marking the beginning of the memory block to be advised
/// @param size size in bytes of the memory block to be advised.
/// @param dev the device ID to prefetch to, cudaCpuDeviceId = -1, 0, 1, ...
/// @param list list of cuda advises
void advise(ptrdiff_t byteOffset, size_t size, int dev, std::initializer_list<cudaMemoryAdvise> list)
void advise(ptrdiff_t byteOffset, size_t size, int dev, std::initializer_list<cudaMemoryAdvise> list) const
{
void *ptr = util::PtrAdd(mPtr, byteOffset);
for (auto a : list) cudaCheck(cudaMemAdvise(ptr, size, a, dev));
for (auto a : list) cudaCheck(util::cuda::memAdvise(ptr, size, a, dev));
}
/// @brief Prefetches data to the specified device, i.e. ensure the device has an up-to-date copy of the memory specified
/// @param byteOffset offset in bytes marking the begenning of the memory block to be prefetched
/// @param byteOffset offset in bytes marking the beginning of the memory block to be prefetched
/// @param size size in bytes of the memory block to be prefetched. The default value of zero means copy all @c this->size() bytes.
/// @param dev the device ID to prefetch to, cudaCpuDeviceId = -1, 0, 1, ...
/// @param stream cuda stream
void prefetch(ptrdiff_t byteOffset = 0, size_t size = 0, int dev = cudaCpuDeviceId, cudaStream_t stream = cudaStreamPerThread)
void prefetch(ptrdiff_t byteOffset = 0, size_t size = 0, int dev = cudaCpuDeviceId, cudaStream_t stream = cudaStreamPerThread) const
{
cudaCheck(cudaMemPrefetchAsync(util::PtrAdd(mPtr, byteOffset), size ? size : mSize, dev, stream));
cudaCheck(util::cuda::memPrefetchAsync(util::PtrAdd(mPtr, byteOffset), size ? size : mSize, dev, stream));
}
///////////////////////////////////////////////////////////////////////
/// @brief Prefectches all data to the specified device
/// @brief Prefetches all data to the specified device
/// @param device device ID, cudaCpuDeviceId = -1, 0, 1, ...
/// @param stream cuda stream
/// @param sync if false the memory copy is asynchronous
/// @note Legacy method included for compatibility with DeviceBuffer
void deviceUpload(int device = 0, cudaStream_t stream = cudaStreamPerThread, bool sync = false) const
{
cudaCheck(cudaMemPrefetchAsync(mPtr, mSize, device, stream));
cudaCheck(util::cuda::memPrefetchAsync(mPtr, mSize, device, stream));
if (sync) cudaCheck(cudaStreamSynchronize(stream));
}
void deviceUpload(int device, void* stream, bool sync) const{this->deviceUpload(device, cudaStream_t(stream));}
/// @brief Prefectches all data to the current device, as given by cudaGetDevice
/// @brief Prefetches all data to the current device, as given by cudaGetDevice
/// @param stream cuda stream
/// @param sync if false the memory copy is asynchronous
/// @note Legacy method included for compatibility with DeviceBuffer
@@ -254,10 +254,9 @@ public:
/// @brief Prefetches all data to the host
/// @param stream cuda stream
/// @param sync if false the memory copy is asynchronous
void deviceDownload(cudaStream_t stream = 0, bool sync = false) const
{
cudaCheck(cudaMemPrefetchAsync(mPtr, mSize, cudaCpuDeviceId, stream));
cudaCheck(util::cuda::memPrefetchAsync(mPtr, mSize, cudaCpuDeviceId, stream));
if (sync) cudaCheck(cudaStreamSynchronize(stream));
}
@@ -271,11 +270,11 @@ public:
///////////////////////////////////////////////////////////////////////
/// @brief Retuns a raw pointer to the unified memory managed by this instance.
/// @brief Returns a raw pointer to the unified memory managed by this instance.
/// @warning Note that the pointer can be NULL!
void* data() const {return mPtr;}
/// @brief Returns an offset pointer of a specefic type from the allocated unified memory
/// @brief Returns an offset pointer of a specific type from the allocated unified memory
/// @tparam T Type of the pointer returned
/// @param count Numbers of elements of @c parameter type T to skip (or offset) the return pointer
/// @warning assumes that this instance is not empty!
+7 -2
View File
@@ -7,7 +7,7 @@
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
project(NanoVDBExamples LANGUAGES CXX)
include(GNUInstallDirs)
@@ -100,14 +100,19 @@ nanovdb_example(NAME "ex_read_nanovdb_sphere")
nanovdb_example(NAME "ex_read_nanovdb_sphere_accessor")
nanovdb_example(NAME "ex_read_nanovdb_sphere_accessor_cuda")
nanovdb_example(NAME "ex_index_grid_cuda")
nanovdb_example(NAME "ex_nodemanager_cuda")
nanovdb_example(NAME "ex_nodemanager_cuda" OPENVDB)
nanovdb_example(NAME "ex_voxels_to_grid_cuda")
nanovdb_example(NAME "ex_voxels_to_grid_mgpu")
nanovdb_example(NAME "ex_modify_nanovdb_thrust")
nanovdb_example(NAME "ex_map_pool_buffer")
nanovdb_example(NAME "ex_bump_pool_buffer")
nanovdb_example(NAME "ex_collide_level_set")
nanovdb_example(NAME "ex_raytrace_fog_volume")
nanovdb_example(NAME "ex_raytrace_level_set")
nanovdb_example(NAME "ex_dilate_nanovdb_cuda" OPENVDB)
nanovdb_example(NAME "ex_merge_nanovdb_cuda" OPENVDB)
nanovdb_example(NAME "ex_refine_nanovdb_cuda" OPENVDB)
nanovdb_example(NAME "ex_coarsen_nanovdb_cuda" OPENVDB)
if(CUDAToolkit_FOUND)
nanovdb_example(NAME "ex_make_mgpu_nanovdb") # requires cuRAND
@@ -0,0 +1,150 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
// the following files are from OpenVDB
#include <openvdb/tools/Composite.h>
#include <openvdb/util/CpuTimer.h>
// the following files are from NanoVDB
#include <nanovdb/NanoVDB.h>
#include <nanovdb/cuda/DeviceBuffer.h>
#include <nanovdb/tools/CreateNanoGrid.h>
template<class CoordT>
inline CoordT
coarsenCoord(const CoordT& coord)
{
auto coarsenComponent = [](const typename CoordT::ValueType n) {return (n>=0) ? (n>>1) : -((-n+1)>>1);};
CoordT result;
result[0] = coarsenComponent(coord[0]);
result[1] = coarsenComponent(coord[1]);
result[2] = coarsenComponent(coord[2]);
return result;
}
template<typename BuildT>
void mainCoarsenGrid(
nanovdb::NanoGrid<BuildT> *deviceGridOriginal,
nanovdb::NanoGrid<BuildT> *deviceGridCoarsened,
nanovdb::NanoGrid<BuildT> *indexGridOriginal,
nanovdb::NanoGrid<BuildT> *indexGridCoarsened,
uint32_t benchmark_iters
);
/// @brief This example depends on OpenVDB, NanoVDB, and CUDA
int main(int argc, char *argv[])
{
using GridT = openvdb::FloatGrid;
using BuildT = nanovdb::ValueOnIndex;
openvdb::util::CpuTimer cpuTimer;
const bool printGridDiagnostics = true;
try {
if (argc<2) OPENVDB_THROW(openvdb::ValueError, "usage: "+std::string(argv[0])+" input.vdb [<iterations>]\n");
int benchmark_iters = 10;
if (argc > 2) sscanf(argv[2], "%d", &benchmark_iters);
// Read the initial level set from file
cpuTimer.start("Read input VDB file");
openvdb::initialize();
openvdb::io::File inFile(argv[1]);
inFile.open(false); // disable delayed loading
auto baseGrids = inFile.getGrids();
inFile.close();
auto grid = openvdb::gridPtrCast<GridT>(baseGrids->at(0));
if (!grid) OPENVDB_THROW(openvdb::ValueError, "First grid is not a FloatGrid\n");
cpuTimer.stop();
// Convert to indexGrid (original, un-coarsened)
cpuTimer.start("Converting openVDB input to indexGrid (original version)");
auto handleOriginal = nanovdb::tools::openToIndexVDB<BuildT, nanovdb::cuda::DeviceBuffer>(
grid,
0u, // Don't copy data channel
false, // No stats
false, // No tiles
1 // Verbose mode
);
auto *indexGridOriginal = handleOriginal.grid<BuildT>();
cpuTimer.stop();
if (printGridDiagnostics) {
std::cout << "============ Original Grid ===========" << std::endl;
std::cout << "Allocated values [valueCount()] : " << indexGridOriginal->valueCount() << std::endl;
std::cout << "Active voxels [activeVoxelCount()] : " << indexGridOriginal->activeVoxelCount() << std::endl;
auto minCorner = indexGridOriginal->indexBBox().min(), maxCorner = indexGridOriginal->indexBBox().max();
std::cout << "Index-space bounding box : [" << minCorner.x() << "," << minCorner.y() << "," << minCorner.z()
<< "] -> [" << maxCorner.x() << "," << maxCorner.y() << "," << maxCorner.z() << "]" << std::endl;
std::cout << "Leaf nodes : " << indexGridOriginal->tree().nodeCount(0) << std::endl;
std::cout << "Lower internal nodes : " << indexGridOriginal->tree().nodeCount(1) << std::endl;
std::cout << "Upper internal nodes : " << indexGridOriginal->tree().nodeCount(2) << std::endl;
std::cout << "Leaf-level occupancy : "
<< 100.f * (float)(indexGridOriginal->activeVoxelCount())/(float)(indexGridOriginal->tree().nodeCount(0) * 512)
<< "%" << std::endl;
std::cout << "Memory usage : " << indexGridOriginal->gridSize() << " bytes" << std::endl;
}
// Coarsening (CPU/OpenVDB version)
cpuTimer.start("Coarsening OpenVDB (on CPU)");
using TreeT = GridT::TreeType;
using LeafManagerT = openvdb::tree::LeafManager<const TreeT>;
LeafManagerT leafMgr(grid->tree());
auto coarsenedGrid = openvdb::FloatGrid::create(grid->background());
coarsenedGrid->setTransform(grid->transform().copy());
coarsenedGrid->setName(grid->getName());
auto dstAcc = coarsenedGrid->getAccessor();
for (std::size_t leafID = 0; leafID < leafMgr.leafCount(); ++leafID) {
const auto& srcLeaf = leafMgr.leaf(leafID);
for (auto iter = srcLeaf.cbeginValueOn(); iter; ++iter) {
const auto dstCoord = coarsenCoord(iter.getCoord());
if (!dstAcc.isValueOn(dstCoord))
dstAcc.setValue(dstCoord, iter.getValue());
}
}
cpuTimer.stop();
// Convert to indexGrid (coarsened)
cpuTimer.start("Converting openVDB input to indexGrid (coarsened version)");
auto handleCoarsened = nanovdb::tools::openToIndexVDB<BuildT, nanovdb::cuda::DeviceBuffer>(
coarsenedGrid,
0u, // Don't copy data channel
false, // No stats
false, // No tiles
1 // Verbose mode
);
cpuTimer.stop();
auto *indexGridCoarsened = handleCoarsened.grid<BuildT>();
if (printGridDiagnostics) {
std::cout << "=========== Coarsened Grid ===========" << std::endl;
std::cout << "Allocated values [valueCount()] : " << indexGridCoarsened->valueCount() << std::endl;
std::cout << "Active voxels [activeVoxelCount()] : " << indexGridCoarsened->activeVoxelCount() << std::endl;
auto minCorner = indexGridCoarsened->indexBBox().min(), maxCorner = indexGridCoarsened->indexBBox().max();
std::cout << "Index-space bounding box : [" << minCorner.x() << "," << minCorner.y() << "," << minCorner.z()
<< "] -> [" << maxCorner.x() << "," << maxCorner.y() << "," << maxCorner.z() << "]" << std::endl;
std::cout << "Leaf nodes : " << indexGridCoarsened->tree().nodeCount(0) << std::endl;
std::cout << "Lower internal nodes : " << indexGridCoarsened->tree().nodeCount(1) << std::endl;
std::cout << "Upper internal nodes : " << indexGridCoarsened->tree().nodeCount(2) << std::endl;
std::cout << "Leaf-level occupancy : "
<< 100.f * (float)(indexGridCoarsened->activeVoxelCount())/(float)(indexGridCoarsened->tree().nodeCount(0) * 512)
<< "%" << std::endl;
std::cout << "Memory usage : " << indexGridCoarsened->gridSize() << " bytes" << std::endl;
}
// Copy both NanoVDB grids to GPU
handleOriginal.deviceUpload();
handleCoarsened.deviceUpload();
auto* deviceGridOriginal = handleOriginal.deviceGrid<BuildT>();
auto* deviceGridCoarsened = handleCoarsened.deviceGrid<BuildT>();
if (!deviceGridOriginal || !deviceGridCoarsened)
OPENVDB_THROW(openvdb::RuntimeError, "Failure while uploading indexGrids to GPU");
// Launch benchmark
mainCoarsenGrid( deviceGridOriginal, deviceGridCoarsened, indexGridOriginal, indexGridCoarsened, benchmark_iters );
}
catch (const std::exception& e) {
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
}
return 0;
}
@@ -0,0 +1,56 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
#include <nanovdb/tools/cuda/CoarsenGrid.cuh>
template<typename T>
bool bufferCheck(const T* deviceBuffer, const T* hostBuffer, size_t elem_count) {
T* tmpBuffer = new T[elem_count];
cudaCheck(cudaMemcpy(tmpBuffer, deviceBuffer, elem_count * sizeof(T), cudaMemcpyDeviceToHost));
bool same = true;
for (int i=0; same && i< elem_count; ++i) { same = (tmpBuffer[i] == hostBuffer[i]); }
delete [] tmpBuffer;
return same;
}
template<typename BuildT>
void mainCoarsenGrid(
nanovdb::NanoGrid<BuildT> *deviceGridOriginal,
nanovdb::NanoGrid<BuildT> *deviceGridCoarsened,
nanovdb::NanoGrid<BuildT> *indexGridOriginal,
nanovdb::NanoGrid<BuildT> *indexGridCoarsened,
uint32_t benchmark_iters)
{
nanovdb::util::cuda::Timer gpuTimer;
// Initialize coarsener
nanovdb::tools::cuda::CoarsenGrid<BuildT> coarsener( deviceGridOriginal );
coarsener.setChecksum(nanovdb::CheckMode::Default);
coarsener.setVerbose(1);
auto handle = coarsener.getHandle();
auto dstGrid = handle.template deviceGrid<BuildT>();
// Check for correctness
if (bufferCheck((char*)dstGrid, (char*)indexGridCoarsened->data(), indexGridCoarsened->gridSize()))
std::cout << "Result of CoarsenGrid check out CORRECT against reference" << std::endl;
else
std::cout << "Result of CoarsenGrid compares INCORRECT against reference" << std::endl;
// Re-run warm-started iterations
coarsener.setVerbose(0);
for (int i = 0; i < benchmark_iters; i++) {
gpuTimer.start("Re-running entire coarsening after warmstart");
auto dummyHandle = coarsener.getHandle();
gpuTimer.stop();
}
}
template
void mainCoarsenGrid(
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *deviceGridOriginal,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *deviceGridCoarsened,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *indexGridOriginal,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *indexGridCoarsened,
uint32_t benchmark_iters
);
@@ -0,0 +1,133 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
// the following files are from OpenVDB
#include <openvdb/tools/Morphology.h>
#include <openvdb/util/CpuTimer.h>
// the following files are from NanoVDB
#include <nanovdb/NanoVDB.h>
#include <nanovdb/cuda/DeviceBuffer.h>
#include <nanovdb/tools/CreateNanoGrid.h>
template<typename BuildT>
void mainDilateGrid(
nanovdb::NanoGrid<BuildT> *deviceGridOriginal,
nanovdb::NanoGrid<BuildT> *deviceGridDilated,
nanovdb::NanoGrid<BuildT> *indexGridOriginal,
nanovdb::NanoGrid<BuildT> *indexGridDilated,
uint32_t nnType,
uint32_t benchmark_iters
);
/// @brief This example depends on OpenVDB, NanoVDB, and CUDA
int main(int argc, char *argv[])
{
using GridT = openvdb::FloatGrid;
using BuildT = nanovdb::ValueOnIndex;
// Select the type of dilation here. The NN_EDGE case supports leaf dilation too (currently)
// openvdb::tools::NearestNeighbors nnType = openvdb::tools::NN_FACE_EDGE_VERTEX;
openvdb::tools::NearestNeighbors nnType = openvdb::tools::NN_FACE;
openvdb::util::CpuTimer cpuTimer;
const bool printGridDiagnostics = true;
try {
if (argc<2) OPENVDB_THROW(openvdb::ValueError, "usage: "+std::string(argv[0])+" input.vdb [<iterations>]\n");
int benchmark_iters = 10;
if (argc > 2) sscanf(argv[2], "%d", &benchmark_iters);
// Read the initial level set from file
cpuTimer.start("Read input VDB file");
openvdb::initialize();
openvdb::io::File inFile(argv[1]);
inFile.open(false); // disable delayed loading
auto baseGrids = inFile.getGrids();
inFile.close();
auto grid = openvdb::gridPtrCast<GridT>(baseGrids->at(0));
openvdb::FloatGrid* ptr = grid.get(); // raw pointer
if (!grid) OPENVDB_THROW(openvdb::ValueError, "First grid is not a FloatGrid\n");
cpuTimer.stop();
// Convert to indexGrid (original, un-dilated)
cpuTimer.start("Converting openVDB input to indexGrid (original version)");
auto handleOriginal = nanovdb::tools::openToIndexVDB<BuildT, nanovdb::cuda::DeviceBuffer>(
grid,
0u, // Don't copy data channel
false, // No stats
false, // No tiles
1 // Verbose mode
);
auto *indexGridOriginal = handleOriginal.grid<BuildT>();
cpuTimer.stop();
if (printGridDiagnostics) {
std::cout << "============ Original Grid ===========" << std::endl;
std::cout << "Allocated values [valueCount()] : " << indexGridOriginal->valueCount() << std::endl;
std::cout << "Active voxels [activeVoxelCount()] : " << indexGridOriginal->activeVoxelCount() << std::endl;
auto minCorner = indexGridOriginal->indexBBox().min(), maxCorner = indexGridOriginal->indexBBox().max();
std::cout << "Index-space bounding box : [" << minCorner.x() << "," << minCorner.y() << "," << minCorner.z()
<< "] -> [" << maxCorner.x() << "," << maxCorner.y() << "," << maxCorner.z() << "]" << std::endl;
std::cout << "Leaf nodes : " << indexGridOriginal->tree().nodeCount(0) << std::endl;
std::cout << "Lower internal nodes : " << indexGridOriginal->tree().nodeCount(1) << std::endl;
std::cout << "Upper internal nodes : " << indexGridOriginal->tree().nodeCount(2) << std::endl;
std::cout << "Leaf-level occupancy : "
<< 100.f * (float)(indexGridOriginal->activeVoxelCount())/(float)(indexGridOriginal->tree().nodeCount(0) * 512)
<< "%" << std::endl;
std::cout << "Memory usage : " << indexGridOriginal->gridSize() << " bytes" << std::endl;
}
// Dilation (CPU/OpenVDB version)
cpuTimer.start("Dilating openVDB (on CPU)");
openvdb::tools::dilateActiveValues(grid->tree(), 1, nnType);
cpuTimer.stop();
// Convert to indexGrid (dilated)
cpuTimer.start("Converting openVDB input to indexGrid (dilated version)");
auto handleDilated = nanovdb::tools::openToIndexVDB<BuildT, nanovdb::cuda::DeviceBuffer>(
grid,
0u, // Don't copy data channel
false, // No stats
false, // No tiles
1 // Verbose mode
);
cpuTimer.stop();
auto *indexGridDilated = handleDilated.grid<BuildT>();
if (printGridDiagnostics) {
std::cout << "============ Dilated Grid ============" << std::endl;
std::cout << "Allocated values [valueCount()] : " << indexGridDilated->valueCount() << std::endl;
std::cout << "Active voxels [activeVoxelCount()] : " << indexGridDilated->activeVoxelCount() << std::endl;
auto minCorner = indexGridDilated->indexBBox().min(), maxCorner = indexGridDilated->indexBBox().max();
std::cout << "Index-space bounding box : [" << minCorner.x() << "," << minCorner.y() << "," << minCorner.z()
<< "] -> [" << maxCorner.x() << "," << maxCorner.y() << "," << maxCorner.z() << "]" << std::endl;
std::cout << "Leaf nodes : " << indexGridDilated->tree().nodeCount(0) << std::endl;
std::cout << "Lower internal nodes : " << indexGridDilated->tree().nodeCount(1) << std::endl;
std::cout << "Upper internal nodes : " << indexGridDilated->tree().nodeCount(2) << std::endl;
std::cout << "Leaf-level occupancy : "
<< 100.f * (float)(indexGridDilated->activeVoxelCount())/(float)(indexGridDilated->tree().nodeCount(0) * 512)
<< "%" << std::endl;
std::cout << "Memory usage : " << indexGridDilated->gridSize() << " bytes" << std::endl;
}
// Copy both NanoVDB grids to GPU
handleOriginal.deviceUpload();
handleDilated.deviceUpload();
auto* deviceGridOriginal = handleOriginal.deviceGrid<BuildT>();
auto* deviceGridDilated = handleDilated.deviceGrid<BuildT>();
if (!deviceGridOriginal || !deviceGridDilated)
OPENVDB_THROW(openvdb::RuntimeError, "Failure while uploading indexGrids to GPU");
// Launch benchmark
mainDilateGrid( deviceGridOriginal, deviceGridDilated, indexGridOriginal, indexGridDilated, nnType, benchmark_iters );
}
catch (const std::exception& e) {
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
}
return 0;
}
@@ -0,0 +1,102 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
#include <nanovdb/tools/cuda/DilateGrid.cuh>
#include <nanovdb/tools/cuda/PruneGrid.cuh>
#include <nanovdb/util/cuda/Injection.cuh>
template<typename T>
bool bufferCheck(const T* deviceBuffer, const T* hostBuffer, size_t elem_count) {
T* tmpBuffer = new T[elem_count];
cudaCheck(cudaMemcpy(tmpBuffer, deviceBuffer, elem_count * sizeof(T), cudaMemcpyDeviceToHost));
bool same = true;
for (int i=0; same && i< elem_count; ++i) { same = (tmpBuffer[i] == hostBuffer[i]); }
delete [] tmpBuffer;
return same;
}
template<typename BuildT>
void mainDilateGrid(
nanovdb::NanoGrid<BuildT> *deviceGridOriginal,
nanovdb::NanoGrid<BuildT> *deviceGridDilated,
nanovdb::NanoGrid<BuildT> *indexGridOriginal,
nanovdb::NanoGrid<BuildT> *indexGridDilated,
uint32_t nnType,
uint32_t benchmark_iters)
{
nanovdb::util::cuda::Timer gpuTimer;
// Initialize dilator
nanovdb::tools::cuda::DilateGrid<BuildT> dilator( deviceGridOriginal );
dilator.setOperation(nanovdb::tools::morphology::NearestNeighbors(nnType));
dilator.setChecksum(nanovdb::CheckMode::Default);
dilator.setVerbose(1);
auto handle = dilator.getHandle();
auto dstGrid = handle.template deviceGrid<BuildT>();
// Check for correctness
if (bufferCheck((char*)dstGrid, (char*)indexGridDilated->data(), indexGridDilated->gridSize()))
std::cout << "Result of DilateGrid check out CORRECT against reference" << std::endl;
else
std::cout << "Result of DilateGrid compares INCORRECT against reference" << std::endl;
// Re-run warm-started iterations
dilator.setVerbose(0);
for (int i = 0; i < benchmark_iters; i++) {
gpuTimer.start("Re-running entire dilation after warmstart");
auto dummyHandle = dilator.getHandle();
gpuTimer.stop();
}
uint32_t dstLeafCount = nanovdb::util::cuda::DeviceGridTraits<BuildT>::getTreeData(dstGrid).mNodeCount[0];
nanovdb::cuda::DeviceBuffer dstLeafMaskBuffer;
nanovdb::Mask<3>* dstLeafMasks = nullptr;
if (dstLeafCount) {
dstLeafMaskBuffer = nanovdb::cuda::DeviceBuffer::create( std::size_t(dstLeafCount) * sizeof(nanovdb::Mask<3>), nullptr, false );
dstLeafMasks = static_cast<nanovdb::Mask<3>*>(dstLeafMaskBuffer.deviceData());
if (!dstLeafMasks) throw std::runtime_error("No GPU buffer for dstLeafMask");
}
const unsigned int numThreads = 128;
auto numBlocks = [numThreads] (unsigned int n) {return (n + numThreads - 1) / numThreads;};
gpuTimer.start("Injecting un-dilated topology as a pruning mask");
if (dstLeafCount)
nanovdb::util::cuda::lambdaKernel<<<numBlocks(dstLeafCount), numThreads>>>(dstLeafCount,
nanovdb::util::cuda::InjectGridMaskFunctor<BuildT>(),
deviceGridOriginal, dstGrid, dstLeafMasks );
gpuTimer.stop();
// Initialize pruner
nanovdb::tools::cuda::PruneGrid<BuildT> pruner( dstGrid, dstLeafMasks );
pruner.setChecksum(nanovdb::CheckMode::Default);
pruner.setVerbose(1);
auto prunedHandle = pruner.getHandle();
auto prunedGrid = prunedHandle.template deviceGrid<BuildT>();
// Check for correctness
if (bufferCheck((char*)prunedGrid, (char*)indexGridOriginal->data(), indexGridOriginal->gridSize()))
std::cout << "Result of PruneGrid check out CORRECT against reference" << std::endl;
else
std::cout << "Result of PruneGrid compares INCORRECT against reference" << std::endl;
// Re-run warm-started iterations
pruner.setVerbose(0);
for (int i = 0; i < benchmark_iters; i++) {
gpuTimer.start("Re-running entire pruning after warmstart");
auto dummyHandle = pruner.getHandle();
gpuTimer.stop();
}
}
template
void mainDilateGrid(
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *deviceGridOriginal,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *deviceGridDilated,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *indexGridOriginal,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *indexGridDilated,
uint32_t nnType,
uint32_t benchmark_iters
);
@@ -3,16 +3,17 @@
#include <nanovdb/NanoVDB.h> // this defined the core tree data structure of NanoVDB accessable on both the host and device
#include <nanovdb/cuda/GridHandle.cuh>// required since GridHandle<DeviceBuffer> has device code
#include <stdio.h> // for printf
#include <cstdio> // for printf
#include <cinttypes>
// This is called by the host only
void cpu_kernel(const nanovdb::NanoGrid<nanovdb::ValueOnIndex>* cpuGrid)
{
nanovdb::ChannelAccessor<float, nanovdb::ValueOnIndex> acc(*cpuGrid);
//printf("\nNanoVDB CPU: channels=%u values=%lu\n", acc.grid().blindDataCount(), acc.root().maximum());
printf("NanoVDB CPU; %lu\n", acc.idx( 0, 0, 0));
printf("NanoVDB CPU; %lu\n", acc.idx( 99, 0, 0));
printf("NanoVDB CPU; %lu\n", acc.idx(100, 0, 0));
printf("NanoVDB CPU; %" PRIu64 "\n", acc.idx( 0, 0, 0));
printf("NanoVDB CPU; %" PRIu64 "\n", acc.idx( 99, 0, 0));
printf("NanoVDB CPU; %" PRIu64 "\n", acc.idx(100, 0, 0));
printf("NanoVDB CPU; %4.2f\n", acc( 0, 0, 0));
printf("NanoVDB CPU; %4.2f\n", acc( 99, 0, 0));
printf("NanoVDB CPU; %4.2f\n", acc(100, 0, 0));
@@ -23,9 +24,9 @@ __global__ void gpu_kernel(const nanovdb::NanoGrid<nanovdb::ValueOnIndex>* gpuGr
{
nanovdb::ChannelAccessor<float, nanovdb::ValueOnIndex> acc(*gpuGrid);
//printf("\nNanoVDB GPU: channels=%u values=%lu\n", gpuGrid->blindDataCount(), acc.root().maximum());
printf("NanoVDB GPU; %lu\n", acc.idx( 0, 0, 0));
printf("NanoVDB GPU; %lu\n", acc.idx( 99, 0, 0));
printf("NanoVDB GPU; %lu\n", acc.idx(100, 0, 0));
printf("NanoVDB GPU; %" PRIu64 "\n", acc.idx( 0, 0, 0));
printf("NanoVDB GPU; %" PRIu64 "\n", acc.idx( 99, 0, 0));
printf("NanoVDB GPU; %" PRIu64 "\n", acc.idx(100, 0, 0));
printf("NanoVDB GPU; %4.2f\n", acc( 0, 0, 0));
printf("NanoVDB GPU; %4.2f\n", acc( 99, 0, 0));
printf("NanoVDB GPU; %4.2f\n", acc(100, 0, 0));
@@ -39,4 +40,4 @@ extern "C" void launch_kernels(const nanovdb::NanoGrid<nanovdb::ValueOnIndex>* g
gpu_kernel<<<1, 1, 0, stream>>>(gpuGrid); // Launch the device kernel asynchronously
cpu_kernel(cpuGrid); // Launch the host "kernel" (synchronously)
}
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
#include <nanovdb/tools/CreatePrimitives.h>
#include <nanovdb/cuda/UnifiedBuffer.h>
#include <nanovdb/cuda/DeviceStreamMap.h>
#include <nanovdb/cuda/DeviceMesh.h>
#include <nanovdb/util/cuda/Timer.h>
#include <cassert>
@@ -177,12 +177,12 @@ void testConvolution()
for (int i = 0; i < Do * 27 * Di; i++) stencilHostPtr[i] = uniform_dist(gen);
nanovdb::cuda::DeviceStreamMap devStreamMap( nanovdb::cuda::DeviceStreamMap::DeviceType::Unified, {}, 2);
const size_t deviceCount = devStreamMap.size();
nanovdb::cuda::DeviceMesh deviceMesh;
const size_t deviceCount = deviceMesh.deviceCount();
std::cout << "Number of devices that supports unified memory: " << deviceCount << std::endl;
// Calculate minimum page size which corresponds to minimum physical allocation granularity
size_t minGranularity = devStreamMap.getMinPageSize();
size_t minGranularity = nanovdb::cuda::minDevicePageSize(deviceMesh);
// Ensure that we don't split an input and/or output feature across page boundaries
minGranularity = std::lcm(std::lcm(minGranularity, sizeof(InputBufferType)), sizeof(OutputBufferType));
@@ -194,10 +194,10 @@ void testConvolution()
using BufferT = nanovdb::cuda::DeviceBuffer;
auto indexHandle = nanovdb::tools::createNanoGrid<nanovdb::FloatGrid, nanovdb::ValueOnIndex, BufferT>(*floatGrid, 0u, false, false, 1);
for (auto& [device, stream] : devStreamMap) {// copy host buffer to all the device buffers
cudaSetDevice(device);
indexHandle.deviceUpload(device, stream, true);
}
std::for_each(deviceMesh.begin(), deviceMesh.end(), [&](const nanovdb::cuda::DeviceNode& node) {// copy host buffer to all the device buffers
cudaCheck(cudaSetDevice(node.id));
indexHandle.deviceUpload(node.id, node.stream, true);
});
auto* indexGrid = indexHandle.grid<nanovdb::ValueOnIndex>();
auto ceil = [](size_t x, size_t y)->size_t{return ((x + y - 1) / y) * y;};
@@ -213,25 +213,39 @@ void testConvolution()
// Randomly initialize input features
std::vector<std::thread> threads;
auto streamIter = devStreamMap.begin();
for (int i = 0; i < deviceCount; ++i, ++streamIter) {
threads.push_back(std::thread([&, i, streamIter]() {
cudaSetDevice(streamIter->first);
float* inputStripePtr = inputBuffer.data<float>(deviceValueCount * Di * i);
float* outputStripePtr = outputBuffer.data<float>(deviceValueCount * Do * i);
std::for_each(deviceMesh.begin(), deviceMesh.end(), [&](const nanovdb::cuda::DeviceNode& node) {
threads.emplace_back([&](int device, cudaStream_t stream) {
cudaCheck(cudaSetDevice(device));
float* inputStripePtr = inputBuffer.data<float>(deviceValueCount * Di * device);
float* outputStripePtr = outputBuffer.data<float>(deviceValueCount * Do * device);
unsigned long long seed = 42u;
curandGenerator_t rng;
curandStatus_t curandStatus = curandCreateGenerator(&rng, CURAND_RNG_PSEUDO_DEFAULT);
curandStatus = curandSetStream(rng, stream);
curandStatus = curandSetPseudoRandomGeneratorSeed(rng, seed);
curandStatus = curandGenerateUniform(rng, inputStripePtr, deviceValueCount * Di);
curandStatus = curandGenerateUniform(rng, outputStripePtr, deviceValueCount * Do);
curandStatus = curandDestroyGenerator(rng);
NANOVDB_ASSERT(curandStatus == CURAND_STATUS_SUCCESS);
cudaDeviceSynchronize();
}));
}
for (int i = 0; i < deviceCount; ++i) threads[i].join();
cudaCheck(cudaStreamSynchronize(stream));
// If we use managed memory, we need to advise about the usage of the memory range in order to obtain an
// equivalently optimal paging strategy. cudaMemAdviseSetReadMostly instructs the "paging policy" that data
// is far more likely to be read than written, cudaMemAdviseSetPreferredLocation suggests the preferred device to place the data on, and cudaMemAdviseSetAccessedBy is a hint about the which devices are accessing the data.
const size_t inPageSize = deviceValueCount * Di * sizeof(float), inPageOffset = inPageSize * device;// in bytes
const size_t outPageSize = deviceValueCount * Do * sizeof(float), outPageOffset = outPageSize * device;// in bytes
inputBuffer.advise(inPageOffset, inPageSize, device, {cudaMemAdviseSetReadMostly, cudaMemAdviseSetPreferredLocation});
inputBuffer.prefetch(inPageOffset, inPageSize, device, stream);
outputBuffer.advise(outPageOffset, outPageSize, device, cudaMemAdviseSetPreferredLocation);
std::for_each(deviceMesh.begin(), deviceMesh.end(), [&](const nanovdb::cuda::DeviceNode& otherNode) {
inputBuffer.advise( inPageOffset, inPageSize, otherNode.id, cudaMemAdviseSetAccessedBy);
outputBuffer.advise(outPageOffset, outPageSize, otherNode.id, cudaMemAdviseSetAccessedBy);
});
}, node.id, node.stream);
});
std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
threads.clear();
// Spawn a convolution kernel on each device that operates on a disjoint subset of the leaves. The leaves operated on by
@@ -239,63 +253,45 @@ void testConvolution()
// the same device.
auto **timers = new nanovdb::util::cuda::Timer*[deviceCount];
const size_t leafNodeCount = indexGrid->tree().nodeCount(0);
streamIter = devStreamMap.begin();
for (int i = 0; i < deviceCount; ++i, ++streamIter) {
threads.emplace_back([&, i, streamIter]() {// capture iterator by value and not reference
const int device = streamIter->first;
cudaStream_t stream = streamIter->second;
cudaSetDevice(device);
timers[i] = new nanovdb::util::cuda::Timer(stream);
size_t deviceLeafNodeCount = (leafNodeCount + deviceCount - 1) / deviceCount;
const size_t deviceLeafNodeOffset = deviceLeafNodeCount * i;
deviceLeafNodeCount = std::min(deviceLeafNodeCount, leafNodeCount - deviceLeafNodeOffset);
std::for_each(deviceMesh.begin(), deviceMesh.end(), [&](const nanovdb::cuda::DeviceNode& node) {
threads.emplace_back([&](int device, cudaStream_t stream) {
cudaCheck(cudaSetDevice(device));
timers[device] = new nanovdb::util::cuda::Timer(stream);
void* stencilDevicePtr;
cudaMallocAsync(&stencilDevicePtr, sizeof(float) * Do * 27 * Di, stream);
cudaMemcpyAsync(stencilDevicePtr, stencilHostPtr, sizeof(float) * Do * 27 * Di, cudaMemcpyHostToDevice, stream);
cudaCheck(nanovdb::util::cuda::mallocAsync(&stencilDevicePtr, sizeof(float) * Do * 27 * Di, stream));
cudaCheck(cudaMemcpyAsync(stencilDevicePtr, stencilHostPtr, sizeof(float) * Do * 27 * Di, cudaMemcpyHostToDevice, stream));
// If we use managed memory, we need to advise about the usage of the memory range in order to obtain an
// equivalently optimal paging strategy. cudaMemAdviseSetReadMostly instructs the "paging policy" that data
// is far more likely to be read than set, cudaMemAdviseSetPreferredLocation defines the device that is most like
// to the data, and cudaMemAdviseSetAccessedBy is a hint about the which devices are communicating data.
const size_t inPageSize = deviceValueCount * Di * sizeof(float), inPageOffset = inPageSize * i;// in bytes
const size_t outPageSize = deviceValueCount * Do * sizeof(float), outPageOffset = outPageSize * i;// in bytes
inputBuffer.advise(inPageOffset, inPageSize, device, {cudaMemAdviseSetReadMostly, cudaMemAdviseSetPreferredLocation});
inputBuffer.prefetch(inPageOffset, inPageSize, device, stream);
outputBuffer.advise(outPageOffset, outPageSize, device, cudaMemAdviseSetPreferredLocation);
for (auto other = devStreamMap.begin(); other != devStreamMap.end(); ++other) {
inputBuffer.advise( inPageOffset, inPageSize, other->first, cudaMemAdviseSetAccessedBy);
outputBuffer.advise(outPageOffset, outPageSize, other->first, cudaMemAdviseSetAccessedBy);
}
cudaStreamSynchronize(stream);
auto d_indexGrid = reinterpret_cast<nanovdb::OnIndexGrid*>(indexHandle.deviceData(device));
size_t deviceLeafNodeCount = (leafNodeCount + deviceCount - 1) / deviceCount;
const size_t deviceLeafNodeOffset = deviceLeafNodeCount * device;
deviceLeafNodeCount = std::min(deviceLeafNodeCount, leafNodeCount - deviceLeafNodeOffset);
auto deviceIndexGrid = reinterpret_cast<nanovdb::OnIndexGrid*>(indexHandle.deviceData(device));
// Run 10 warmup iterations
float* stencilPtr = (float*)stencilDevicePtr;
dim3 blockDim(256);
for (int k = 0; k < 10; ++k) {
stencilConvolve_v7<<<deviceLeafNodeCount * 2 * 4 * 4, blockDim, 0, stream>>>(
d_indexGrid, deviceLeafNodeOffset, inputBuffer.data<float>(), nullptr, nullptr, stencilPtr, outputBuffer.data<float>(), nullptr);
deviceIndexGrid, deviceLeafNodeOffset, inputBuffer.data<float>(), nullptr, nullptr, stencilPtr, outputBuffer.data<float>(), nullptr);
}
cudaStreamSynchronize(stream);
timers[i]->start();
timers[device]->start();
stencilConvolve_v7<<<deviceLeafNodeCount * 2 * 4 * 4, blockDim, 0, stream>>>(
d_indexGrid, deviceLeafNodeOffset, inputBuffer.data<float>(), nullptr, nullptr, stencilPtr, outputBuffer.data<float>(), nullptr);
timers[i]->record();
cudaFree(stencilDevicePtr);
});
}
for (int i = 0; i < deviceCount; ++i) threads[i].join();
delete [] stencilHostPtr;
deviceIndexGrid, deviceLeafNodeOffset, inputBuffer.data<float>(), nullptr, nullptr, stencilPtr, outputBuffer.data<float>(), nullptr);
timers[device]->record();
cudaCheck(nanovdb::util::cuda::freeAsync(stencilDevicePtr, stream));
cudaCheck(cudaStreamSynchronize(stream));
}, node.id, node.stream);
});
std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
threads.clear();
delete[] stencilHostPtr;
streamIter = devStreamMap.begin();
for (int i = 0; i < deviceCount; ++i, ++streamIter) {
timers[i]->print("Device " + std::to_string(streamIter->first) + " GPU convolution ", std::cout);
delete timers[i];
}
delete [] timers;
std::for_each(deviceMesh.begin(), deviceMesh.end(), [&](const nanovdb::cuda::DeviceNode& node) {
timers[node.id]->print("Device " + std::to_string(node.id) + " GPU convolution ", std::cout);
delete timers[node.id];
});
delete[] timers;
std::cout << "Multi-GPU sparse convolution test complete!" << std::endl;
}// testConvolution
@@ -0,0 +1,166 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
// the following files are from OpenVDB
#include <openvdb/tools/Composite.h>
#include <openvdb/util/CpuTimer.h>
// the following files are from NanoVDB
#include <nanovdb/NanoVDB.h>
#include <nanovdb/cuda/DeviceBuffer.h>
#include <nanovdb/tools/CreateNanoGrid.h>
template<typename BuildT>
void mainMergeGrids(
nanovdb::NanoGrid<BuildT> *deviceSrcGrid1,
nanovdb::NanoGrid<BuildT> *deviceSrcGrid2,
nanovdb::NanoGrid<BuildT> *deviceDstReferenceGrid,
nanovdb::NanoGrid<BuildT> *hostSrcGrid1,
nanovdb::NanoGrid<BuildT> *hostSrcGrid2,
nanovdb::NanoGrid<BuildT> *hostDstReferenceGrid,
uint32_t benchmark_iters
);
/// @brief This example depends on OpenVDB, NanoVDB, and CUDA
int main(int argc, char *argv[])
{
using GridT = openvdb::FloatGrid;
using BuildT = nanovdb::ValueOnIndex;
openvdb::util::CpuTimer cpuTimer;
const bool printGridDiagnostics = true;
try {
if (argc < 3) OPENVDB_THROW(openvdb::ValueError, "usage: "+std::string(argv[0])+" input1.vdb input2.vdb [<iterations>]\n");
int benchmark_iters = 10;
if (argc > 3) sscanf(argv[3], "%d", &benchmark_iters);
// Read the openvdb inputs from files
cpuTimer.start("Read first input VDB file");
openvdb::initialize();
openvdb::io::File inFile1(argv[1]);
inFile1.open(false); // disable delayed loading
auto baseGrids1 = inFile1.getGrids();
inFile1.close();
auto grid1 = openvdb::gridPtrCast<GridT>(baseGrids1->at(0));
if (!grid1) OPENVDB_THROW(openvdb::ValueError, "First grid is not a FloatGrid\n");
cpuTimer.stop();
cpuTimer.start("Read second input VDB file");
openvdb::initialize();
openvdb::io::File inFile2(argv[2]);
inFile2.open(false); // disable delayed loading
auto baseGrids2 = inFile2.getGrids();
inFile2.close();
auto grid2 = openvdb::gridPtrCast<GridT>(baseGrids2->at(0));
if (!grid2) OPENVDB_THROW(openvdb::ValueError, "Second grid is not a FloatGrid\n");
cpuTimer.stop();
// Convert to indexGrid
cpuTimer.start("Converting openVDB input to indexGrid (first component)");
auto srcHandle1 = nanovdb::tools::openToIndexVDB<BuildT, nanovdb::cuda::DeviceBuffer>(
grid1,
0u, // Don't copy data channel
false, // No stats
false, // No tiles
1 // Verbose mode
);
auto *srcIndexGrid1 = srcHandle1.grid<BuildT>();
cpuTimer.stop();
cpuTimer.start("Converting openVDB input to indexGrid (second component)");
auto srcHandle2 = nanovdb::tools::openToIndexVDB<BuildT, nanovdb::cuda::DeviceBuffer>(
grid2,
0u, // Don't copy data channel
false, // No stats
false, // No tiles
1 // Verbose mode
);
auto *srcIndexGrid2 = srcHandle2.grid<BuildT>();
cpuTimer.stop();
if (printGridDiagnostics) {
std::cout << "============ Component Grid #1 ======= " << std::endl;
std::cout << "Allocated values [valueCount()] : " << srcIndexGrid1->valueCount() << std::endl;
std::cout << "Active voxels [activeVoxelCount()] : " << srcIndexGrid1->activeVoxelCount() << std::endl;
auto minCorner = srcIndexGrid1->indexBBox().min(), maxCorner = srcIndexGrid1->indexBBox().max();
std::cout << "Index-space bounding box : [" << minCorner.x() << "," << minCorner.y() << "," << minCorner.z()
<< "] -> [" << maxCorner.x() << "," << maxCorner.y() << "," << maxCorner.z() << "]" << std::endl;
std::cout << "Leaf nodes : " << srcIndexGrid1->tree().nodeCount(0) << std::endl;
std::cout << "Lower internal nodes : " << srcIndexGrid1->tree().nodeCount(1) << std::endl;
std::cout << "Upper internal nodes : " << srcIndexGrid1->tree().nodeCount(2) << std::endl;
std::cout << "Leaf-level occupancy : "
<< 100.f * (float)(srcIndexGrid1->activeVoxelCount())/(float)(srcIndexGrid1->tree().nodeCount(0) * 512)
<< "%" << std::endl;
std::cout << "Memory usage : " << srcIndexGrid1->gridSize() << " bytes" << std::endl;
std::cout << "============ Component Grid #2 ======= " << std::endl;
std::cout << "Allocated values [valueCount()] : " << srcIndexGrid2->valueCount() << std::endl;
std::cout << "Active voxels [activeVoxelCount()] : " << srcIndexGrid2->activeVoxelCount() << std::endl;
minCorner = srcIndexGrid2->indexBBox().min(), maxCorner = srcIndexGrid2->indexBBox().max();
std::cout << "Index-space bounding box : [" << minCorner.x() << "," << minCorner.y() << "," << minCorner.z()
<< "] -> [" << maxCorner.x() << "," << maxCorner.y() << "," << maxCorner.z() << "]" << std::endl;
std::cout << "Leaf nodes : " << srcIndexGrid2->tree().nodeCount(0) << std::endl;
std::cout << "Lower internal nodes : " << srcIndexGrid2->tree().nodeCount(1) << std::endl;
std::cout << "Upper internal nodes : " << srcIndexGrid2->tree().nodeCount(2) << std::endl;
std::cout << "Leaf-level occupancy : "
<< 100.f * (float)(srcIndexGrid2->activeVoxelCount())/(float)(srcIndexGrid2->tree().nodeCount(0) * 512)
<< "%" << std::endl;
std::cout << "Memory usage : " << srcIndexGrid2->gridSize() << " bytes" << std::endl;
}
// Merge (CPU/OpenVDB version)
cpuTimer.start("Merging openVDBs (on CPU)");
auto mergedGrid = grid1->deepCopy();
openvdb::tools::compSum(*mergedGrid, *grid2);
cpuTimer.stop();
// Convert to indexGrid
cpuTimer.start("Converting merged openVDB output to indexGrid");
auto dstReferenceHandle = nanovdb::tools::openToIndexVDB<BuildT, nanovdb::cuda::DeviceBuffer>(
mergedGrid,
0u, // Don't copy data channel
false, // No stats
false, // No tiles
1 // Verbose mode
);
auto *dstReferenceGrid = dstReferenceHandle.grid<BuildT>();
cpuTimer.stop();
if (printGridDiagnostics) {
std::cout << "============ Merged Grid (CPU) ======= " << std::endl;
std::cout << "Allocated values [valueCount()] : " << dstReferenceGrid->valueCount() << std::endl;
std::cout << "Active voxels [activeVoxelCount()] : " << dstReferenceGrid->activeVoxelCount() << std::endl;
auto minCorner = dstReferenceGrid->indexBBox().min(), maxCorner = dstReferenceGrid->indexBBox().max();
std::cout << "Index-space bounding box : [" << minCorner.x() << "," << minCorner.y() << "," << minCorner.z()
<< "] -> [" << maxCorner.x() << "," << maxCorner.y() << "," << maxCorner.z() << "]" << std::endl;
std::cout << "Leaf nodes : " << dstReferenceGrid->tree().nodeCount(0) << std::endl;
std::cout << "Lower internal nodes : " << dstReferenceGrid->tree().nodeCount(1) << std::endl;
std::cout << "Upper internal nodes : " << dstReferenceGrid->tree().nodeCount(2) << std::endl;
std::cout << "Leaf-level occupancy : "
<< 100.f * (float)(dstReferenceGrid->activeVoxelCount())/(float)(dstReferenceGrid->tree().nodeCount(0) * 512)
<< "%" << std::endl;
std::cout << "Memory usage : " << dstReferenceGrid->gridSize() << " bytes" << std::endl;
}
// Copy both NanoVDB grids to GPU
srcHandle1.deviceUpload();
srcHandle2.deviceUpload();
dstReferenceHandle.deviceUpload();
auto* deviceSrcGrid1 = srcHandle1.deviceGrid<BuildT>();
auto* deviceSrcGrid2 = srcHandle2.deviceGrid<BuildT>();
auto* deviceDstReferenceGrid = dstReferenceHandle.deviceGrid<BuildT>();
if (!deviceSrcGrid1 || !deviceSrcGrid2 || !deviceDstReferenceGrid)
OPENVDB_THROW(openvdb::RuntimeError, "Failure while uploading indexGrids to GPU");
// Launch benchmark
mainMergeGrids(
deviceSrcGrid1, deviceSrcGrid2, deviceDstReferenceGrid,
srcIndexGrid1, srcIndexGrid2, dstReferenceGrid, benchmark_iters);
}
catch (const std::exception& e) {
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
}
return 0;
}
@@ -0,0 +1,59 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
#include <nanovdb/tools/cuda/MergeGrids.cuh>
template<typename T>
bool bufferCheck(const T* deviceBuffer, const T* hostBuffer, size_t elem_count) {
T* tmpBuffer = new T[elem_count];
cudaCheck(cudaMemcpy(tmpBuffer, deviceBuffer, elem_count * sizeof(T), cudaMemcpyDeviceToHost));
bool same = true;
for (int i=0; same && i< elem_count; ++i) { same = (tmpBuffer[i] == hostBuffer[i]); }
delete [] tmpBuffer;
return same;
}
template<typename BuildT>
void mainMergeGrids(
nanovdb::NanoGrid<BuildT> *deviceSrcGrid1,
nanovdb::NanoGrid<BuildT> *deviceSrcGrid2,
nanovdb::NanoGrid<BuildT> *deviceDstReferenceGrid,
nanovdb::NanoGrid<BuildT> *hostSrcGrid1,
nanovdb::NanoGrid<BuildT> *hostSrcGrid2,
nanovdb::NanoGrid<BuildT> *hostDstReferenceGrid,
uint32_t benchmark_iters)
{
nanovdb::util::cuda::Timer gpuTimer;
// Initialize converter
nanovdb::tools::cuda::MergeGrids<BuildT> converter( deviceSrcGrid1, deviceSrcGrid2 );
converter.setChecksum(nanovdb::CheckMode::Default);
converter.setVerbose(1);
auto handle = converter.getHandle();
auto dstGrid = handle.template deviceGrid<BuildT>();
// Check for correctness
if (bufferCheck((char*)dstGrid, (char*)hostDstReferenceGrid->data(), hostDstReferenceGrid->gridSize()))
std::cout << "Result of MergeGrids check out CORRECT against reference" << std::endl;
else
std::cout << "Result of MergeGrids compares INCORRECT against reference" << std::endl;
// Re-run warm-started iterations
converter.setVerbose(0);
for (int i = 0; i < benchmark_iters; i++) {
gpuTimer.start("Re-running entire dilation after warmstart");
auto handle2 = converter.getHandle();
gpuTimer.stop();
}
}
template
void mainMergeGrids(
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *deviceSrcGrid1,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *deviceSrcGrid2,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *deviceDstReferenceGrid,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *hostSrcGrid1,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *hostSrcGrid2,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *hostDstReferenceGrid,
uint32_t benchmark_iters);
@@ -11,12 +11,12 @@
#include <openvdb/math/Ray.h>
#include <openvdb/tools/LevelSetSphere.h>
#include <nanovdb/cuda/DeviceBuffer.h>
#include <nanovdb/tools/NanoToOpenVDB.h>
#include "common.h"
#if defined(NANOVDB_USE_CUDA)
#include <nanovdb/cuda/DeviceBuffer.h>
using BufferT = nanovdb::cuda::DeviceBuffer;
#else
using BufferT = nanovdb::HostBuffer;
@@ -30,10 +30,9 @@ void runOpenVDB(nanovdb::GridHandle<BufferT>& handle, int numIterations, int wid
using Vec3T = openvdb::math::Vec3<RealT>;
using RayT = openvdb::math::Ray<RealT>;
auto srcGrid = nanovdb::tools::nanoToOpenVDB(handle);
openvdb::GridBase::Ptr srcGrid = nanovdb::tools::nanoToOpenVDB(handle);
std::cout << "Exporting to OpenVDB grid[" << srcGrid->getName() << "]...\n";
auto h_grid = (GridT*)srcGrid.get();
GridT::Ptr h_grid = openvdb::GridBase::grid<GridT>(srcGrid);
float* h_outImage = reinterpret_cast<float*>(imageBuffer.data());
@@ -82,7 +81,7 @@ void runOpenVDB(nanovdb::GridHandle<BufferT>& handle, int numIterations, int wid
{
float durationAvg = 0;
for (int i = 0; i < numIterations; ++i) {
float duration = renderImage(false, renderOp, width, height, h_outImage, h_grid);
float duration = renderImage(false, renderOp, width, height, h_outImage, h_grid.get());
//std::cout << "Duration(OpenVDB-Host) = " << duration << " ms" << std::endl;
durationAvg += duration;
}
@@ -11,12 +11,12 @@
#include <openvdb/math/Ray.h>
#include <openvdb/tools/RayIntersector.h>
#include <nanovdb/cuda/DeviceBuffer.h>
#include <nanovdb/tools/NanoToOpenVDB.h>
#include "common.h"
#if defined(NANOVDB_USE_CUDA)
#include <nanovdb/cuda/DeviceBuffer.h>
using BufferT = nanovdb::cuda::DeviceBuffer;
#else
using BufferT = nanovdb::HostBuffer;
@@ -31,7 +31,7 @@ void runOpenVDB(nanovdb::GridHandle<nanovdb::cuda::DeviceBuffer>& handle, int nu
using RayT = openvdb::math::Ray<RealT>;
#if 1
auto srcGrid = nanovdb::tools::nanoToOpenVDB(handle);
openvdb::GridBase::Ptr srcGrid = nanovdb::tools::nanoToOpenVDB(handle);
std::cout << "Exporting to OpenVDB grid[" << srcGrid->getName() << "]...\n";
#else
openvdb::initialize();
@@ -42,7 +42,7 @@ void runOpenVDB(nanovdb::GridHandle<nanovdb::cuda::DeviceBuffer>& handle, int nu
std::cout << "Loading OpenVDB grid[" << srcGrid->getName() << "]...\n";
#endif
auto h_grid = (GridT*)srcGrid.get();
GridT::Ptr h_grid = openvdb::GridBase::grid<GridT>(srcGrid);
float* h_outImage = reinterpret_cast<float*>(imageBuffer.data());
@@ -85,7 +85,7 @@ void runOpenVDB(nanovdb::GridHandle<nanovdb::cuda::DeviceBuffer>& handle, int nu
{
float durationAvg = 0;
for (int i = 0; i < numIterations; ++i) {
float duration = renderImage(false, renderOp, width, height, h_outImage, h_grid);
float duration = renderImage(false, renderOp, width, height, h_outImage, h_grid.get());
//std::cout << "Duration(OpenVDB-Host) = " << duration << " ms" << std::endl;
durationAvg += duration;
}
@@ -0,0 +1,200 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
#ifdef NANOVDB_USE_TBB
#include <tbb/tbb.h>
#endif
// the following files are from OpenVDB
#include <openvdb/tools/Composite.h>
#include <openvdb/util/CpuTimer.h>
// the following files are from NanoVDB
#include <nanovdb/NanoVDB.h>
#include <nanovdb/cuda/DeviceBuffer.h>
#include <nanovdb/tools/CreateNanoGrid.h>
template<typename BuildT>
void mainRefineGrid(
nanovdb::NanoGrid<BuildT> *deviceGridOriginal,
nanovdb::NanoGrid<BuildT> *deviceGridRefined,
nanovdb::NanoGrid<BuildT> *indexGridOriginal,
nanovdb::NanoGrid<BuildT> *indexGridRefined,
uint32_t benchmark_iters
);
#ifdef NANOVDB_USE_TBB
template<typename GridT>
class VoxelRefiner
{
using TreeT = typename GridT::TreeType;
using LeafMgrT = openvdb::tree::LeafManager<const TreeT>;
public:
VoxelRefiner(typename GridT::Ptr srcGrid, tbb::combinable<typename GridT::Ptr>& destGrids) : mSrcGrid(srcGrid), mDestGrids(destGrids) {}
VoxelRefiner(const VoxelRefiner& other, tbb::split) : mSrcGrid(other.mSrcGrid), mDestGrids(other.mDestGrids) {}
void operator()(const typename LeafMgrT::LeafRange& range) const
{
// Refine into a thread-local copy, for safety
typename GridT::Ptr& dstGrid = mDestGrids.local();
if (!dstGrid) { // Create if it doesn't exist
dstGrid = GridT::create(mSrcGrid->background());
dstGrid->setTransform(mSrcGrid->transform().copy());
}
auto unitBox = openvdb::CoordBBox::createCube(openvdb::Coord(), 2);
auto dstAcc = dstGrid->getAccessor();
for (auto srcLeafIter = range.begin(); srcLeafIter; ++srcLeafIter) {
for (auto srcIter = srcLeafIter->cbeginValueOn(); srcIter; ++srcIter) {
const auto& ijk = srcIter.getCoord();
const auto& value = srcIter.getValue();
for (auto boxIter = unitBox.begin(); boxIter != unitBox.end(); ++boxIter)
dstAcc.setValue(ijk+ijk+*boxIter, value);
}
}
}
private:
typename GridT::Ptr mSrcGrid;
tbb::combinable<typename GridT::Ptr>& mDestGrids;
};
#endif
/// @brief This example depends on OpenVDB, NanoVDB, and CUDA
int main(int argc, char *argv[])
{
using GridT = openvdb::FloatGrid;
using BuildT = nanovdb::ValueOnIndex;
openvdb::util::CpuTimer cpuTimer;
const bool printGridDiagnostics = true;
try {
if (argc<2) OPENVDB_THROW(openvdb::ValueError, "usage: "+std::string(argv[0])+" input.vdb [<iterations>]\n");
int benchmark_iters = 10;
if (argc > 2) sscanf(argv[2], "%d", &benchmark_iters);
// Read the initial level set from file
cpuTimer.start("Read input VDB file");
openvdb::initialize();
openvdb::io::File inFile(argv[1]);
inFile.open(false); // disable delayed loading
auto baseGrids = inFile.getGrids();
inFile.close();
auto grid = openvdb::gridPtrCast<GridT>(baseGrids->at(0));
if (!grid) OPENVDB_THROW(openvdb::ValueError, "First grid is not a FloatGrid\n");
cpuTimer.stop();
// Convert to indexGrid (original, un-refined)
cpuTimer.start("Converting openVDB input to indexGrid (original version)");
auto handleOriginal = nanovdb::tools::openToIndexVDB<BuildT, nanovdb::cuda::DeviceBuffer>(
grid,
0u, // Don't copy data channel
false, // No stats
false, // No tiles
1 // Verbose mode
);
auto *indexGridOriginal = handleOriginal.grid<BuildT>();
cpuTimer.stop();
if (printGridDiagnostics) {
std::cout << "============ Original Grid ===========" << std::endl;
std::cout << "Allocated values [valueCount()] : " << indexGridOriginal->valueCount() << std::endl;
std::cout << "Active voxels [activeVoxelCount()] : " << indexGridOriginal->activeVoxelCount() << std::endl;
auto minCorner = indexGridOriginal->indexBBox().min(), maxCorner = indexGridOriginal->indexBBox().max();
std::cout << "Index-space bounding box : [" << minCorner.x() << "," << minCorner.y() << "," << minCorner.z()
<< "] -> [" << maxCorner.x() << "," << maxCorner.y() << "," << maxCorner.z() << "]" << std::endl;
std::cout << "Leaf nodes : " << indexGridOriginal->tree().nodeCount(0) << std::endl;
std::cout << "Lower internal nodes : " << indexGridOriginal->tree().nodeCount(1) << std::endl;
std::cout << "Upper internal nodes : " << indexGridOriginal->tree().nodeCount(2) << std::endl;
std::cout << "Leaf-level occupancy : "
<< 100.f * (float)(indexGridOriginal->activeVoxelCount())/(float)(indexGridOriginal->tree().nodeCount(0) * 512)
<< "%" << std::endl;
std::cout << "Memory usage : " << indexGridOriginal->gridSize() << " bytes" << std::endl;
}
// Refinement (CPU/OpenVDB version)
cpuTimer.start("Refining OpenVDB (on CPU)");
using TreeT = GridT::TreeType;
using LeafManagerT = openvdb::tree::LeafManager<const TreeT>;
#ifdef NANOVDB_USE_TBB
// Phase 1: Multithreaded refinement
tbb::combinable<GridT::Ptr> localGrids;
LeafManagerT leafMgr(grid->tree());
VoxelRefiner<GridT> op(grid, localGrids);
tbb::parallel_for( leafMgr.leafRange(), op);
// Phase 2: Recombine partial grids into final refined result
auto refinedGrid = openvdb::FloatGrid::create(grid->background());
refinedGrid->setTransform(grid->transform().copy());
refinedGrid->setName(grid->getName());
localGrids.combine_each(
[&refinedGrid](const openvdb::FloatGrid::Ptr& localGrid) { if (localGrid) openvdb::tools::compSum(*refinedGrid, *localGrid); } );
#else
// Single-threaded refinement
LeafManagerT leafMgr(grid->tree());
auto refinedGrid = openvdb::FloatGrid::create(grid->background());
refinedGrid->setTransform(grid->transform().copy());
refinedGrid->setName(grid->getName());
auto dstAcc = refinedGrid->getAccessor();
auto unitBox = openvdb::CoordBBox::createCube(openvdb::Coord(), 2);
for (std::size_t leafID = 0; leafID < leafMgr.leafCount(); ++leafID) {
const auto& srcLeaf = leafMgr.leaf(leafID);
for (auto iter = srcLeaf.cbeginValueOn(); iter; ++iter) {
const auto& ijk = iter.getCoord();
const auto& value = iter.getValue();
for (auto boxIter = unitBox.begin(); boxIter != unitBox.end(); ++boxIter)
dstAcc.setValue(ijk+ijk+*boxIter, value);
}
}
#endif
cpuTimer.stop();
// Convert to indexGrid (refined)
cpuTimer.start("Converting openVDB input to indexGrid (refineed version)");
auto handleRefined = nanovdb::tools::openToIndexVDB<BuildT, nanovdb::cuda::DeviceBuffer>(
refinedGrid,
0u, // Don't copy data channel
false, // No stats
false, // No tiles
1 // Verbose mode
);
cpuTimer.stop();
auto *indexGridRefined = handleRefined.grid<BuildT>();
if (printGridDiagnostics) {
std::cout << "============ Refined Grid ============" << std::endl;
std::cout << "Allocated values [valueCount()] : " << indexGridRefined->valueCount() << std::endl;
std::cout << "Active voxels [activeVoxelCount()] : " << indexGridRefined->activeVoxelCount() << std::endl;
auto minCorner = indexGridRefined->indexBBox().min(), maxCorner = indexGridRefined->indexBBox().max();
std::cout << "Index-space bounding box : [" << minCorner.x() << "," << minCorner.y() << "," << minCorner.z()
<< "] -> [" << maxCorner.x() << "," << maxCorner.y() << "," << maxCorner.z() << "]" << std::endl;
std::cout << "Leaf nodes : " << indexGridRefined->tree().nodeCount(0) << std::endl;
std::cout << "Lower internal nodes : " << indexGridRefined->tree().nodeCount(1) << std::endl;
std::cout << "Upper internal nodes : " << indexGridRefined->tree().nodeCount(2) << std::endl;
std::cout << "Leaf-level occupancy : "
<< 100.f * (float)(indexGridRefined->activeVoxelCount())/(float)(indexGridRefined->tree().nodeCount(0) * 512)
<< "%" << std::endl;
std::cout << "Memory usage : " << indexGridRefined->gridSize() << " bytes" << std::endl;
}
// Copy both NanoVDB grids to GPU
handleOriginal.deviceUpload();
handleRefined.deviceUpload();
auto* deviceGridOriginal = handleOriginal.deviceGrid<BuildT>();
auto* deviceGridRefined = handleRefined.deviceGrid<BuildT>();
if (!deviceGridOriginal || !deviceGridRefined)
OPENVDB_THROW(openvdb::RuntimeError, "Failure while uploading indexGrids to GPU");
// Launch benchmark
mainRefineGrid( deviceGridOriginal, deviceGridRefined, indexGridOriginal, indexGridRefined, benchmark_iters );
}
catch (const std::exception& e) {
std::cerr << "An exception occurred: \"" << e.what() << "\"" << std::endl;
}
return 0;
}
@@ -0,0 +1,56 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
#include <nanovdb/tools/cuda/RefineGrid.cuh>
template<typename T>
bool bufferCheck(const T* deviceBuffer, const T* hostBuffer, size_t elem_count) {
T* tmpBuffer = new T[elem_count];
cudaCheck(cudaMemcpy(tmpBuffer, deviceBuffer, elem_count * sizeof(T), cudaMemcpyDeviceToHost));
bool same = true;
for (int i=0; same && i< elem_count; ++i) { same = (tmpBuffer[i] == hostBuffer[i]); }
delete [] tmpBuffer;
return same;
}
template<typename BuildT>
void mainRefineGrid(
nanovdb::NanoGrid<BuildT> *deviceGridOriginal,
nanovdb::NanoGrid<BuildT> *deviceGridRefined,
nanovdb::NanoGrid<BuildT> *indexGridOriginal,
nanovdb::NanoGrid<BuildT> *indexGridRefined,
uint32_t benchmark_iters)
{
nanovdb::util::cuda::Timer gpuTimer;
// Initialize refiner
nanovdb::tools::cuda::RefineGrid<BuildT> refiner( deviceGridOriginal );
refiner.setChecksum(nanovdb::CheckMode::Default);
refiner.setVerbose(1);
auto handle = refiner.getHandle();
auto dstGrid = handle.template deviceGrid<BuildT>();
// Check for correctness
if (bufferCheck((char*)dstGrid, (char*)indexGridRefined->data(), indexGridRefined->gridSize()))
std::cout << "Result of RefineGrid check out CORRECT against reference" << std::endl;
else
std::cout << "Result of RefineGrid compares INCORRECT against reference" << std::endl;
// Re-run warm-started iterations
refiner.setVerbose(0);
for (int i = 0; i < benchmark_iters; i++) {
gpuTimer.start("Re-running entire refinment after warmstart");
auto dummyHandle = refiner.getHandle();
gpuTimer.stop();
}
}
template
void mainRefineGrid(
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *deviceGridOriginal,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *deviceGridRefined,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *indexGridOriginal,
nanovdb::NanoGrid<nanovdb::ValueOnIndex> *indexGridRefined,
uint32_t benchmark_iters
);
@@ -29,7 +29,7 @@ int main()
using OpT = nanovdb::SetVoxel<float>;// defines type of random-access operation (set value)
const nanovdb::Coord &ijk = d_coords[tid];
d_grid->tree().set<OpT>(ijk, d_values[tid]);// normally one should use a ValueAccessor
printf("GPU: voxel # %lu, grid(%4i,%4i,%4i) = %5.1f\n", tid, ijk[0], ijk[1], ijk[2], d_grid->tree().getValue(ijk));
printf("GPU: voxel # %zu, grid(%4i,%4i,%4i) = %5.1f\n", tid, ijk[0], ijk[1], ijk[2], d_grid->tree().getValue(ijk));
}); cudaCheckError();
// Copy grid from GPU to CPU and print the voxel values for validation
@@ -37,7 +37,7 @@ int main()
auto *grid = handle.grid<float>();
for (size_t i=0; i<numVoxels; ++i) {
const nanovdb::Coord &ijk = coords[i];
printf("CPU: voxel # %lu, grid(%4i,%4i,%4i) = %5.1f\n", i, ijk[0], ijk[1], ijk[2], grid->tree().getValue(ijk));
printf("CPU: voxel # %zu, grid(%4i,%4i,%4i) = %5.1f\n", i, ijk[0], ijk[1], ijk[2], grid->tree().getValue(ijk));
}
// free arrays allocated on the device
@@ -49,4 +49,4 @@ int main()
}
return 0;
}
}
@@ -0,0 +1,60 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
#include <nanovdb/tools/cuda/DistributedPointsToGrid.cuh>
namespace
{
template<typename BufferT = nanovdb::HostBuffer>
std::vector<nanovdb::Coord>
initializeCoordListFromCSV(const std::string& filename)
{
std::ifstream file;
file.open(filename);
std::string line;
std::vector<nanovdb::Coord> coords;
while (getline(file, line)) {
std::istringstream stream(line);
nanovdb::Coord coord;
stream >> coord.x(); stream.ignore(1, ',' );
stream >> coord.y(); stream.ignore(1, ',' );
stream >> coord.z();
coords.emplace_back(coord);
}
file.close();
return coords;
}
}
/// @brief Demonstrates how to create a NanoVDB grid from voxel coordinates on the GPU
void testVoxelsToGrid(const std::string& filename)
{
nanovdb::cuda::DeviceMesh deviceMesh;
std::cout << "CSV index source : " << filename << std::endl;
std::cout << "==================================================" << std::endl;
std::vector<nanovdb::Coord> hostCoords = initializeCoordListFromCSV(filename);
std::cout << "Number of coords : " << hostCoords.size() << std::endl;
size_t coordCount = hostCoords.size();
nanovdb::Coord* coords = nullptr;
cudaMallocManaged(&coords, coordCount * sizeof(nanovdb::Coord));
cudaMemcpy(coords, hostCoords.data(), coordCount * sizeof(nanovdb::Coord), cudaMemcpyHostToDevice);
nanovdb::tools::cuda::DistributedPointsToGrid<nanovdb::ValueOnIndex> distributedConverter(deviceMesh);
auto distributedHandle = distributedConverter.getHandle(coords, coordCount);
cudaFree(coords);
}
int main(int argc, char** argv)
{
if (argc != 2)
throw std::runtime_error("Expected CSV filename as command-line argument");
std::string filename(argv[1]);
testVoxelsToGrid(filename);
return 0;
}
+7 -6
View File
@@ -14,7 +14,7 @@
\note This file does NOT depend on OpenVDB, but optionally on ZIP and BLOSC
\details NanoVDB files take on one of two following formats:
\details NanoVDB files take on one of the two following formats:
1) multiple segments each with multiple grids (segments have easy to access metadata about its grids)
2) starting with verion 32.6.0 nanovdb files also support a raw buffer with one or more grids (just a
dump of a raw grid buffer, so no new metadata in headers as when using segments mentioned above).
@@ -223,7 +223,7 @@ fileSize_t Internal::write(std::ostream& os, const GridHandle<BufferT>& handle,
uLongf size = compressBound(static_cast<uLongf>(residual)); // Get an upper bound on the size of the compressed data.
std::unique_ptr<Bytef[]> tmp(new Bytef[size]);
const int status = compress(tmp.get(), &size, reinterpret_cast<const Bytef*>(data), static_cast<uLongf>(residual));
if (status != Z_OK) std::runtime_error("Internal write error in ZIP");
if (status != Z_OK) throw std::runtime_error("Internal write error in ZIP");
if (size > residual) std::cerr << "\nWarning: Unexpected ZIP compression from " << residual << " to " << size << " bytes\n";
const fileSize_t outBytes = size;
os.write(reinterpret_cast<const char*>(&outBytes), sizeof(fileSize_t));
@@ -240,7 +240,7 @@ fileSize_t Internal::write(std::ostream& os, const GridHandle<BufferT>& handle,
fileSize_t chunk = residual < MAX_SIZE ? residual : MAX_SIZE, size = chunk + BLOSC_MAX_OVERHEAD;
std::unique_ptr<char[]> tmp(new char[size]);
const int count = blosc_compress_ctx(9, 1, sizeof(float), chunk, data, tmp.get(), size, BLOSC_LZ4_COMPNAME, 1 << 18, 1);
if (count <= 0) std::runtime_error("Internal write error in BLOSC");
if (count <= 0) throw std::runtime_error("Internal write error in BLOSC");
const fileSize_t outBytes = count;
os.write(reinterpret_cast<const char*>(&outBytes), sizeof(fileSize_t));
os.write(reinterpret_cast<const char*>(tmp.get()), outBytes);
@@ -284,7 +284,7 @@ void Internal::read(std::istream& is, char* data, fileSize_t residual, Codec cod
is.read(reinterpret_cast<char*>(tmp.get()), size);
uLongf numBytes = static_cast<uLongf>(residual);
int status = uncompress(reinterpret_cast<Bytef*>(data), &numBytes, tmp.get(), static_cast<uLongf>(size));
if (status != Z_OK) std::runtime_error("Internal read error in ZIP");
if (status != Z_OK) throw std::runtime_error("Internal read error in ZIP");
if (fileSize_t(numBytes) != residual) throw std::runtime_error("UNZIP failed on byte size");
#else
throw std::runtime_error("ZIP compression codec was disabled during build");
@@ -300,7 +300,7 @@ void Internal::read(std::istream& is, char* data, fileSize_t residual, Codec cod
is.read(reinterpret_cast<char*>(tmp.get()), size);
const fileSize_t chunk = residual < MAX_SIZE ? residual : MAX_SIZE;
const int count = blosc_decompress_ctx(tmp.get(), data, size_t(chunk), 1); //fails with more threads :(
if (count < 1) std::runtime_error("Internal read error in BLOSC");
if (count < 1) throw std::runtime_error("Internal read error in BLOSC");
if (count != int(chunk)) throw std::runtime_error("BLOSC failed on byte size");
data += size_t(chunk);
residual -= chunk;
@@ -332,10 +332,11 @@ inline FileGridMetaData::FileGridMetaData(uint64_t size, Codec c, const GridData
{0, 0, 0, 1}, // nodeCount[4]
{0, 0, 0}, // tileCount[3]
c, // codec
0, // padding
uint16_t(gridData.mBlindMetadataCount), // number of blind meta data
Version()}// version
, gridName(gridData.gridName())
{
NANOVDB_ASSERT(gridData.mBlindMetadataCount <= uint32_t(1 << 16));// due to uint32_t -> uin16_t conversion
auto &treeData = *reinterpret_cast<const TreeData*>(gridData.treePtr());
nameKey = stringHash(gridName);
voxelCount = treeData.mVoxelCount;
+14
View File
@@ -74,9 +74,23 @@ public:
{
if (mDim == dim)
return false;
// compute valid voxel range
Coord voxelMax = (mVoxel + Coord(mDim - 1)) & (~(dim - 1));
Coord voxelMin = mVoxel & (~(dim - 1));
mDim = dim;
const Vec3T &pos = ray(mT0), &inv = ray.invDir();
mVoxel = RoundDown<CoordT>(pos) & (~(dim - 1));
// clamp mVoxel to valid range
mVoxel[0] = nanovdb::math::Min(mVoxel[0], voxelMax[0]);
mVoxel[1] = nanovdb::math::Min(mVoxel[1], voxelMax[1]);
mVoxel[2] = nanovdb::math::Min(mVoxel[2], voxelMax[2]);
mVoxel[0] = nanovdb::math::Max(mVoxel[0], voxelMin[0]);
mVoxel[1] = nanovdb::math::Max(mVoxel[1], voxelMin[1]);
mVoxel[2] = nanovdb::math::Max(mVoxel[2], voxelMin[2]);
for (int axis = 0; axis < 3; ++axis) {
if (mStep[axis] == 0)
continue;
+800 -1
View File
@@ -17,6 +17,10 @@
#include <nanovdb/util/Util.h>// for __hostdev__ and lots of other utility functions
#if defined(__CUDA_ARCH__)
#include <cuda/std/limits>// for ::cuda::std::numeric_limits
#endif
namespace nanovdb {// =================================================================
namespace math {// =============================================================
@@ -83,7 +87,13 @@ struct Delta<double>
/// Maximum floating-point values
template<typename T>
struct Maximum;
#if defined(__CUDA_ARCH__) || defined(__HIP__)
#if defined(__CUDA_ARCH__)
template<typename T>
struct Maximum
{
__hostdev__ static T value() { return ::cuda::std::numeric_limits<T>::max(); }
};
#elif defined(__HIP__)
template<>
struct Maximum<int>
{
@@ -576,6 +586,774 @@ public:
__hostdev__ inline Coord round() const { return *this; }
}; // Coord class
/// @brief Type alias for Coord so we have a consistent naming convention
using Coord3 = Coord;
template <typename T>
class Vec2;
/// @brief Signed (i, j) 32-bit integer coordinate class, similar to openvdb::math::Coord
class Coord2
{
int32_t mVec[2]; // private member data - three signed index coordinates
public:
using ValueType = int32_t;
using IndexType = uint32_t;
/// @brief Initialize all coordinates to zero.
__hostdev__ Coord2()
: mVec{0, 0}
{
}
/// @brief Initializes all coordinates to the given signed integer.
__hostdev__ explicit Coord2(ValueType n)
: mVec{n, n}
{
}
/// @brief Initializes coordinate to the given signed integers.
__hostdev__ Coord2(ValueType i, ValueType j)
: mVec{i, j}
{
}
__hostdev__ Coord2(ValueType* ptr)
: mVec{ptr[0], ptr[1]}
{
}
__hostdev__ int32_t x() const { return mVec[0]; }
__hostdev__ int32_t y() const { return mVec[1]; }
__hostdev__ int32_t& x() { return mVec[0]; }
__hostdev__ int32_t& y() { return mVec[1]; }
__hostdev__ static Coord2 max() { return Coord2(int32_t((1u << 31) - 1)); }
__hostdev__ static Coord2 min() { return Coord2(-int32_t((1u << 31) - 1) - 1); }
__hostdev__ static size_t memUsage() { return sizeof(Coord2); }
/// @brief Return a const reference to the given Coord component.
/// @warning The argument is assumed to be 0 or 1,
__hostdev__ const ValueType& operator[](IndexType i) const { return mVec[i]; }
/// @brief Return a non-const reference to the given Coord component.
/// @warning The argument is assumed to be 0 or 1.
__hostdev__ ValueType& operator[](IndexType i) { return mVec[i]; }
/// @brief Assignment operator that works with openvdb::Coord
template<typename CoordT>
__hostdev__ Coord2& operator=(const CoordT& other)
{
static_assert(sizeof(Coord2) == sizeof(CoordT), "Mis-matched sizeof");
mVec[0] = other[0];
mVec[1] = other[1];
return *this;
}
/// @brief Return a new instance with coordinates masked by the given unsigned integer.
__hostdev__ Coord2 operator&(IndexType n) const { return Coord2(mVec[0] & n, mVec[1] & n); }
// @brief Return a new instance with coordinates left-shifted by the given unsigned integer.
__hostdev__ Coord2 operator<<(IndexType n) const { return Coord2(mVec[0] << n, mVec[1] << n); }
// @brief Return a new instance with coordinates right-shifted by the given unsigned integer.
__hostdev__ Coord2 operator>>(IndexType n) const { return Coord2(mVec[0] >> n, mVec[1] >> n); }
/// @brief Return true if this Coord is lexicographically less than the given Coord.
__hostdev__ bool operator<(const Coord2& rhs) const
{
return mVec[0] < rhs[0] ? true
: mVec[0] > rhs[0] ? false
: mVec[1] < rhs[1] ? true : false;
}
/// @brief Return true if this Coord is lexicographically less or equal to the given Coord.
__hostdev__ bool operator<=(const Coord2& rhs) const
{
return mVec[0] < rhs[0] ? true
: mVec[0] > rhs[0] ? false
: mVec[1] <= rhs[1] ? true : false;
}
// @brief Return true if this Coord is lexicographically greater than the given Coord.
__hostdev__ bool operator>(const Coord2& rhs) const
{
return mVec[0] > rhs[0] ? true
: mVec[0] < rhs[0] ? false
: mVec[1] > rhs[1] ? true : false;
}
// @brief Return true if this Coord is lexicographically greater or equal to the given Coord.
__hostdev__ bool operator>=(const Coord2& rhs) const
{
return mVec[0] > rhs[0] ? true
: mVec[0] < rhs[0] ? false
: mVec[1] >= rhs[1] ? true : false;
}
// @brief Return true if the Coord components are identical.
__hostdev__ bool operator==(const Coord2& rhs) const { return mVec[0] == rhs[0] && mVec[1] == rhs[1]; }
__hostdev__ bool operator!=(const Coord2& rhs) const { return mVec[0] != rhs[0] || mVec[1] != rhs[1]; }
__hostdev__ Coord2& operator&=(int n)
{
mVec[0] &= n;
mVec[1] &= n;
return *this;
}
__hostdev__ Coord2& operator<<=(uint32_t n)
{
mVec[0] <<= n;
mVec[1] <<= n;
return *this;
}
__hostdev__ Coord2& operator>>=(uint32_t n)
{
mVec[0] >>= n;
mVec[1] >>= n;
return *this;
}
__hostdev__ Coord2& operator+=(int n)
{
mVec[0] += n;
mVec[1] += n;
return *this;
}
__hostdev__ Coord2 operator+(const Coord2& rhs) const { return Coord2(mVec[0] + rhs[0], mVec[1] + rhs[1]); }
__hostdev__ Coord2 operator-(const Coord2& rhs) const { return Coord2(mVec[0] - rhs[0], mVec[1] - rhs[1]); }
__hostdev__ Coord2 operator-() const { return Coord2(-mVec[0], -mVec[1]); }
__hostdev__ Coord2& operator+=(const Coord2& rhs)
{
mVec[0] += rhs[0];
mVec[1] += rhs[1];
return *this;
}
__hostdev__ Coord2& operator-=(const Coord2& rhs)
{
mVec[0] -= rhs[0];
mVec[1] -= rhs[1];
return *this;
}
/// @brief Perform a component-wise minimum with the other Coord.
__hostdev__ Coord2& minComponent(const Coord2& other)
{
if (other[0] < mVec[0])
mVec[0] = other[0];
if (other[1] < mVec[1])
mVec[1] = other[1];
return *this;
}
/// @brief Perform a component-wise maximum with the other Coord.
__hostdev__ Coord2& maxComponent(const Coord2& other)
{
if (other[0] > mVec[0])
mVec[0] = other[0];
if (other[1] > mVec[1])
mVec[1] = other[1];
return *this;
}
#if defined(__CUDACC__) // the following functions only run on the GPU!
__device__ inline Coord2& minComponentAtomic(const Coord2& other)
{
atomicMin(&mVec[0], other[0]);
atomicMin(&mVec[1], other[1]);
return *this;
}
__device__ inline Coord2& maxComponentAtomic(const Coord2& other)
{
atomicMax(&mVec[0], other[0]);
atomicMax(&mVec[1], other[1]);
return *this;
}
#endif
__hostdev__ Coord2 offsetBy(ValueType dx, ValueType dy) const
{
return Coord2(mVec[0] + dx, mVec[1] + dy);
}
__hostdev__ Coord2 offsetBy(ValueType n) const { return this->offsetBy(n, n); }
/// Return true if any of the components of @a a are smaller than the
/// corresponding components of @a b.
__hostdev__ static inline bool lessThan(const Coord2& a, const Coord2& b)
{
return (a[0] < b[0] || a[1] < b[1]);
}
/// @brief Return the largest integer coordinates that are not greater
/// than @a xyz (node centered conversion).
template<typename Vec2T>
__hostdev__ static Coord2 Floor(const Vec2T& xy) { return Coord2(math::Floor(xy[0]), math::Floor(xy[1])); }
/// @brief Return a single precision floating-point vector of this coordinate
__hostdev__ inline Vec2<float> asVec2s() const;
/// @brief Return a double precision floating-point vector of this coordinate
__hostdev__ inline Vec2<double> asVec2d() const;
// returns a copy of itself, so it mimics the behaviour of Vec3<T>::round()
__hostdev__ inline Coord2 round() const { return *this; }
}; // Coord2 class
// ----------------------------> Vec2 <--------------------------------------
/// @brief A simple vector class with three components, similar to openvdb::math::Vec3
template<typename T>
class Vec2
{
T mVec[2];
public:
static const int SIZE = 2;
static const int size = 2; // in openvdb::math::Tuple
using ValueType = T;
Vec2() = default;
__hostdev__ explicit Vec2(T x)
: mVec{x, x}
{
}
__hostdev__ Vec2(T x, T y)
: mVec{x, y}
{
}
template<template<class> class Vec2T, class T2>
__hostdev__ Vec2(const Vec2T<T2>& v)
: mVec{T(v[0]), T(v[1])}
{
static_assert(Vec2T<T2>::size == size, "expected Vec2T::size==2!");
}
template<typename T2>
__hostdev__ explicit Vec2(const Vec2<T2>& v)
: mVec{T(v[0]), T(v[1])}
{
}
__hostdev__ explicit Vec2(const Coord2& ijk)
: mVec{T(ijk[0]), T(ijk[1])}
{
}
__hostdev__ bool operator==(const Vec2& rhs) const { return mVec[0] == rhs[0] && mVec[1] == rhs[1]; }
__hostdev__ bool operator!=(const Vec2& rhs) const { return mVec[0] != rhs[0] || mVec[1] != rhs[1]; }
template<template<class> class Vec2T, class T2>
__hostdev__ Vec2& operator=(const Vec2T<T2>& rhs)
{
static_assert(Vec2T<T2>::size == size, "expected Vec2T::size==2!");
mVec[0] = rhs[0];
mVec[1] = rhs[1];
return *this;
}
__hostdev__ const T& operator[](int i) const { return mVec[i]; }
__hostdev__ T& operator[](int i) { return mVec[i]; }
template<typename Vec2T>
__hostdev__ T dot(const Vec2T& v) const { return mVec[0] * v[0] + mVec[1] * v[1]; }
__hostdev__ T lengthSqr() const
{
return mVec[0] * mVec[0] + mVec[1] * mVec[1]; // 3 flops
}
__hostdev__ T length() const { return Sqrt(this->lengthSqr()); }
__hostdev__ Vec2 operator-() const { return Vec2(-mVec[0], -mVec[1]); }
__hostdev__ Vec2 operator*(const Vec2& v) const { return Vec2(mVec[0] * v[0], mVec[1] * v[1]); }
__hostdev__ Vec2 operator/(const Vec2& v) const { return Vec2(mVec[0] / v[0], mVec[1] / v[1]); }
__hostdev__ Vec2 operator+(const Vec2& v) const { return Vec2(mVec[0] + v[0], mVec[1] + v[1]); }
__hostdev__ Vec2 operator-(const Vec2& v) const { return Vec2(mVec[0] - v[0], mVec[1] - v[1]); }
__hostdev__ Vec2 operator+(const Coord& ijk) const { return Vec2(mVec[0] + ijk[0], mVec[1] + ijk[1]); }
__hostdev__ Vec2 operator-(const Coord& ijk) const { return Vec2(mVec[0] - ijk[0], mVec[1] - ijk[1]); }
__hostdev__ Vec2 operator*(const T& s) const { return Vec2(s * mVec[0], s * mVec[1]); }
__hostdev__ Vec2 operator/(const T& s) const { return (T(1) / s) * (*this); }
__hostdev__ Vec2& operator+=(const Vec2& v)
{
mVec[0] += v[0];
mVec[1] += v[1];
return *this;
}
__hostdev__ Vec2& operator+=(const Coord& ijk)
{
mVec[0] += T(ijk[0]);
mVec[1] += T(ijk[1]);
return *this;
}
__hostdev__ Vec2& operator-=(const Vec2& v)
{
mVec[0] -= v[0];
mVec[1] -= v[1];
return *this;
}
__hostdev__ Vec2& operator-=(const Coord& ijk)
{
mVec[0] -= T(ijk[0]);
mVec[1] -= T(ijk[1]);
return *this;
}
__hostdev__ Vec2& operator*=(const T& s)
{
mVec[0] *= s;
mVec[1] *= s;
return *this;
}
__hostdev__ Vec2& operator/=(const T& s) { return (*this) *= T(1) / s; }
__hostdev__ Vec2& normalize() { return (*this) /= this->length(); }
/// @brief Perform a component-wise minimum with the other Coord.
__hostdev__ Vec2& minComponent(const Vec2& other)
{
if (other[0] < mVec[0])
mVec[0] = other[0];
if (other[1] < mVec[1])
mVec[1] = other[1];
return *this;
}
/// @brief Perform a component-wise maximum with the other Coord.
__hostdev__ Vec2& maxComponent(const Vec2& other)
{
if (other[0] > mVec[0])
mVec[0] = other[0];
if (other[1] > mVec[1])
mVec[1] = other[1];
return *this;
}
/// @brief Return the smallest vector component
__hostdev__ ValueType min() const
{
return mVec[0] < mVec[1] ? mVec[0] : mVec[1];
}
/// @brief Return the largest vector component
__hostdev__ ValueType max() const
{
return mVec[0] > mVec[1] ? mVec[0] : mVec[1];
}
/// @brief Round each component if this Vec<T> up to its integer value
/// @return Return an integer Coord
__hostdev__ Coord2 floor() const { return Coord2(Floor(mVec[0]), Floor(mVec[1])); }
/// @brief Round each component if this Vec<T> down to its integer value
/// @return Return an integer Coord
__hostdev__ Coord2 ceil() const { return Coord2(Ceil(mVec[0]), Ceil(mVec[1])); }
/// @brief Round each component if this Vec<T> to its closest integer value
/// @return Return an integer Coord
__hostdev__ Coord2 round() const
{
if constexpr(util::is_same<T, float>::value) {
return Coord2(Floor(mVec[0] + 0.5f), Floor(mVec[1] + 0.5f));
} else if constexpr(util::is_same<T, int>::value) {
return Coord2(mVec[0], mVec[1]);
} else {
return Coord2(Floor(mVec[0] + 0.5), Floor(mVec[1] + 0.5));
}
}
/// @brief return a non-const raw constant pointer to array of three vector components
__hostdev__ T* asPointer() { return mVec; }
/// @brief return a const raw constant pointer to array of three vector components
__hostdev__ const T* asPointer() const { return mVec; }
}; // Vec2<T>
template<typename T1, typename T2>
__hostdev__ inline Vec2<T2> operator*(T1 scalar, const Vec2<T2>& vec)
{
return Vec2<T2>(scalar * vec[0], scalar * vec[1]);
}
template<typename T1, typename T2>
__hostdev__ inline Vec2<T2> operator/(T1 scalar, const Vec2<T2>& vec)
{
return Vec2<T2>(scalar / vec[0], scalar / vec[1]);
}
/// @brief Return a single precision floating-point vector of this coordinate
__hostdev__ inline Vec2<float> Coord2::asVec2s() const
{
return Vec2<float>(float(mVec[0]), float(mVec[1]));
}
/// @brief Return a double precision floating-point vector of this coordinate
__hostdev__ inline Vec2<double> Coord2::asVec2d() const
{
return Vec2<double>(double(mVec[0]), double(mVec[1]));
}
// Matrix base class
template<typename T, int ROWS, int COLS>
class MatBase {
protected:
T mData[ROWS * COLS]; // 1D array storage
static constexpr int rows() { return ROWS; }
static constexpr int cols() { return COLS; }
static constexpr int size() { return ROWS * COLS; }
public:
MatBase() = default;
template<typename S>
__hostdev__ MatBase(S* array) {
for (int i = 0; i < ROWS * COLS; ++i) {
mData[i] = static_cast<T>(array[i]);
}
}
// 2D array access
__hostdev__ T* operator[](int row) {
return &mData[row * COLS];
}
__hostdev__ const T* operator[](int row) const {
return &mData[row * COLS];
}
};
// Forward declarations
template<typename T> class Mat2;
template<typename T> class Mat2x3;
template<typename T> class Mat3x2;
template<typename T> class Mat3;
template<typename T> class Mat4;
template <typename T>
class Mat2 : public MatBase<T, 2, 2> {
using Base = MatBase<T, 2, 2>;
public:
Mat2() = default;
/// @brief Constructor given individual array elements, the ordering is in row major form:
/** @verbatim
a b
c d
@endverbatim */
__hostdev__ Mat2(T a, T b, T c, T d) {
this->mData[0] = a; this->mData[1] = b;
this->mData[2] = c; this->mData[3] = d;
}
/// @brief Constructor given array of elements, the ordering is in row major form
template<typename Source>
__hostdev__ Mat2(Source* array) : Base(array) {}
__hostdev__ Mat2 operator-() const { return Mat2(-(*this)[0][0], -(*this)[0][1], -(*this)[1][0], -(*this)[1][1]); }
/// @brief Multiply by 2x2 matrix @a m and return the resulting matrix.
__hostdev__ Mat2<T> operator*(const Mat2<T>& m) const {
return Mat2<T>(
(*this)[0][0] * m[0][0] + (*this)[0][1] * m[1][0],
(*this)[0][0] * m[0][1] + (*this)[0][1] * m[1][1],
(*this)[1][0] * m[0][0] + (*this)[1][1] * m[1][0],
(*this)[1][0] * m[0][1] + (*this)[1][1] * m[1][1]
);
}
/// @brief Add each element of the given matrix to the corresponding element of this matrix.
__hostdev__ Mat2<T>& operator+=(const Mat2<T>& m) {
(*this)[0][0] += m[0][0];
(*this)[0][1] += m[0][1];
(*this)[1][0] += m[1][0];
(*this)[1][1] += m[1][1];
return *this;
}
/// @brief returns transpose of this
__hostdev__ Mat2<T> transpose() const {
return Mat2<T>((*this)[0][0], (*this)[1][0], (*this)[0][1], (*this)[1][1]);
}
/// @brief returns inverse of this
__hostdev__ Mat2<T> inverse() const {
T det = (*this)[0][0] * (*this)[1][1] - (*this)[0][1] * (*this)[1][0];
if (isApproxZero(det)) {
return Mat2<T>();
}
T invDet = 1.f / det;
return Mat2<T>((*this)[1][1] * invDet, -(*this)[0][1] * invDet, -(*this)[1][0] * invDet, (*this)[0][0] * invDet);
}
};
template <typename T>
class Mat2x3 : public MatBase<T, 2, 3> {
using Base = MatBase<T, 2, 3>;
public:
Mat2x3() = default;
/// @brief Constructor given individual array elements, the ordering is in row major form:
/** @verbatim
a b c
d e f
@endverbatim */
__hostdev__ Mat2x3(T a, T b, T c, T d, T e, T f) {
this->mData[0] = a; this->mData[1] = b; this->mData[2] = c;
this->mData[3] = d; this->mData[4] = e; this->mData[5] = f;
}
/// @brief Constructor given array of elements, the ordering is in row major form
template<typename Source>
__hostdev__ Mat2x3(Source* array) : Base(array) {}
/// @brief Add two matrices and return the resulting matrix.
__hostdev__ Mat2x3<T> operator+(const Mat2x3<T>& m) const {
return Mat2x3<T>(
(*this)[0][0] + m[0][0], (*this)[0][1] + m[0][1], (*this)[0][2] + m[0][2],
(*this)[1][0] + m[1][0], (*this)[1][1] + m[1][1], (*this)[1][2] + m[1][2]
);
}
/// @brief Add a 2x3 matrix to this matrix.
__hostdev__ Mat2x3<T>& operator+=(const Mat2x3<T>& m) {
(*this)[0][0] += m[0][0]; (*this)[0][1] += m[0][1]; (*this)[0][2] += m[0][2];
(*this)[1][0] += m[1][0]; (*this)[1][1] += m[1][1]; (*this)[1][2] += m[1][2];
return *this;
}
/// @brief returns transpose of this
__hostdev__ Mat3x2<T> transpose() const {
return Mat3x2<T>(
(*this)[0][0], (*this)[1][0], // First row
(*this)[0][1], (*this)[1][1], // Second row
(*this)[0][2], (*this)[1][2] // Third row
);
}
};
template <typename T>
class Mat3x2: public MatBase<T, 3, 2> {
using Base = MatBase<T, 3, 2>;
public:
Mat3x2() = default;
/// @brief Constructor given individual array elements, the ordering is in row major form:
/** @verbatim
a b
c d
e f
@endverbatim */
template<typename Source>
__hostdev__ Mat3x2(Source a, Source b, Source c, Source d, Source e, Source f)
{
this->mData[0] = a; this->mData[1] = b;
this->mData[2] = c; this->mData[3] = d;
this->mData[4] = e; this->mData[5] = f;
}
/// @brief Constructor given array of elements, the ordering is in row major form
template<typename Source>
__hostdev__ Mat3x2(Source *a): Base(a) {}
/// @brief returns transpose of this
__hostdev__ Mat2x3<T> transpose() const {
return Mat2x3<T>((*this)[0][0], (*this)[1][0], (*this)[2][0], // First row
(*this)[0][1], (*this)[1][1], (*this)[2][1]); // Second row
}
};
template <typename T>
class Mat3 : public MatBase<T, 3, 3> {
using Base = MatBase<T, 3, 3>;
public:
Mat3() = default;
/// @brief Constructor given individual array elements, the ordering is in row major form:
/** @verbatim
a b c
d e f
g h i
@endverbatim */
template<typename Source>
__hostdev__ Mat3(Source a, Source b, Source c,
Source d, Source e, Source f,
Source g, Source h, Source i)
{
this->mData[0] = a; this->mData[1] = b; this->mData[2] = c;
this->mData[3] = d; this->mData[4] = e; this->mData[5] = f;
this->mData[6] = g; this->mData[7] = h; this->mData[8] = i;
}
/// @brief Constructor given array of elements, the ordering is in row major form
template<typename Source>
__hostdev__ Mat3(Source *a): Base(a) {}
/// @brief Add two matrices and return the resulting matrix.
__hostdev__ Mat3<T> operator+(const Mat3<T>& m) const {
return Mat3<T>(
(*this)[0][0] + m[0][0], (*this)[0][1] + m[0][1], (*this)[0][2] + m[0][2],
(*this)[1][0] + m[1][0], (*this)[1][1] + m[1][1], (*this)[1][2] + m[1][2],
(*this)[2][0] + m[2][0], (*this)[2][1] + m[2][1], (*this)[2][2] + m[2][2]
);
}
/// @brief Multiply by @a v and return the resulting vector.
__hostdev__ Vec3<T> operator*(const Vec3<T>& v) const {
return Vec3<T>(
(*this)[0][0] * v[0] + (*this)[0][1] * v[1] + (*this)[0][2] * v[2],
(*this)[1][0] * v[0] + (*this)[1][1] * v[1] + (*this)[1][2] * v[2],
(*this)[2][0] * v[0] + (*this)[2][1] * v[1] + (*this)[2][2] * v[2]
);
}
/// @brief Multiply by 3x3 matrix @a m and return the resulting matrix.
__hostdev__ Mat3<T> operator*(const Mat3<T>& m) const {
return Mat3<T>(
(*this)[0][0] * m[0][0] + (*this)[0][1] * m[1][0] + (*this)[0][2] * m[2][0],
(*this)[0][0] * m[0][1] + (*this)[0][1] * m[1][1] + (*this)[0][2] * m[2][1],
(*this)[0][0] * m[0][2] + (*this)[0][1] * m[1][2] + (*this)[0][2] * m[2][2],
(*this)[1][0] * m[0][0] + (*this)[1][1] * m[1][0] + (*this)[1][2] * m[2][0],
(*this)[1][0] * m[0][1] + (*this)[1][1] * m[1][1] + (*this)[1][2] * m[2][1],
(*this)[1][0] * m[0][2] + (*this)[1][1] * m[1][2] + (*this)[1][2] * m[2][2],
(*this)[2][0] * m[0][0] + (*this)[2][1] * m[1][0] + (*this)[2][2] * m[2][0],
(*this)[2][0] * m[0][1] + (*this)[2][1] * m[1][1] + (*this)[2][2] * m[2][1],
(*this)[2][0] * m[0][2] + (*this)[2][1] * m[1][2] + (*this)[2][2] * m[2][2]
);
}
/// @brief Add each element of the given matrix to the corresponding element of this matrix.
__hostdev__ Mat3<T>& operator+=(const Mat3<T>& m) {
(*this)[0][0] += m[0][0]; (*this)[0][1] += m[0][1]; (*this)[0][2] += m[0][2];
(*this)[1][0] += m[1][0]; (*this)[1][1] += m[1][1]; (*this)[1][2] += m[1][2];
(*this)[2][0] += m[2][0]; (*this)[2][1] += m[2][1]; (*this)[2][2] += m[2][2];
return *this;
}
/// @brief returns transpose of this
__hostdev__ Mat3 transpose() const {
return Mat3((*this)[0][0], (*this)[1][0], (*this)[2][0],
(*this)[0][1], (*this)[1][1], (*this)[2][1],
(*this)[0][2], (*this)[1][2], (*this)[2][2]);
}
};
template <typename T>
class Mat4 : public MatBase<T, 4, 4> {
using Base = MatBase<T, 4, 4>;
public:
Mat4() = default;
/// @brief Constructor given individual array elements, the ordering is in row major form:
/** @verbatim
a b c d
e f g h
i j k l
m n o p
@endverbatim */
template<typename Source>
__hostdev__ Mat4(Source a, Source b, Source c, Source d,
Source e, Source f, Source g, Source h,
Source i, Source j, Source k, Source l,
Source m, Source n, Source o, Source p)
{
this->mData[0] = a; this->mData[1] = b; this->mData[2] = c; this->mData[3] = d;
this->mData[4] = e; this->mData[5] = f; this->mData[6] = g; this->mData[7] = h;
this->mData[8] = i; this->mData[9] = j; this->mData[10] = k; this->mData[11] = l;
this->mData[12] = m; this->mData[13] = n; this->mData[14] = o; this->mData[15] = p;
}
/// @brief Constructor given array of elements, the ordering is in row major form
template<typename Source>
__hostdev__ Mat4(Source *a): Base(a) {}
/// @brief returns transpose of this
__hostdev__ Mat4 transpose() const {
return Mat4((*this)[0][0], (*this)[1][0], (*this)[2][0], (*this)[3][0],
(*this)[0][1], (*this)[1][1], (*this)[2][1], (*this)[3][1],
(*this)[0][2], (*this)[1][2], (*this)[2][2], (*this)[3][2],
(*this)[0][3], (*this)[1][3], (*this)[2][3], (*this)[3][3]);
}
};
/// @brief Multiply a scalar by a 2x2 matrix, result is a 2x2 matrix
template<typename T>
__hostdev__ Mat2<T> operator*(const T& s, const Mat2<T>& m) {
return Mat2<T>(m[0][0] * s, m[0][1] * s, m[1][0] * s, m[1][1] * s);
}
/// @brief Multiply a 2x3 matrix by a 3x2 matrix, result is a 2x2 matrix
template<typename T>
__hostdev__ Mat2<T> operator*(const Mat2x3<T>& lhs, const Mat3x2<T>& rhs) {
return Mat2<T>(
// First row
lhs[0][0] * rhs[0][0] + lhs[0][1] * rhs[1][0] + lhs[0][2] * rhs[2][0], // [0][0]
lhs[0][0] * rhs[0][1] + lhs[0][1] * rhs[1][1] + lhs[0][2] * rhs[2][1], // [0][1]
// Second row
lhs[1][0] * rhs[0][0] + lhs[1][1] * rhs[1][0] + lhs[1][2] * rhs[2][0], // [1][0]
lhs[1][0] * rhs[0][1] + lhs[1][1] * rhs[1][1] + lhs[1][2] * rhs[2][1] // [1][1]
);
}
/// @brief Multiply a 3x3 matrix by a 2x3 matrix, result is a 2x3 matrix
template<typename T>
__hostdev__ Mat2x3<T> operator*(const Mat3<T>& lhs, const Mat2x3<T>& rhs) {
return Mat2x3<T>(
// First row
lhs[0][0] * rhs[0][0] + lhs[0][1] * rhs[1][0],
lhs[0][0] * rhs[0][1] + lhs[0][1] * rhs[1][1],
lhs[0][0] * rhs[0][2] + lhs[0][1] * rhs[1][2],
// Second row
lhs[1][0] * rhs[0][0] + lhs[1][1] * rhs[1][0],
lhs[1][0] * rhs[0][1] + lhs[1][1] * rhs[1][1],
lhs[1][0] * rhs[0][2] + lhs[1][1] * rhs[1][2]
);
}
/// @brief Multiply a 2x3 matrix by a 3x3 matrix, result is a 2x3 matrix
template<typename T>
__hostdev__ Mat2x3<T> operator*(const Mat2x3<T>& lhs, const Mat3<T>& rhs) {
return Mat2x3<T>(
// First row (3 elements)
lhs[0][0] * rhs[0][0] + lhs[0][1] * rhs[1][0] + lhs[0][2] * rhs[2][0],
lhs[0][0] * rhs[0][1] + lhs[0][1] * rhs[1][1] + lhs[0][2] * rhs[2][1],
lhs[0][0] * rhs[0][2] + lhs[0][1] * rhs[1][2] + lhs[0][2] * rhs[2][2],
// Second row (3 elements)
lhs[1][0] * rhs[0][0] + lhs[1][1] * rhs[1][0] + lhs[1][2] * rhs[2][0],
lhs[1][0] * rhs[0][1] + lhs[1][1] * rhs[1][1] + lhs[1][2] * rhs[2][1],
lhs[1][0] * rhs[0][2] + lhs[1][1] * rhs[1][2] + lhs[1][2] * rhs[2][2]
);
}
/// @brief Multiply a 3x2 matrix by a 2x2 matrix, result is a 3x2 matrix
template<typename T>
__hostdev__ Mat3x2<T> operator*(const Mat3x2<T>& lhs, const Mat2<T>& rhs) {
return Mat3x2<T>(
lhs[0][0] * rhs[0][0] + lhs[0][1] * rhs[1][0],
lhs[0][0] * rhs[0][1] + lhs[0][1] * rhs[1][1],
lhs[1][0] * rhs[0][0] + lhs[1][1] * rhs[1][0],
lhs[1][0] * rhs[0][1] + lhs[1][1] * rhs[1][1],
lhs[2][0] * rhs[0][0] + lhs[2][1] * rhs[1][0],
lhs[2][0] * rhs[0][1] + lhs[2][1] * rhs[1][1]
);
}
/// @brief Multiply a 2x2 matrix by a 2x3 matrix, result is a 2x3 matrix
template<typename T>
__hostdev__ Mat2x3<T> operator*(const Mat2<T>& lhs, const Mat2x3<T>& rhs) {
return Mat2x3<T>(
// First row (3 elements)
lhs[0][0] * rhs[0][0] + lhs[0][1] * rhs[1][0],
lhs[0][0] * rhs[0][1] + lhs[0][1] * rhs[1][1],
lhs[0][0] * rhs[0][2] + lhs[0][1] * rhs[1][2],
// Second row (3 elements)
lhs[1][0] * rhs[0][0] + lhs[1][1] * rhs[1][0],
lhs[1][0] * rhs[0][1] + lhs[1][1] * rhs[1][1],
lhs[1][0] * rhs[0][2] + lhs[1][1] * rhs[1][2]
);
}
/// @brief Multiply a 3x2 matrix by a 2x3 matrix, result is a 3x3 matrix
template<typename T>
__hostdev__ Mat3<T> operator*(const Mat3x2<T>& lhs, const Mat2x3<T>& rhs) {
return Mat3<T>(
lhs[0][0] * rhs[0][0] + lhs[0][1] * rhs[1][0],
lhs[0][0] * rhs[0][1] + lhs[0][1] * rhs[1][1],
lhs[0][0] * rhs[0][2] + lhs[0][1] * rhs[1][2],
lhs[1][0] * rhs[0][0] + lhs[1][1] * rhs[1][0],
lhs[1][0] * rhs[0][1] + lhs[1][1] * rhs[1][1],
lhs[1][0] * rhs[0][2] + lhs[1][1] * rhs[1][2],
lhs[2][0] * rhs[0][0] + lhs[2][1] * rhs[1][0],
lhs[2][0] * rhs[0][1] + lhs[2][1] * rhs[1][1],
lhs[2][0] * rhs[0][2] + lhs[2][1] * rhs[1][2]
);
}
// ----------------------------> Vec3 <--------------------------------------
/// @brief A simple vector class with three components, similar to openvdb::math::Vec3
@@ -634,6 +1412,14 @@ public:
mVec[2] * v[0] - mVec[0] * v[2],
mVec[0] * v[1] - mVec[1] * v[0]);
}
/// @brief Outer product of a 3x1 vector and a 1x3 vector, result is a 3x3 matrix
template<typename Vec3T>
__hostdev__ Mat3<ValueType> outer(const Vec3T& v) const
{
return Mat3<ValueType>(mVec[0] * v[0], mVec[0] * v[1], mVec[0] * v[2],
mVec[1] * v[0], mVec[1] * v[1], mVec[1] * v[2],
mVec[2] * v[0], mVec[2] * v[1], mVec[2] * v[2]);
}
__hostdev__ T lengthSqr() const
{
return mVec[0] * mVec[0] + mVec[1] * mVec[1] + mVec[2] * mVec[2]; // 5 flops
@@ -892,6 +1678,19 @@ __hostdev__ inline Vec4<T2> operator/(T1 scalar, const Vec4<T2>& vec)
{
return Vec4<T2>(scalar / vec[0], scalar / vec[1], scalar / vec[2], scalar / vec[3]);
}
/// @brief Return the matrix vector product of a 4x4 matrix and a 4d vector
/// @param m 4x4 matrix
/// @param v 4d vector
/// @return result of matrix-vector multiplication, i.e. m x v
template <typename T>
__hostdev__ inline Vec4<T> operator*(const Mat4<T>& m, const Vec4<T>& v) {
return Vec4<T>(
m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2] + m[0][3] * v[3],
m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2] + m[1][3] * v[3],
m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2] + m[2][3] * v[3],
m[3][0] * v[0] + m[3][1] * v[1] + m[3][2] * v[2] + m[3][3] * v[3]
);
}
// ----------------------------> matMult <--------------------------------------
+781
View File
@@ -0,0 +1,781 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
/*!
\file nanovdb/putil/IO.h
\author Andrew Reidmeyer
\brief This file contains IO utilities for PNanoVDB.
*/
#ifndef NANOVDB_PUTILS_IO_H_HAS_BEEN_INCLUDED
#define NANOVDB_PUTILS_IO_H_HAS_BEEN_INCLUDED
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__CUDACC__)
#define PNANOVDB_IO_INLINE static __host__ __device__
#elif defined(_WIN32)
#define PNANOVDB_IO_INLINE static inline
#else
#define PNANOVDB_IO_INLINE static inline
#endif
// needs to match NANOVDB_DATA_ALIGNMENT from nanovdb/NanoVDB.h
#define PNANOVDB_DATA_ALIGNMENT 32
PNANOVDB_IO_INLINE void* pnanovdb_aligned_malloc(size_t alignment, size_t size)
{
size_t aligned_size = size + alignment + sizeof(void*);
void* alloc_ptr = malloc(aligned_size);
void* ptr = 0;
if (alloc_ptr)
{
ptr = (void*)(alignment * (((size_t)alloc_ptr + sizeof(void*) + alignment - 1u) / alignment));
void** header_ptr = (void**)((size_t)ptr - sizeof(void*));
*header_ptr = alloc_ptr;
}
return ptr;
}
PNANOVDB_IO_INLINE void pnanovdb_aligned_free(void* ptr)
{
if (ptr)
{
void** header_ptr = (void**)((size_t)ptr - sizeof(void*));
free(*header_ptr);
}
}
// abstraction for file-like read/write
typedef struct pnanovdb_io_buf_t
{
void* file;
void(*read)(void* file, uint64_t offset, void* dst_data, uint64_t size);
void(*write)(void* file, uint64_t offset, void* src_data, uint64_t size);
}pnanovdb_io_buf_t;
// implementation for fread/fwrite
PNANOVDB_IO_INLINE void pnanovdb_io_buf_fread(void* file, uint64_t offset, void* dst_data, uint64_t size)
{
fseek((FILE*)file, offset, SEEK_SET);
fread(dst_data, size, 1u, (FILE*)file);
}
PNANOVDB_IO_INLINE void pnanovdb_io_buf_fwrite(void* file, uint64_t offset, void* src_data, uint64_t size)
{
fseek((FILE*)file, offset, SEEK_SET);
fwrite(src_data, size, 1u, (FILE*)file);
}
// implementation for dynamic buffer
typedef struct pnanovdb_io_dynamic_buf_t
{
void* data;
uint64_t size;
uint64_t capacity;
}pnanovdb_io_dynamic_buf_t;
PNANOVDB_IO_INLINE void pnanovdb_io_dynamic_buf_read(void* file, uint64_t offset, void* dst_data, uint64_t size)
{
pnanovdb_io_dynamic_buf_t* ptr = (pnanovdb_io_dynamic_buf_t*)file;
uint8_t* dst8 = (uint8_t*)dst_data;
uint8_t* src8 = (uint8_t*)ptr->data;
uint64_t size_in_bounds = size;
if (offset >= ptr->size)
{
size_in_bounds = 0;
}
else if (offset + size > ptr->size)
{
size_in_bounds = ptr->size - offset;
}
memcpy(dst8, src8 + offset, size_in_bounds);
memset(dst8 + size_in_bounds, 0, size - size_in_bounds);
}
PNANOVDB_IO_INLINE void pnanovdb_io_dynamic_buf_write(void* file, uint64_t offset, void* src_data, uint64_t size)
{
pnanovdb_io_dynamic_buf_t* ptr = (pnanovdb_io_dynamic_buf_t*)file;
// grow capacity as needed
if (offset + size > ptr->capacity)
{
uint64_t new_capacity = 1024u;
while (new_capacity < offset + size)
{
new_capacity = (new_capacity << 1u) - (new_capacity >> 1u);
}
void* new_data = pnanovdb_aligned_malloc(PNANOVDB_DATA_ALIGNMENT, new_capacity);
memcpy(new_data, ptr->data, ptr->size);
if (ptr->capacity != 0u)
{
pnanovdb_aligned_free(ptr->data);
}
ptr->data = new_data;
ptr->capacity = new_capacity;
}
// expand size and zero initialize as needed
if (offset + size > ptr->size)
{
uint8_t* data8 = (uint8_t*)ptr->data;
memset(data8 + ptr->size, 0, offset + size - ptr->size);
ptr->size = offset + size;
}
// write data
uint8_t* src8 = (uint8_t*)src_data;
uint8_t* dst8 = (uint8_t*)ptr->data;
memcpy(dst8 + offset, src8, size);
}
#if defined(PNANOVDB_IO_BUF_CUSTOM)
#define PNANOVDB_BUF_CUSTOM
#define pnanovdb_buf_t pnanovdb_io_buf_t*
PNANOVDB_IO_INLINE uint32_t pnanovdb_buf_read_uint32(pnanovdb_buf_t buf, uint64_t byte_offset)
{
uint32_t ret = 0u;
buf->read(buf->file, byte_offset, &ret, 4u);
return ret;
}
PNANOVDB_IO_INLINE uint64_t pnanovdb_buf_read_uint64(pnanovdb_buf_t buf, uint64_t byte_offset)
{
uint64_t ret = 0u;
buf->read(buf->file, byte_offset, &ret, 8u);
return ret;
}
PNANOVDB_IO_INLINE void pnanovdb_buf_write_uint32(pnanovdb_buf_t buf, uint64_t byte_offset, uint32_t value)
{
buf->write(buf->file, byte_offset, &value, 4u);
}
PNANOVDB_IO_INLINE void pnanovdb_buf_write_uint64(pnanovdb_buf_t buf, uint64_t byte_offset, uint64_t value)
{
buf->write(buf->file, byte_offset, &value, 8u);
}
#define pnanovdb_grid_type_t uint32_t
#define PNANOVDB_GRID_TYPE_GET(grid_typeIn, nameIn) pnanovdb_grid_type_constants[grid_typeIn].nameIn
#endif
#ifndef PNANOVDB_C
#define PNANOVDB_C
#endif
#include <nanovdb/PNanoVDB.h>
typedef struct pnanovdb_io_fileheader_t
{
pnanovdb_uint64_t magic; // 8 bytes, 0
pnanovdb_uint32_t version; // 4 bytes, 8
pnanovdb_uint32_t grid_count_and_codec; // 4 bytes, 12
}pnanovdb_io_fileheader_t;
#define PNANOVDB_IO_FILEHEADER_SIZE 16
typedef struct pnanovdb_io_filemetadata_t
{
pnanovdb_uint64_t grid_size; // 8 bytes, 0
pnanovdb_uint64_t file_size; // 8 bytes, 8
pnanovdb_uint64_t name_key; // 8 bytes, 16
pnanovdb_uint64_t voxel_count; // 8 bytes, 24
pnanovdb_uint32_t grid_type; // 4 bytes, 32
pnanovdb_uint32_t grid_class; // 4 bytes, 36
double world_bbox[6u]; // 48 bytes, 40
pnanovdb_coord_t index_bbox_min; // 12 bytes, 88
pnanovdb_coord_t index_bbox_max; // 12 bytes, 100
double voxel_size[3u]; // 24 bytes, 112
pnanovdb_uint32_t name_size; // 4 bytes, 136
pnanovdb_uint32_t node_count_leaf; // 4 bytes, 140
pnanovdb_uint32_t node_count_lower; // 4 bytes, 144
pnanovdb_uint32_t node_count_upper; // 4 bytes, 148
pnanovdb_uint32_t node_count_root; // 4 bytes, 152
pnanovdb_uint32_t tile_count_lower; // 4 bytes, 156
pnanovdb_uint32_t tile_count_upper; // 4 bytes, 160
pnanovdb_uint32_t tile_count_root; // 4 bytes, 164
pnanovdb_uint32_t codec_and_padding; // 4 bytes, 168
pnanovdb_uint32_t version; // 4 bytes, 172
}pnanovdb_io_filemetadata_t;
#define PNANOVDB_IO_FILEMETADATA_SIZE 176
#if defined(PNANOVDB_IO_BUF_CUSTOM)
PNANOVDB_IO_INLINE pnanovdb_grid_handle_t pnanovdb_io_fread_fileheader(
pnanovdb_buf_t file,
pnanovdb_io_fileheader_t* dst_header,
pnanovdb_io_filemetadata_t* dst_metadatas,
pnanovdb_uint64_t dst_metadata_count)
{
pnanovdb_grid_t local_grid = {};
file->read(file->file, 0u, &local_grid, PNANOVDB_GRID_SIZE);
pnanovdb_bool_t is_raw_grid = PNANOVDB_FALSE;
if (local_grid.magic == PNANOVDB_MAGIC_GRID ||
local_grid.data2 == PNANOVDB_MAGIC_GRID)
{
is_raw_grid = PNANOVDB_TRUE;
}
if (is_raw_grid)
{
pnanovdb_grid_handle_t raw_grid = {pnanovdb_address_null()};
return raw_grid;
}
pnanovdb_uint64_t offset = 0u;
pnanovdb_io_fileheader_t local_header = {};
file->read(file->file, offset, &local_header, PNANOVDB_IO_FILEHEADER_SIZE);
if (dst_header)
{
*dst_header = local_header;
}
offset += PNANOVDB_IO_FILEHEADER_SIZE;
pnanovdb_uint32_t grid_count = local_header.grid_count_and_codec & 0xFFFF;
for (uint32_t idx = 0u; idx < grid_count; idx++)
{
pnanovdb_io_filemetadata_t local_metadata = {};
file->read(file->file, offset, &local_metadata, PNANOVDB_IO_FILEMETADATA_SIZE);
if (idx < dst_metadata_count)
{
dst_metadatas[idx] = local_metadata;
}
offset += PNANOVDB_IO_FILEMETADATA_SIZE;
// skip past name
offset += local_metadata.name_size;
}
pnanovdb_grid_handle_t first_grid = {pnanovdb_address_offset64(pnanovdb_address_null(), offset)};
return first_grid;
}
PNANOVDB_IO_INLINE void pnanovdb_io_memcpy(
pnanovdb_buf_t dst_buf, pnanovdb_address_t dst_addr,
pnanovdb_buf_t src_buf, pnanovdb_address_t src_addr,
pnanovdb_uint64_t num_bytes)
{
pnanovdb_uint64_t num_words = (num_bytes >> 2u);
for (pnanovdb_uint64_t word_idx = 0u; word_idx < num_words; word_idx++)
{
pnanovdb_uint32_t val = pnanovdb_read_uint32(src_buf, pnanovdb_address_offset_product(src_addr, word_idx, 4u));
pnanovdb_write_uint32(dst_buf, pnanovdb_address_offset_product(dst_addr, word_idx, 4u), val);
}
}
PNANOVDB_IO_INLINE pnanovdb_uint32_t pnanovdb_io_get_leaf_size(pnanovdb_uint32_t grid_type, pnanovdb_buf_t buf, pnanovdb_leaf_handle_t leaf)
{
pnanovdb_uint32_t size = PNANOVDB_GRID_TYPE_GET(grid_type, leaf_size);
if (grid_type == PNANOVDB_GRID_TYPE_FPN)
{
pnanovdb_uint32_t flags = pnanovdb_leaf_get_bbox_dif_and_flags(buf, leaf) >> 24u;
pnanovdb_uint32_t value_log_bits = flags >> 5;
pnanovdb_uint32_t value_bits = 1u << value_log_bits;
pnanovdb_uint32_t table_bytes = value_bits << (9u - 3u);
size += table_bytes;
}
return size;
}
PNANOVDB_IO_INLINE void pnanovdb_io_copy_subregion(
pnanovdb_buf_t src_buf,
pnanovdb_grid_handle_t src_grid,
pnanovdb_buf_t dst_buf,
pnanovdb_grid_handle_t dst_grid,
pnanovdb_coord_t ijk_min,
pnanovdb_coord_t ijk_max)
{
// leaf precise, not voxel precise for now
ijk_min.x &= ~7;
ijk_min.y &= ~7;
ijk_min.z &= ~7;
ijk_max.x |= 7;
ijk_max.y |= 7;
ijk_max.z |= 7;
pnanovdb_buf_t buf = src_buf;
pnanovdb_grid_handle_t grid = src_grid;
// copy grid
pnanovdb_io_memcpy(dst_buf, dst_grid.address, buf, grid.address, PNANOVDB_GRID_SIZE);
pnanovdb_grid_type_t grid_type = pnanovdb_grid_get_grid_type(buf, grid);
// copy tree
pnanovdb_tree_handle_t tree = pnanovdb_grid_get_tree(buf, grid);
pnanovdb_tree_handle_t dst_tree = pnanovdb_grid_get_tree(dst_buf, dst_grid);
pnanovdb_io_memcpy(dst_buf, dst_tree.address, buf, tree.address, PNANOVDB_TREE_SIZE);
// copy root
pnanovdb_root_handle_t root = pnanovdb_tree_get_root(buf, tree);
pnanovdb_root_handle_t dst_root = pnanovdb_tree_get_root(dst_buf, dst_tree);
pnanovdb_io_memcpy(dst_buf, dst_root.address, buf, root.address, PNANOVDB_GRID_TYPE_GET(grid_type, root_size));
// set new root to clipped bounds
pnanovdb_root_set_bbox_min(dst_buf, dst_root, &ijk_min);
pnanovdb_root_set_bbox_max(dst_buf, dst_root, &ijk_max);
// copy root tiles
pnanovdb_uint32_t tile_count = pnanovdb_root_get_tile_count(buf, root);
for (pnanovdb_uint32_t tile_idx = 0u; tile_idx < tile_count; tile_idx++)
{
pnanovdb_root_tile_handle_t root_tile = pnanovdb_root_get_tile(grid_type, root, tile_idx);
pnanovdb_root_tile_handle_t dst_root_tile = pnanovdb_root_get_tile(grid_type, dst_root, tile_idx);
pnanovdb_io_memcpy(dst_buf, dst_root_tile.address, buf, root_tile.address, PNANOVDB_GRID_TYPE_GET(grid_type, root_tile_size));
}
// copy upper
for (pnanovdb_uint32_t tile_idx = 0u; tile_idx < tile_count; tile_idx++)
{
pnanovdb_root_tile_handle_t root_tile = pnanovdb_root_get_tile(grid_type, root, tile_idx);
pnanovdb_root_tile_handle_t dst_root_tile = pnanovdb_root_get_tile(grid_type, dst_root, tile_idx);
if (pnanovdb_root_tile_get_child_mask(buf, root_tile))
{
pnanovdb_upper_handle_t upper = pnanovdb_root_get_child(grid_type, buf, root, root_tile);
pnanovdb_upper_handle_t dst_upper = pnanovdb_root_get_child(grid_type, dst_buf, dst_root, dst_root_tile);
pnanovdb_io_memcpy(dst_buf, dst_upper.address, buf, upper.address, PNANOVDB_GRID_TYPE_GET(grid_type, upper_size));
}
}
// copy lower
for (pnanovdb_uint32_t tile_idx = 0u; tile_idx < tile_count; tile_idx++)
{
pnanovdb_root_tile_handle_t root_tile = pnanovdb_root_get_tile(grid_type, root, tile_idx);
pnanovdb_root_tile_handle_t dst_root_tile = pnanovdb_root_get_tile(grid_type, dst_root, tile_idx);
if (pnanovdb_root_tile_get_child_mask(buf, root_tile))
{
pnanovdb_upper_handle_t upper = pnanovdb_root_get_child(grid_type, buf, root, root_tile);
pnanovdb_upper_handle_t dst_upper = pnanovdb_root_get_child(grid_type, dst_buf, dst_root, dst_root_tile);
for (pnanovdb_uint32_t upper_n = 0u; upper_n < PNANOVDB_UPPER_TABLE_COUNT; upper_n++)
{
if (pnanovdb_upper_get_child_mask(buf, upper, upper_n))
{
pnanovdb_lower_handle_t lower = pnanovdb_upper_get_child(grid_type, buf, upper, upper_n);
pnanovdb_lower_handle_t dst_lower = pnanovdb_upper_get_child(grid_type, dst_buf, dst_upper, upper_n);
pnanovdb_io_memcpy(dst_buf, dst_lower.address, buf, lower.address, PNANOVDB_GRID_TYPE_GET(grid_type, lower_size));
}
}
}
}
// copy leaf
pnanovdb_uint32_t dst_node_count_leaf = 0u;
pnanovdb_leaf_handle_t dst_current_leaf = {
pnanovdb_address_offset(dst_tree.address, pnanovdb_tree_get_node_offset_leaf(dst_buf, dst_tree))};
for (pnanovdb_uint32_t tile_idx = 0u; tile_idx < tile_count; tile_idx++)
{
pnanovdb_root_tile_handle_t root_tile = pnanovdb_root_get_tile(grid_type, root, tile_idx);
pnanovdb_root_tile_handle_t dst_root_tile = pnanovdb_root_get_tile(grid_type, dst_root, tile_idx);
if (pnanovdb_root_tile_get_child_mask(buf, root_tile))
{
pnanovdb_upper_handle_t upper = pnanovdb_root_get_child(grid_type, buf, root, root_tile);
pnanovdb_upper_handle_t dst_upper = pnanovdb_root_get_child(grid_type, dst_buf, dst_root, dst_root_tile);
for (pnanovdb_uint32_t upper_n = 0u; upper_n < PNANOVDB_UPPER_TABLE_COUNT; upper_n++)
{
if (pnanovdb_upper_get_child_mask(buf, upper, upper_n))
{
pnanovdb_lower_handle_t lower = pnanovdb_upper_get_child(grid_type, buf, upper, upper_n);
pnanovdb_lower_handle_t dst_lower = pnanovdb_upper_get_child(grid_type, dst_buf, dst_upper, upper_n);
for (pnanovdb_uint32_t lower_n = 0u; lower_n < PNANOVDB_LOWER_TABLE_COUNT; lower_n++)
{
if (pnanovdb_lower_get_child_mask(buf, lower, lower_n))
{
pnanovdb_leaf_handle_t leaf = pnanovdb_lower_get_child(grid_type, buf, lower, lower_n);
pnanovdb_coord_t leaf_ijk_min = pnanovdb_leaf_get_bbox_min(buf, leaf);
pnanovdb_uint32_t dif_and_flags = pnanovdb_leaf_get_bbox_dif_and_flags(buf, leaf);
pnanovdb_coord_t leaf_ijk_max = {
pnanovdb_uint32_as_int32(dif_and_flags & 255u) + leaf_ijk_min.x,
pnanovdb_uint32_as_int32((dif_and_flags >> 8u) & 255u) + leaf_ijk_min.y,
pnanovdb_uint32_as_int32((dif_and_flags >> 16u) & 255u) + leaf_ijk_min.z
};
// leaf precise, not voxel precise for now
leaf_ijk_min.x &= ~7;
leaf_ijk_min.y &= ~7;
leaf_ijk_min.z &= ~7;
leaf_ijk_max.x |= 7;
leaf_ijk_max.y |= 7;
leaf_ijk_max.z |= 7;
pnanovdb_bool_t pred = !(
leaf_ijk_max.x < ijk_min.x || leaf_ijk_min.x > ijk_max.x ||
leaf_ijk_max.y < ijk_min.y || leaf_ijk_min.y > ijk_max.y ||
leaf_ijk_max.z < ijk_min.z || leaf_ijk_min.z > ijk_max.z );
if (pred)
{
pnanovdb_uint32_t leaf_size = pnanovdb_io_get_leaf_size(grid_type, buf, leaf);
pnanovdb_io_memcpy(dst_buf, dst_current_leaf.address, buf, leaf.address, leaf_size);
pnanovdb_lower_set_child(grid_type, dst_buf, dst_lower, lower_n, dst_current_leaf);
// voxel precise
#if 0
for (pnanovdb_uint32_t leaf_n = 0u; leaf_n < PNANOVDB_LEAF_TABLE_COUNT; leaf_n++)
{
pnanovdb_coord_t voxel_ijk = {
(leaf_ijk_min.x & ~7) + (pnanovdb_uint32_as_int32(leaf_n >> 6u) & 7),
(leaf_ijk_min.y & ~7) + (pnanovdb_uint32_as_int32(leaf_n >> 3u) & 7),
(leaf_ijk_min.z & ~7) + (pnanovdb_uint32_as_int32(leaf_n >> 0u) & 7)
};
pnanovdb_bool_t voxel_pred = !(
voxel_ijk.x < ijk_min.x || voxel_ijk.x > ijk_max.x ||
voxel_ijk.y < ijk_min.y || voxel_ijk.y > ijk_max.y ||
voxel_ijk.z < ijk_min.z || voxel_ijk.z > ijk_max.z );
if (!voxel_pred)
{
pnanovdb_leaf_set_value_mask(dst_buf, dst_current_leaf, leaf_n, PNANOVDB_FALSE);
pnanovdb_address_t val_addr =
pnanovdb_leaf_get_table_address(grid_type, dst_buf, dst_current_leaf, leaf_n);
pnanovdb_address_t background_addr = pnanovdb_root_get_background_address(grid_type, buf, root);
pnanovdb_io_memcpy(dst_buf, val_addr, buf, background_addr, (PNANOVDB_GRID_TYPE_GET(grid_type, value_stride_bits) >> 3u));
}
}
#endif
dst_current_leaf.address = pnanovdb_address_offset(dst_current_leaf.address, leaf_size);
dst_node_count_leaf++;
}
else
{
pnanovdb_address_t background_addr = pnanovdb_root_get_background_address(grid_type, buf, root);
pnanovdb_uint64_t background_index = pnanovdb_read_uint64(buf, background_addr);
pnanovdb_lower_set_table_child(grid_type, dst_buf, dst_lower, lower_n, pnanovdb_uint64_as_int64(background_index));
pnanovdb_lower_set_child_mask(dst_buf, dst_lower, lower_n, PNANOVDB_FALSE);
pnanovdb_lower_set_value_mask(dst_buf, dst_lower, lower_n, PNANOVDB_FALSE);
}
}
}
}
}
}
}
//pnanovdb_grid_set_blind_metadata_count(dst_buf, dst_grid, 0u);
// need to update tree for new reduced leaf range
pnanovdb_tree_set_node_count_leaf(dst_buf, dst_tree, dst_node_count_leaf);
pnanovdb_uint32_t blindmetadata_count = pnanovdb_grid_get_blind_metadata_count(buf, grid);
pnanovdb_uint64_t dst_blind_metadata_offset =
dst_current_leaf.address.byte_offset -
dst_grid.address.byte_offset;
pnanovdb_grid_set_blind_metadata_offset(dst_buf, dst_grid, dst_blind_metadata_offset);
// copy blindmetadata headers
for (pnanovdb_uint32_t metadata_idx = 0u; metadata_idx < blindmetadata_count; metadata_idx++)
{
pnanovdb_gridblindmetadata_handle_t metadata = pnanovdb_grid_get_gridblindmetadata(buf, grid, metadata_idx);
pnanovdb_gridblindmetadata_handle_t dst_metadata = pnanovdb_grid_get_gridblindmetadata(dst_buf, dst_grid, metadata_idx);
pnanovdb_io_memcpy(dst_buf, dst_metadata.address, buf, metadata.address, PNANOVDB_GRIDBLINDMETADATA_SIZE);
}
#if 0
// copy blindmetadata
for (pnanovdb_uint32_t metadata_idx = 0u; metadata_idx < blindmetadata_count; metadata_idx++)
{
pnanovdb_gridblindmetadata_handle_t metadata = pnanovdb_grid_get_gridblindmetadata(buf, grid, metadata_idx);
pnanovdb_gridblindmetadata_handle_t dst_metadata = pnanovdb_grid_get_gridblindmetadata(dst_buf, dst_grid, metadata_idx);
pnanovdb_int64_t byte_offset = pnanovdb_gridblindmetadata_get_data_offset(buf, metadata);
pnanovdb_address_t address = pnanovdb_address_offset64(metadata.address, pnanovdb_int64_as_uint64(byte_offset));
pnanovdb_int64_t dst_byte_offset = pnanovdb_gridblindmetadata_get_data_offset(dst_buf, dst_metadata);
pnanovdb_address_t dst_address = pnanovdb_address_offset64(dst_metadata.address, pnanovdb_int64_as_uint64(dst_byte_offset));
pnanovdb_uint32_t value_size = pnanovdb_gridblindmetadata_get_value_size(buf, metadata);
pnanovdb_uint64_t value_count = pnanovdb_gridblindmetadata_get_value_count(buf, metadata);
pnanovdb_io_memcpy(dst_buf, dst_address, buf, address, value_count * value_size);
}
#else
for (pnanovdb_uint32_t metadata_idx = 0u; metadata_idx < blindmetadata_count; metadata_idx++)
{
pnanovdb_gridblindmetadata_handle_t metadata = pnanovdb_grid_get_gridblindmetadata(buf, grid, metadata_idx);
pnanovdb_gridblindmetadata_handle_t dst_metadata = pnanovdb_grid_get_gridblindmetadata(dst_buf, dst_grid, metadata_idx);
pnanovdb_int64_t byte_offset = pnanovdb_gridblindmetadata_get_data_offset(buf, metadata);
pnanovdb_address_t address = pnanovdb_address_offset64(metadata.address, pnanovdb_int64_as_uint64(byte_offset));
pnanovdb_int64_t dst_byte_offset = pnanovdb_gridblindmetadata_get_data_offset(dst_buf, dst_metadata);
pnanovdb_address_t dst_address = pnanovdb_address_offset64(dst_metadata.address, pnanovdb_int64_as_uint64(dst_byte_offset));
pnanovdb_uint32_t value_size = pnanovdb_gridblindmetadata_get_value_size(buf, metadata);
pnanovdb_uint64_t value_count = pnanovdb_gridblindmetadata_get_value_count(buf, metadata);
pnanovdb_uint64_t dst_value_index = 0llu;
pnanovdb_uint64_t value_index = 0llu;
pnanovdb_address_t index_addr = pnanovdb_root_get_background_address(grid_type, buf, root);
value_index = pnanovdb_read_uint64(buf, index_addr);
pnanovdb_address_t dst_index_addr = pnanovdb_root_get_background_address(grid_type, dst_buf, dst_root);
pnanovdb_write_uint64(dst_buf, dst_index_addr, dst_value_index);
pnanovdb_uint64_t last_root_tile_index = value_index;
pnanovdb_uint64_t last_upper_tile_index = value_index;
pnanovdb_uint64_t last_lower_tile_index = value_index;
pnanovdb_uint64_t last_root_tile_dst_index = dst_value_index;
pnanovdb_uint64_t last_upper_tile_dst_index = dst_value_index;
pnanovdb_uint64_t last_lower_tile_dst_index = dst_value_index;
pnanovdb_io_memcpy(
dst_buf, pnanovdb_address_offset64_product(dst_address, dst_value_index, value_size),
buf, pnanovdb_address_offset64_product(address, value_index, value_size), value_size);
dst_value_index++;
// walk subregion, copy referenced blindmetadata
pnanovdb_uint32_t dst_tile_count = pnanovdb_root_get_tile_count(dst_buf, dst_root);
for (pnanovdb_uint32_t tile_idx = 0u; tile_idx < dst_tile_count; tile_idx++)
{
pnanovdb_root_tile_handle_t dst_root_tile = pnanovdb_root_get_tile(grid_type, dst_root, tile_idx);
pnanovdb_root_tile_handle_t root_tile = pnanovdb_root_get_tile(grid_type, root, tile_idx);
if (pnanovdb_root_tile_get_child_mask(dst_buf, dst_root_tile))
{
pnanovdb_upper_handle_t dst_upper = pnanovdb_root_get_child(grid_type, dst_buf, dst_root, dst_root_tile);
pnanovdb_upper_handle_t upper = pnanovdb_root_get_child(grid_type, buf, root, root_tile);
for (pnanovdb_uint32_t upper_n = 0u; upper_n < PNANOVDB_UPPER_TABLE_COUNT; upper_n++)
{
if (pnanovdb_upper_get_child_mask(dst_buf, dst_upper, upper_n))
{
pnanovdb_lower_handle_t dst_lower = pnanovdb_upper_get_child(grid_type, dst_buf, dst_upper, upper_n);
pnanovdb_lower_handle_t lower = pnanovdb_upper_get_child(grid_type, buf, upper, upper_n);
for (pnanovdb_uint32_t lower_n = 0u; lower_n < PNANOVDB_LOWER_TABLE_COUNT; lower_n++)
{
if (pnanovdb_lower_get_child_mask(dst_buf, dst_lower, lower_n))
{
pnanovdb_leaf_handle_t dst_leaf = pnanovdb_lower_get_child(grid_type, dst_buf, dst_lower, lower_n);
pnanovdb_leaf_handle_t leaf = pnanovdb_lower_get_child(grid_type, buf, lower, lower_n);
pnanovdb_uint32_t leaf_value_count = pnanovdb_leaf_onindex_get_value_count(dst_buf, dst_leaf);
dst_index_addr = pnanovdb_leaf_get_table_address(grid_type, dst_buf, dst_leaf, 0u);
pnanovdb_write_uint64(dst_buf, dst_index_addr, dst_value_index);
pnanovdb_address_t index_addr = pnanovdb_leaf_get_table_address(grid_type, buf, leaf, 0u);
value_index = pnanovdb_read_uint64(buf, index_addr);
pnanovdb_io_memcpy(
dst_buf, pnanovdb_address_offset64_product(dst_address, dst_value_index, value_size),
buf, pnanovdb_address_offset64_product(address, value_index, value_size), value_size * leaf_value_count);
dst_value_index+=leaf_value_count;
}
else if(pnanovdb_lower_get_child_mask(buf, lower, lower_n))
{
// this covers leaves disabled by pred above
pnanovdb_address_t background_addr = pnanovdb_root_get_background_address(grid_type, dst_buf, dst_root);
pnanovdb_uint64_t background_index = pnanovdb_read_uint64(buf, background_addr);
dst_index_addr = pnanovdb_lower_get_table_address(grid_type, dst_buf, dst_lower, lower_n);
pnanovdb_write_uint64(dst_buf, dst_index_addr, background_index);
}
else
{
pnanovdb_address_t index_addr = pnanovdb_lower_get_table_address(grid_type, buf, lower, lower_n);
value_index = pnanovdb_read_uint64(buf, index_addr);
if (last_lower_tile_index != value_index)
{
last_lower_tile_index = value_index;
dst_index_addr = pnanovdb_lower_get_table_address(grid_type, dst_buf, dst_lower, lower_n);
pnanovdb_write_uint64(dst_buf, dst_index_addr, dst_value_index);
last_lower_tile_dst_index = dst_value_index;
pnanovdb_io_memcpy(
dst_buf, pnanovdb_address_offset64_product(dst_address, dst_value_index, value_size),
buf, pnanovdb_address_offset64_product(address, value_index, value_size), value_size);
dst_value_index++;
}
else
{
dst_index_addr = pnanovdb_lower_get_table_address(grid_type, dst_buf, dst_lower, lower_n);
pnanovdb_write_uint64(dst_buf, dst_index_addr, last_lower_tile_dst_index);
}
}
}
}
else
{
pnanovdb_address_t index_addr = pnanovdb_upper_get_table_address(grid_type, buf, upper, upper_n);
value_index = pnanovdb_read_uint64(buf, index_addr);
if (last_upper_tile_index != value_index)
{
last_upper_tile_index = value_index;
dst_index_addr = pnanovdb_upper_get_table_address(grid_type, dst_buf, dst_upper, upper_n);
pnanovdb_write_uint64(dst_buf, dst_index_addr, dst_value_index);
last_upper_tile_dst_index = dst_value_index;
pnanovdb_io_memcpy(
dst_buf, pnanovdb_address_offset64_product(dst_address, dst_value_index, value_size),
buf, pnanovdb_address_offset64_product(address, value_index, value_size), value_size);
dst_value_index++;
}
else
{
dst_index_addr = pnanovdb_upper_get_table_address(grid_type, dst_buf, dst_upper, upper_n);
pnanovdb_write_uint64(dst_buf, dst_index_addr, last_upper_tile_dst_index);
}
}
}
}
else
{
pnanovdb_address_t index_addr = pnanovdb_root_tile_get_value_address(grid_type, buf, root_tile);
value_index = pnanovdb_read_uint64(buf, index_addr);
if (last_root_tile_index != value_index)
{
last_root_tile_index = value_index;
dst_index_addr = pnanovdb_root_tile_get_value_address(grid_type, dst_buf, dst_root_tile);
pnanovdb_write_uint64(dst_buf, dst_index_addr, dst_value_index);
last_root_tile_dst_index = dst_value_index;
pnanovdb_io_memcpy(
dst_buf, pnanovdb_address_offset64_product(dst_address, dst_value_index, value_size),
buf, pnanovdb_address_offset64_product(address, value_index, value_size), value_size);
dst_value_index++;
}
else
{
dst_index_addr = pnanovdb_root_tile_get_value_address(grid_type, dst_buf, dst_root_tile);
pnanovdb_write_uint64(dst_buf, dst_index_addr, last_root_tile_dst_index);
}
}
}
pnanovdb_gridblindmetadata_set_value_count(dst_buf, dst_metadata, dst_value_index);
pnanovdb_int64_t metadata_size_diff = pnanovdb_uint64_as_int64(dst_value_index - value_count) * value_size;
// compact later metadata ranges against new count
for (pnanovdb_uint32_t local_metadata_idx = metadata_idx + 1u; local_metadata_idx < blindmetadata_count; local_metadata_idx++)
{
pnanovdb_gridblindmetadata_handle_t local_dst_metadata = pnanovdb_grid_get_gridblindmetadata(dst_buf, dst_grid, metadata_idx);
pnanovdb_int64_t local_dst_byte_offset = pnanovdb_gridblindmetadata_get_data_offset(dst_buf, local_dst_metadata);
local_dst_byte_offset += metadata_size_diff;
pnanovdb_gridblindmetadata_set_data_offset(dst_buf, local_dst_metadata, local_dst_byte_offset);
}
}
// compute final grid size based on maximum of final leaf and final metadata
pnanovdb_uint64_t grid_size =
dst_current_leaf.address.byte_offset -
dst_grid.address.byte_offset;
for (pnanovdb_uint32_t metadata_idx = 0u; metadata_idx < blindmetadata_count; metadata_idx++)
{
pnanovdb_gridblindmetadata_handle_t dst_metadata = pnanovdb_grid_get_gridblindmetadata(dst_buf, dst_grid, metadata_idx);
pnanovdb_int64_t dst_byte_offset = pnanovdb_gridblindmetadata_get_data_offset(dst_buf, dst_metadata);
pnanovdb_address_t dst_address = pnanovdb_address_offset64(dst_metadata.address, pnanovdb_int64_as_uint64(dst_byte_offset));
pnanovdb_uint32_t value_size = pnanovdb_gridblindmetadata_get_value_size(dst_buf, dst_metadata);
pnanovdb_uint64_t value_count = pnanovdb_gridblindmetadata_get_value_count(dst_buf, dst_metadata);
pnanovdb_uint64_t cmp_grid_size =
dst_address.byte_offset + value_count * value_size -
dst_grid.address.byte_offset;
if (grid_size < cmp_grid_size)
{
grid_size = cmp_grid_size;
}
}
pnanovdb_grid_set_grid_size(dst_buf, dst_grid, grid_size);
#endif
}
PNANOVDB_IO_INLINE void pnanovdb_io_print_slice(pnanovdb_io_buf_t* src_nanovdb, FILE* dst_bmp)
{
pnanovdb_grid_handle_t grid = pnanovdb_io_fread_fileheader(src_nanovdb, 0, 0, 0u);
pnanovdb_buf_t buf = src_nanovdb;
// set grid at post header print offset
printf("pnanovdb_io_print_slice grid(%lld)\n", (long long int)grid.address.byte_offset);
printf("pnanovdb_io_print_slice magic(%llx)\n",
(unsigned long long int)pnanovdb_grid_get_magic(buf, grid));
printf("pnanovdb_io_print_slice version(%x)\n", pnanovdb_grid_get_version(buf, grid));
pnanovdb_tree_handle_t tree = pnanovdb_grid_get_tree(buf, grid);
printf("pnanovdb_io_print_slice tree(%lld)\n", (long long int)tree.address.byte_offset);
printf("pnanovdb_io_print_slice node_count_leaf(%d)\n", pnanovdb_tree_get_node_count_leaf(buf, tree));
printf("pnanovdb_io_print_slice node_count_lower(%d)\n", pnanovdb_tree_get_node_count_lower(buf, tree));
printf("pnanovdb_io_print_slice node_count_upper(%d)\n", pnanovdb_tree_get_node_count_upper(buf, tree));
pnanovdb_root_handle_t root = pnanovdb_tree_get_root(buf, tree);
pnanovdb_grid_type_t grid_type = pnanovdb_grid_get_grid_type(buf, grid);
pnanovdb_coord_t bbox_min = pnanovdb_root_get_bbox_min(buf, root);
pnanovdb_coord_t bbox_max = pnanovdb_root_get_bbox_max(buf, root);
printf("pnanovdb_io_print_slice range([(%d, %d, %d),(%d, %d, %d)])\n",
bbox_min.x, bbox_min.y, bbox_min.z,
bbox_max.x, bbox_max.y, bbox_max.z);
pnanovdb_int32_t image_width = bbox_max.x - bbox_min.x;
pnanovdb_int32_t image_height = bbox_max.y - bbox_min.y;
pnanovdb_int32_t k = (bbox_max.z + bbox_min.z) / 2;
char headerField0 = 'B';
char headerField1 = 'M';
uint32_t size = 54 + image_width * image_height * 4u;
uint16_t reserved1 = 0;
uint16_t reserved2 = 0;
uint32_t offset = 54;
uint32_t headerSize = 40;
uint32_t width = image_width;
uint32_t height = image_height;
uint16_t colorPlanes = 1;
uint16_t bitsPerPixel = 32;
uint32_t compressionMethod = 0;
uint32_t imageSize = image_width * image_height * 4u;
uint32_t hRes = 2000;
uint32_t vRes = 2000;
uint32_t numColors = 0;
uint32_t numImportantColors = 0;
fwrite(&headerField0, 1, 1, dst_bmp);
fwrite(&headerField1, 1, 1, dst_bmp);
fwrite(&size, 4, 1, dst_bmp);
fwrite(&reserved1, 2, 1, dst_bmp);
fwrite(&reserved2, 2, 1, dst_bmp);
fwrite(&offset, 4, 1, dst_bmp);
fwrite(&headerSize, 4, 1, dst_bmp);
fwrite(&width, 4, 1, dst_bmp);
fwrite(&height, 4, 1, dst_bmp);
fwrite(&colorPlanes, 2, 1, dst_bmp);
fwrite(&bitsPerPixel, 2, 1, dst_bmp);
fwrite(&compressionMethod, 4, 1, dst_bmp);
fwrite(&imageSize, 4, 1, dst_bmp);
fwrite(&hRes, 4, 1, dst_bmp);
fwrite(&vRes, 4, 1, dst_bmp);
fwrite(&numColors, 4, 1, dst_bmp);
fwrite(&numImportantColors, 4, 1, dst_bmp);
for (pnanovdb_int32_t j = image_height - 1; j >= 0; j--)
{
for (pnanovdb_int32_t i = 0; i < image_width; i++)
{
pnanovdb_coord_t ijk = {
i + bbox_min.x,
j + bbox_min.y,
k
};
pnanovdb_address_t addr = pnanovdb_root_get_value_address(grid_type, buf, root, &ijk);
float density_value = 1.f;
float color_value = pnanovdb_read_float(buf, addr);
pnanovdb_uint32_t raw_value = pnanovdb_uint32_t(255.f * pnanovdb_max(0.f, pnanovdb_min(1.f, color_value))) |
(pnanovdb_uint32_t(255.f * pnanovdb_max(0.f, pnanovdb_min(1.f, color_value))) << 8u) |
(pnanovdb_uint32_t(255.f * pnanovdb_max(0.f, pnanovdb_min(1.f, color_value))) << 16u) |
(pnanovdb_uint32_t(255.f * pnanovdb_max(0.f, pnanovdb_min(1.f, density_value))) << 24u);
fwrite(&raw_value, 4u, 1u, dst_bmp);
}
}
}
#endif
#endif // end of NANOVDB_PUTILS_IO_H_HAS_BEEN_INCLUDED
-2
View File
@@ -323,8 +323,6 @@ NB_MODULE(nanovdb, m)
.value("Vec4d", GridType::Vec4d)
.value("Index", GridType::Index)
.value("OnIndex", GridType::OnIndex)
.value("IndexMask", GridType::IndexMask)
.value("OnIndexMask", GridType::OnIndexMask)
.value("PointIndex", GridType::PointIndex)
.value("Vec3u8", GridType::Vec3u8)
.value("Vec3u16", GridType::Vec3u16)
+115 -72
View File
@@ -53,7 +53,7 @@
auto dstGrid = handle.grid<float>();// get a pointer to the destination grid
\endcode
\brief Convert a base-pointer to an openvdb grid, denoted srcGrid, to a nanovdb
\brief Convert a base-pointer to an openvdb grid, denoted srcGrid, to a nanovdb
grid of the same type, e.g. float -> float or openvdb::Vec3f -> nanovdb::Vec3f
\code
auto handle = nanovdb::openToNanoVDB(*srcGrid);// convert source grid to a grid handle
@@ -96,7 +96,7 @@
#include <limits>
#include <vector>
#include <set>
#include <cstring> // for memcpy
#include <cstring> // for strncpy
#include <type_traits>
namespace nanovdb {// ============================================================================
@@ -124,6 +124,23 @@ openToNanoVDB(const openvdb::GridBase::Ptr& base,
StatsMode sMode = StatsMode::Default,
CheckMode cMode = CheckMode::Default,
int verbose = 0);
/// @brief Forward declaration of free-standing function that converts an OpenVDB GridBase into a NanoVDB GridHandle with an IndexGrid
/// @tparam DstBuildT Should be either nanovdb::ValueIndex or nanovdb::ValueOnIndex
/// @tparam BufferT Type of the buffer used to allocate the destination grid
/// @param base Shared pointer to a base openvdb grid to be converted
/// @param channels Number of sidecar channels with the values (active or all) in the source grid
/// @param includeStats If true stats are also indexed
/// @param includeTiles If true tile values (active or all) are also indexed
/// @param verbose Mode of verbosity
/// @return Handle to the destination NanoGrid of type IndexGrid or OnIndexGrid
template<typename DstBuildT = nanovdb::ValueOnIndex, typename BufferT = HostBuffer>
typename util::enable_if<BuildTraits<DstBuildT>::is_index, GridHandle<BufferT>>::type
openToIndexVDB(const openvdb::GridBase::Ptr& base,
uint32_t channels = 1u,
bool includeStats = true,
bool includeTiles = true,
int verbose = 0);
#endif
//================================================================================================
@@ -260,12 +277,6 @@ public:
}
};// AbsDiff
inline std::ostream& operator<<(std::ostream& os, const AbsDiff& diff)
{
os << "Absolute tolerance: " << diff.getTolerance();
return os;
}
//================================================================================================
/// @brief Compression oracle based on relative difference
@@ -290,15 +301,9 @@ public:
}
};// RelDiff
inline std::ostream& operator<<(std::ostream& os, const RelDiff& diff)
{
os << "Relative tolerance: " << diff.getTolerance();
return os;
}
//================================================================================================
/// @brief The NodeAccessor provides a uniform API for accessing nodes got NanoVDB, OpenVDB and build Grids
/// @brief The NodeAccessor provides a uniform API for accessing nodes in NanoVDB, OpenVDB and build Grids
///
/// @note General implementation that works with nanovdb::tools::build::Grid
template <typename GridT>
@@ -769,8 +774,8 @@ private:
std::unique_ptr<SrcNodeAccT> mSrcNodeAccPtr;// placeholder for potential local instance
const SrcNodeAccT &mSrcNodeAcc;
struct BlindMetaData; // forward declaration
std::set<BlindMetaData> mBlindMetaData; // sorted according to BlindMetaData.order
struct OrderedBlindMetaData; // forward declaration
std::set<OrderedBlindMetaData> mBlindMetaData; // sorted set of GridBlindMetaData
struct Codec { float min, max; uint64_t offset; uint8_t log2; };// used for adaptive bit-rate quantization
std::unique_ptr<Codec[]> mCodec;// defines a codec per leaf node when DstBuildT = FpN
StatsMode mStats;
@@ -812,47 +817,31 @@ CreateNanoGrid<SrcGridT>::CreateNanoGrid(const SrcNodeAccT &srcNodeAcc)
//================================================================================================
template <typename SrcGridT>
struct CreateNanoGrid<SrcGridT>::BlindMetaData
struct CreateNanoGrid<SrcGridT>::OrderedBlindMetaData
{
BlindMetaData(const std::string& name,// name + used to derive GridBlindDataSemantic
const std::string& type,// used to derive GridType of blind data
GridBlindDataClass dataClass,
size_t i, size_t valueCount, size_t valueSize)
: metaData(reinterpret_cast<GridBlindMetaData*>(new char[sizeof(GridBlindMetaData)]))
OrderedBlindMetaData(const std::string& name,// name, also used to derive GridBlindDataSemantic
const std::string& type,// used to derive GridType of blind data
GridBlindDataClass dataClass,
size_t i, size_t valueCount, size_t valueSize)
: metaData(new GridBlindMetaData(0, valueCount, valueSize, this->mapToSemantics(name), dataClass, this->mapToType(type)))
, order(i)// sorted id of meta data
, size(math::AlignUp<NANOVDB_DATA_ALIGNMENT>(valueCount * valueSize))
{
util::memzero(metaData, sizeof(GridBlindMetaData));// zero out all meta data
if (name.length()>=GridData::MaxNameSize) throw std::runtime_error("blind data name exceeds limit");
std::memcpy(metaData->mName, name.c_str(), name.length() + 1);
metaData->mValueCount = valueCount;
metaData->mSemantic = BlindMetaData::mapToSemantics(name);
metaData->mDataClass = dataClass;
metaData->mDataType = BlindMetaData::mapToType(type);
metaData->mValueSize = valueSize;
if (!metaData->setName(name.c_str())) throw std::runtime_error("blind data name exceeds character limit");
NANOVDB_ASSERT(metaData->isValid());
}
BlindMetaData(const std::string& name,// only name
GridBlindDataSemantic dataSemantic,
GridBlindDataClass dataClass,
GridType dataType,
size_t i, size_t valueCount, size_t valueSize)
: metaData(reinterpret_cast<GridBlindMetaData*>(new char[sizeof(GridBlindMetaData)]))
OrderedBlindMetaData(const std::string& name,// only used to name blind data
GridBlindDataSemantic dataSemantic,
GridBlindDataClass dataClass,
GridType dataType,
size_t i, size_t valueCount, size_t valueSize)
: metaData(new GridBlindMetaData(0, valueCount, valueSize, dataSemantic, dataClass, dataType))
, order(i)// sorted id of meta data
, size(math::AlignUp<NANOVDB_DATA_ALIGNMENT>(valueCount * valueSize))
{
std::memset(metaData, 0, sizeof(GridBlindMetaData));// zero out all meta data
if (name.length()>=GridData::MaxNameSize) throw std::runtime_error("blind data name exceeds character limit");
std::memcpy(metaData->mName, name.c_str(), name.length() + 1);
metaData->mValueCount = valueCount;
metaData->mSemantic = dataSemantic;
metaData->mDataClass = dataClass;
metaData->mDataType = dataType;
metaData->mValueSize = valueSize;
if (!metaData->setName(name.c_str())) throw std::runtime_error("blind data name exceeds character limit");
NANOVDB_ASSERT(metaData->isValid());
}
~BlindMetaData(){ delete [] reinterpret_cast<char*>(metaData); }
bool operator<(const BlindMetaData& other) const { return order < other.order; } // required by std::set
~OrderedBlindMetaData(){ delete metaData; }
bool operator<(const OrderedBlindMetaData& other) const { return order < other.order; } // required by std::set
static GridType mapToType(const std::string& name)
{
GridType type = GridType::Unknown;
@@ -869,6 +858,9 @@ struct CreateNanoGrid<SrcGridT>::BlindMetaData
}
return type;
}
/// @brief Maps from string names of point attributes in openvdb to GridBlindDataSemantic
/// @param String name, typically used for point attributes in OpenVDB
/// @return GridBlindDataSemantic
static GridBlindDataSemantic mapToSemantics(const std::string& name)
{
GridBlindDataSemantic semantic = GridBlindDataSemantic::Unknown;
@@ -885,9 +877,10 @@ struct CreateNanoGrid<SrcGridT>::BlindMetaData
}
return semantic;
}
GridBlindMetaData *metaData;
const size_t order, size;
}; // CreateNanoGrid::BlindMetaData
size_t memUsage() const {return metaData->blindDataSize();}
GridBlindMetaData *metaData;// a pointer is preferred since it avoids deep copy during sorting
const size_t order;// index used for sorting of the blind meta data
}; // CreateNanoGrid::OrderedBlindMetaData
//================================================================================================
@@ -949,7 +942,7 @@ GridHandle<BufferT> CreateNanoGrid<SrcGridT>::initHandle(const BufferT& pool)
mOffset.meta = mOffset.leaf + mLeafNodeSize;// leaf nodes end and blind meta data begins
mOffset.blind = mOffset.meta + sizeof(GridBlindMetaData)*mBlindMetaData.size(); // meta data ends and blind data begins
mOffset.size = mOffset.blind;// end of buffer
for (const auto& b : mBlindMetaData) mOffset.size += b.size; // accumulate all the blind data
for (const auto& b : mBlindMetaData) mOffset.size += b.memUsage(); // accumulate all the blind data
auto buffer = BufferT::create(mOffset.size, &pool);
mBufferPtr = buffer.data();
@@ -1171,7 +1164,7 @@ CreateNanoGrid<SrcGridT>::preProcess(uint32_t channels)
uint32_t order = mBlindMetaData.size();
char str[16];
for (uint32_t i=0; i<channels; ++i) {
mBlindMetaData.emplace("channel_"+std::to_string(i),
mBlindMetaData.emplace("channel_" + std::to_string(i),
toStr(str, toGridType<SrcValueT>()),
GridBlindDataClass::AttributeArray,
order++,
@@ -1269,7 +1262,6 @@ CreateNanoGrid<SrcGridT>::processLeafs()
} else {
dstLeaf->mPrefixSum = 0u;
}
if constexpr(BuildTraits<DstBuildT>::is_indexmask) dstLeaf->mMask = dstLeaf->mValueMask;
}
});
} // CreateNanoGrid::processLeafs<ValueIndex or ValueOnIndex>
@@ -1679,12 +1671,12 @@ void CreateNanoGrid<SrcGridT>::processTree()
dstTree->mNodeCount[2] = static_cast<uint32_t>(nodeCount[2]);
// Count number of active leaf level tiles
dstTree->mTileCount[0] = util::reduce(util::Range1D(0,nodeCount[1]), uint32_t(0), [&](util::Range1D &r, uint32_t sum){
dstTree->mTileCount[0] = util::reduce(util::Range1D(0,nodeCount[1]), uint32_t(0), [&](const util::Range1D &r, uint32_t sum){
for (auto i=r.begin(); i!=r.end(); ++i) sum += mSrcNodeAcc.template node<1>(i).getValueMask().countOn();
return sum;}, std::plus<uint32_t>());
// Count number of active lower internal node tiles
dstTree->mTileCount[1] = util::reduce(util::Range1D(0,nodeCount[2]), uint32_t(0), [&](util::Range1D &r, uint32_t sum){
dstTree->mTileCount[1] = util::reduce(util::Range1D(0,nodeCount[2]), uint32_t(0), [&](const util::Range1D &r, uint32_t sum){
for (auto i=r.begin(); i!=r.end(); ++i) sum += mSrcNodeAcc.template node<2>(i).getValueMask().countOn();
return sum;}, std::plus<uint32_t>());
@@ -1693,7 +1685,7 @@ void CreateNanoGrid<SrcGridT>::processTree()
for (auto it = mSrcNodeAcc.root().cbeginValueOn(); it; ++it) dstTree->mTileCount[2] += 1;
// Count number of active voxels
dstTree->mVoxelCount = util::reduce(util::Range1D(0, nodeCount[0]), uint64_t(0), [&](util::Range1D &r, uint64_t sum){
dstTree->mVoxelCount = util::reduce(util::Range1D(0, nodeCount[0]), uint64_t(0), [&](const util::Range1D &r, uint64_t sum){
for (auto i=r.begin(); i!=r.end(); ++i) sum += mSrcNodeAcc.template node<0>(i).getValueMask().countOn();
return sum;}, std::plus<uint64_t>());
@@ -1715,16 +1707,6 @@ void CreateNanoGrid<SrcGridT>::processGrid()
dstGrid->mBlindMetadataCount = static_cast<uint32_t>(mBlindMetaData.size());
dstGrid->mData1 = this->valueCount();
// if (!isValid(dstGrid->mGridType, dstGrid->mGridClass)) {
//#if 1
// char str[30];
// fprintf(stderr,"Warning: Strange combination of GridType(\"%s\") and GridClass(\"%s\"). Consider changing GridClass to \"Unknown\"\n",
// toStr(str, dstGrid->mGridType), toStr(str + 15, dstGrid->mGridClass));
//#else
// throw std::runtime_error("Invalid combination of GridType("+std::to_string(int(dstGrid->mGridType))+
// ") and GridClass("+std::to_string(int(dstGrid->mGridClass))+"). See NanoVDB.h for details!");
//#endif
// }
util::memzero(dstGrid->mGridName, GridData::MaxNameSize);// initialize mGridName to zero
strncpy(dstGrid->mGridName, mSrcNodeAcc.getName().c_str(), GridData::MaxNameSize-1);
if (mSrcNodeAcc.hasLongGridName()) dstGrid->setLongGridNameOn();// grid name is long so store it as blind data
@@ -1736,11 +1718,11 @@ void CreateNanoGrid<SrcGridT>::processGrid()
dstGrid->mBlindMetadataCount = static_cast<uint32_t>(mBlindMetaData.size());
char *blindData = util::PtrAdd<char>(mBufferPtr, mOffset.blind);
for (const auto &b : mBlindMetaData) {
std::memcpy(metaData, b.metaData, sizeof(GridBlindMetaData));
*metaData = *b.metaData;
metaData->setBlindData(blindData);// sets metaData.mOffset
if (metaData->mDataClass == GridBlindDataClass::GridName) strcpy(blindData, mSrcNodeAcc.getName().c_str());
++metaData;
blindData += b.size;
blindData += b.memUsage();
}
mBlindMetaData.clear();
}
@@ -1820,10 +1802,13 @@ CreateNanoGrid<SrcGridT>::postProcess(uint32_t channels)
for (uint32_t i=0; i<channels; ++i) {
const std::string name = "channel_"+std::to_string(i);
int j = dstGrid->findBlindData(name.c_str());
if (j<0) throw std::runtime_error("missing " + name);
if (j<0) throw std::runtime_error("CreateNanoGrid::postProcess: missing " + name);
auto *metaData = this->dstMeta(j);// partially set in processGrid
metaData->mDataClass = GridBlindDataClass::ChannelArray;
metaData->mDataType = toGridType<SrcValueT>();
if (metaData->mSemantic == GridBlindDataSemantic::Unknown) {// try to derive it from the source grid
metaData->mSemantic = toSemantic( mSrcNodeAcc.gridClass());
}
SrcValueT *blindData = const_cast<SrcValueT*>(metaData->template getBlindData<SrcValueT>());
if (i>0) {// concurrent copy from previous channel
util::forEach(0,valueCount,1024,[&](const util::Range1D &r){
@@ -2024,7 +2009,7 @@ template<typename BufferT>
GridHandle<BufferT>
openToNanoVDB(const openvdb::GridBase::Ptr& base,
StatsMode sMode,
CheckMode cMode,
CheckMode cMode,
int verbose)
{
// We need to define these types because they are not defined in OpenVDB
@@ -2064,10 +2049,68 @@ openToNanoVDB(const openvdb::GridBase::Ptr& base,
OPENVDB_THROW(openvdb::RuntimeError, "Unrecognized OpenVDB grid type");
}
}// openToNanoVDB
template<typename DstBuildT, typename BufferT>
typename util::enable_if<BuildTraits<DstBuildT>::is_index, GridHandle<BufferT>>::type
openToIndexVDB(const openvdb::GridBase::Ptr& base,
uint32_t channels,
bool includeStats,
bool includeTiles,
int verbose)
{
// We need to define these types because they are not defined in OpenVDB
using openvdb_Vec4fTree = typename openvdb::tree::Tree4<openvdb::Vec4f, 5, 4, 3>::Type;
using openvdb_Vec4dTree = typename openvdb::tree::Tree4<openvdb::Vec4d, 5, 4, 3>::Type;
using openvdb_Vec4fGrid = openvdb::Grid<openvdb_Vec4fTree>;
using openvdb_Vec4dGrid = openvdb::Grid<openvdb_Vec4dTree>;
using openvdb_UInt32Grid = openvdb::Grid<openvdb::UInt32Tree>;
if (auto grid = openvdb::GridBase::grid<openvdb::FloatGrid>(base)) {
return createNanoGrid<openvdb::FloatGrid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb::DoubleGrid>(base)) {
return createNanoGrid<openvdb::DoubleGrid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb::Int32Grid>(base)) {
return createNanoGrid<openvdb::Int32Grid, DstBuildT,BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb::Int64Grid>(base)) {
return createNanoGrid<openvdb::Int64Grid, DstBuildT, BufferT>(*grid, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb_UInt32Grid>(base)) {
return createNanoGrid<openvdb_UInt32Grid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb::Vec3fGrid>(base)) {
return createNanoGrid<openvdb::Vec3fGrid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb::Vec3dGrid>(base)) {
return createNanoGrid<openvdb::Vec3dGrid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb::tools::PointIndexGrid>(base)) {
return createNanoGrid<openvdb::tools::PointIndexGrid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb::points::PointDataGrid>(base)) {
return createNanoGrid<openvdb::points::PointDataGrid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb::MaskGrid>(base)) {
return createNanoGrid<openvdb::MaskGrid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb::BoolGrid>(base)) {
return createNanoGrid<openvdb::BoolGrid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb_Vec4fGrid>(base)) {
return createNanoGrid<openvdb_Vec4fGrid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else if (auto grid = openvdb::GridBase::grid<openvdb_Vec4dGrid>(base)) {
return createNanoGrid<openvdb_Vec4dGrid, DstBuildT, BufferT>(*grid, channels, includeStats, includeTiles, verbose);
} else {
OPENVDB_THROW(openvdb::RuntimeError, "Unrecognized OpenVDB grid type");
}
}// openToIndexVDB
#endif
}// namespace tools ===============================================================================
} // namespace nanovdb
inline std::ostream& operator<<(std::ostream& os, const nanovdb::tools::AbsDiff& diff)
{
os << "Absolute tolerance: " << diff.getTolerance();
return os;
}
inline std::ostream& operator<<(std::ostream& os, const nanovdb::tools::RelDiff& diff)
{
os << "Relative tolerance: " << diff.getTolerance();
return os;
}
#endif // NANOVDB_TOOLS_CREATENANOGRID_H_HAS_BEEN_INCLUDED
+3 -3
View File
@@ -617,7 +617,7 @@ initSphere(double radius, // radius of sphere in world units
grid->setTransform(voxelSize, origin);
// Define radius of sphere with narrow-band in voxel units
const ValueT r0 = radius / ValueT(voxelSize), rmax = r0 + ValueT(halfWidth);
const ValueT r0 = ValueT(radius / voxelSize), rmax = r0 + ValueT(halfWidth);
// Radius below the Nyquist frequency
if (r0 < ValueT(1.5f)) return grid;
@@ -689,7 +689,7 @@ initTorus(double radius1, // major radius of torus in world units
grid->setTransform(voxelSize, origin);
// Define size of torus with narrow-band in voxel units
const ValueT r1 = radius1 / ValueT(voxelSize), r2 = radius2 / ValueT(voxelSize), rmax1 = r1 + r2 + ValueT(halfWidth), rmax2 = r2 + ValueT(halfWidth);
const ValueT r1 = ValueT(radius1 / voxelSize), r2 = ValueT(radius2 / voxelSize), rmax1 = r1 + r2 + ValueT(halfWidth), rmax2 = r2 + ValueT(halfWidth);
// Radius below the Nyquist frequency
if (r2 < ValueT(1.5)) return grid;
@@ -1706,7 +1706,7 @@ createPointScatter(const NanoGrid<SrcBuildT>& srcGrid, // origin of grid in worl
dstLeaf->mValueMask = srcLeaf.valueMask();
for (uint32_t j = 0, m = 0; j < 512; ++j) {
if (dstLeaf->mValueMask.isOn(j)) {
const Vec3f ijk = dstLeaf->offsetToGlobalCoord(j).asVec3s();// floating-point representatrion of index coorindates
const Vec3f ijk = dstLeaf->offsetToGlobalCoord(j).asVec3s();// floating-point representation of index coordinates
for (int n = 0; n < pointsPerVoxel; ++n) xyz.push_back(srcGrid.indexToWorld(randomPoint() + ijk));
m += pointsPerVoxel;
}// active voxels
+1 -1
View File
@@ -1968,7 +1968,7 @@ void Grid<BuildT>::operator()(const Func& func, const CoordBBox& bbox, ValueType
NANOVDB_ASSERT(leaf == nullptr);
}
}// loop over sub-part of leafBBox
if (leaf) delete leaf;
delete leaf;
});
// Prune leaf and tile nodes
+422 -234
View File
@@ -15,179 +15,317 @@
#include <nanovdb/NanoVDB.h> // manages and streams the raw memory buffer of a NanoVDB grid.
#include <nanovdb/GridHandle.h>
#include <nanovdb/util/Util.h>
#include <nanovdb/util/ForEach.h>
#include <openvdb/openvdb.h>
#include <openvdb/tools/SignedFloodFill.h>
#ifndef NANOVDB_TOOLS_NANOTOOPENVDB_H_HAS_BEEN_INCLUDED
#define NANOVDB_TOOLS_NANOTOOPENVDB_H_HAS_BEEN_INCLUDED
template<typename T>
struct ConvertTrait {using Type = T;};
template<typename T>
struct ConvertTrait<nanovdb::math::Vec3<T>> {using Type = openvdb::math::Vec3<T>;};
template<typename T>
struct ConvertTrait<nanovdb::math::Vec4<T>> {using Type = openvdb::math::Vec4<T>;};
template<>
struct ConvertTrait<nanovdb::Fp4> {using Type = float;};
template<>
struct ConvertTrait<nanovdb::Fp8> {using Type = float;};
template<>
struct ConvertTrait<nanovdb::Fp16> {using Type = float;};
template<>
struct ConvertTrait<nanovdb::FpN> {using Type = float;};
template<>
struct ConvertTrait<nanovdb::ValueMask> {using Type = openvdb::ValueMask;};
namespace nanovdb {
namespace tools {
/// @brief Forward declaration of free-standing function that de-serializes a typed NanoVDB grid into an OpenVDB Grid
template<typename NanoBuildT>
typename openvdb::Grid<typename openvdb::tree::Tree4<typename ConvertTrait<NanoBuildT>::Type>::Type>::Ptr
nanoToOpenVDB(const NanoGrid<NanoBuildT>& grid, int verbose = 0);
namespace trait {
/// @brief Forward declaration of free-standing function that de-serializes a NanoVDB GridHandle into an OpenVDB GridBase
/// @brief Struct that maps all nanovdb types to corresponding openvdb types.
/// @tparam T NanoVDB build type, e.g. nanovdb::ValueMask -> openvdb::ValueMask
/// nanovdb::Fp8 -> float, nanovdb::ValueIndex -> uin64_t, float -> float
/// @note Since Windows appears to always template instantiate the return_type in
/// enable_if<bool, return_type>, MapToOpen is designed to work for all types T,
/// even types like ValueOnIndex that doesn't have an openvdb analogue!
template<typename T>// see below for more template specializations
struct MapToOpen { using type = typename BuildToValueMap<T>::type; };
template<typename T>
using MapToOpenT = typename MapToOpen<T>::type;
template <typename T>
using OpenTree = typename openvdb::tree::Tree4<trait::MapToOpenT<T>, 5, 4, 3>::Type;
template <typename T>
using OpenGrid = openvdb::Grid< OpenTree<T> >;
template <typename T>
using OpenGridPtr = typename OpenGrid< T >::Ptr;
}// trait namespace
/// @brief Free-standing function that de-serializes a typed NanoVDB grid into an OpenVDB Grid
/// @tparam NanoBuildT Template build type for the @c grid
/// @param grid NanoVDB grid to be converted to an OpenVDB grid
/// @return If NanoBuildT is an index type, e.g. ValueOnIndex, a shared pointer to an OpenVDB GridBase
/// is returned. Otherwise a shared pointer to an OpenVDB Grid of a matching type
///. (trait::OpenGridPtr<NanoBuildT>) is returned.
template<typename NanoBuildT>
auto nanoToOpenVDB(const NanoGrid<NanoBuildT>& grid);
/// @brief Free-standing function that de-serializes an IndexGrid with a sidecar into an OpenVDB Grid
/// @tparam NanoValueT Template type of the sidecar data accompanying the index @c grid
/// @param grid IndexGrid into the sidecar data
/// @param sideCar Linear array of sidecar data
/// @param gridClass GridClass corresponding to the sidecar data
/// @param gridName Name of the output grid
/// @return Shared pointer to an OpenVDB Grid of a matching type (trait::OpenGridPtr<NanoValueT>)
template<typename NanoBuildT, typename NanoValueT>
util::enable_if_t<BuildTraits<NanoBuildT>::is_index, trait::OpenGridPtr<NanoValueT>>
nanoToOpenVDB(const NanoGrid<NanoBuildT>& grid,
const NanoValueT *sideCar,
GridClass gridClass = GridClass::Unknown,
const char *gridName = nullptr);
/// @brief Forward declaration of free-standing function that de-serializes a grid in a NanoVDB GridHandle into an OpenVDB GridBase
/// @tparam BufferT Template type of the buffer used to allocate the grid handle
/// @param handle Handle for the input grid to be converted
/// @param gridID ID of the grid in the GridHandle to be converted (defaults to first grid)
/// @return Shared pointer to an OpenVDB BaseGrid
/// @note If grid number @c gridID in @c handle has GridType IndexGrid, then the first suitable blind data is used as the sidecar data
template<typename BufferT>
openvdb::GridBase::Ptr
nanoToOpenVDB(const GridHandle<BufferT>& handle, int verbose = 0, uint32_t n = 0);
nanoToOpenVDB(const GridHandle<BufferT>& handle, uint32_t gridID = 0);
// ================================================================================================
namespace trait {
template<>
struct MapToOpen<ValueMask> {using type = openvdb::ValueMask;};
template<typename T>
struct MapToOpen<math::Vec3<T>>{using type = openvdb::math::Vec3<T>;};
template<typename T>
struct MapToOpen<math::Vec4<T>>{using type = openvdb::math::Vec4<T>;};
template<typename T, uint32_t LEVEL>
struct OpenNode;
// Partial template specialization of the OpenNode struct
template<typename T>
struct OpenNode<T, 0> {using type = openvdb::tree::LeafNode<MapToOpenT<T>, 3>;};
template<typename T>
struct OpenNode<T, 1> {using type = openvdb::tree::InternalNode<typename OpenNode<T, 0>::type, 4>;};
template<typename T>
struct OpenNode<T, 2> {using type = openvdb::tree::InternalNode<typename OpenNode<T, 1>::type, 5>;};
template<typename T, uint32_t LEVEL>
using OpenNodeT = typename OpenNode<T, LEVEL>::type;
/// @brief Maps from nanovdb::GridClass to openvdb::GridClass
/// @param gridClass nanovdb::GridClass
/// @return openvdb::GridClass
static openvdb::GridClass toOpenGridClass(GridClass gridClass)
{
switch (gridClass) {
case GridClass::LevelSet:
return openvdb::GRID_LEVEL_SET;
case GridClass::FogVolume:
return openvdb::GRID_FOG_VOLUME;
case GridClass::Staggered:
return openvdb::GRID_STAGGERED;
case GridClass::PointIndex:
throw std::runtime_error("NanoToOpenVDB does not yet support PointIndexGrids");
case GridClass::PointData:
throw std::runtime_error("NanoToOpenVDB does not yet support PointDataGrids");
default:
return openvdb::GRID_UNKNOWN;
}
}
template<typename T>
static const auto* mapPtr(const T *v) {return reinterpret_cast<const typename MapToOpen<T>::type*>(v);}
static openvdb::Coord mapCoord(const Coord &ijk){return openvdb::Coord(ijk[0], ijk[1], ijk[2]);}
template<uint32_t LOG2DIM>
static auto mapMask(const Mask<LOG2DIM> &m){return reinterpret_cast<const openvdb::util::NodeMask<LOG2DIM>&>(m);}
}// trait namespace
/// @brief This class will serialize an OpenVDB grid into a NanoVDB grid managed by a GridHandle.
template<typename NanoBuildT>
class NanoToOpenVDB
{
using NanoNode0 = nanovdb::LeafNode<NanoBuildT, openvdb::Coord, openvdb::util::NodeMask>; // note that it's using openvdb coord nd mask types!
using NanoNode1 = nanovdb::InternalNode<NanoNode0>;
using NanoNode2 = nanovdb::InternalNode<NanoNode1>;
using NanoRootT = nanovdb::RootNode<NanoNode2>;
using NanoTreeT = nanovdb::Tree<NanoRootT>;
using NanoGridT = nanovdb::Grid<NanoTreeT>;
using NanoValueT = typename NanoGridT::ValueType;
using OpenBuildT = typename ConvertTrait<NanoBuildT>::Type; // e.g. float -> float but nanovdb::math::Vec3<float> -> openvdb::Vec3<float>
using OpenNode0 = openvdb::tree::LeafNode<OpenBuildT, NanoNode0::LOG2DIM>; // leaf
using OpenNode1 = openvdb::tree::InternalNode<OpenNode0, NanoNode1::LOG2DIM>; // lower
using OpenNode2 = openvdb::tree::InternalNode<OpenNode1, NanoNode2::LOG2DIM>; // upper
using OpenRootT = openvdb::tree::RootNode<OpenNode2>;
using OpenTreeT = openvdb::tree::Tree<OpenRootT>;
using OpenGridT = openvdb::Grid<OpenTreeT>;
using OpenValueT = typename OpenGridT::ValueType;
public:
/// @brief Construction from an existing const OpenVDB Grid.
NanoToOpenVDB(){};
/// @brief Return a shared pointer to a NanoVDB grid constructed from the specified OpenVDB grid
typename OpenGridT::Ptr operator()(const NanoGrid<NanoBuildT>& grid, int verbose = 0);
/// @brief Default c-tor
NanoToOpenVDB(){}
/// @brief Converts nanovdb::Grid<T> -> openvdb::Grid<T>::Ptr
/// @tparam NanoBuildT Template type for the input NanoVDB grid
/// @param grid NanoVDB grid to be converted
/// @return Shared pointer to an OpenVDB grid of matching type
template<typename NanoBuildT>
util::disable_if_t<BuildTraits<NanoBuildT>::is_index, trait::OpenGridPtr<NanoBuildT>>
operator()(const NanoGrid<NanoBuildT>& grid);
/// @brief Converts nanovdb::Grid<Index> + blind data -> openvdb::GridBase::Ptr
/// @param idxGrid NanoVDB IndexGrid with blind data to be converted
/// @param blindDataID Id of the bind data to be used as the sidecar. A negative values means
/// pick the first available (relevant) blind data.
/// @return shared pointer to an OpenVDB GridBase
template<typename NanoIndexT>
util::enable_if_t<BuildTraits<NanoIndexT>::is_index, openvdb::GridBase::Ptr>
operator()(const NanoGrid<NanoIndexT>& idxGrid, int blindDataID = -1);
/// @brief Converts nanovdb::Grid<NanoIndexT> + NanoValueT[] -> openvdb::Grid<NanoValueT>::Ptr
/// @tparam NanoValueT Template to of the sidecar data
/// @param grid NanoVDB IndexGrid
/// @param sideCar Linear array of sidecar data
/// @param gridClass GridClass of the @c sideCar data
/// @param name Name of the output grid
/// @return Shared pointer to an OpenVDB grid of matching type
template<typename NanoIndexT, typename NanoValueT>
util::enable_if_t<BuildTraits<NanoIndexT>::is_index, trait::OpenGridPtr<NanoValueT>>
operator()(const NanoGrid<NanoIndexT>& grid,
const NanoValueT *sideCar,
GridClass gridClass = GridClass::Unknown,
const char *name = nullptr);
private:
template<typename NanoNodeT, typename OpenNodeT>
OpenNodeT* processNode(const NanoNodeT*);
template<int LEVEL, typename NanoBuildT>
util::disable_if_t<BuildTraits<NanoBuildT>::is_index || LEVEL == 0>
process(trait::OpenNodeT<NanoBuildT, LEVEL>*, const NanoNodeT<NanoBuildT, LEVEL>*);
OpenNode2* process(const NanoNode2* node) {return this->template processNode<NanoNode2, OpenNode2>(node);}
OpenNode1* process(const NanoNode1* node) {return this->template processNode<NanoNode1, OpenNode1>(node);}
template<int LEVEL, typename NanoIndexT, typename NanoValueT>
util::enable_if_t<BuildTraits<NanoIndexT>::is_index && LEVEL != 0>
process(trait::OpenNodeT<NanoValueT, LEVEL>*, const NanoNodeT<NanoIndexT, LEVEL>*, const NanoValueT*);
template <typename NanoLeafT>
typename std::enable_if<!std::is_same<bool, typename NanoLeafT::BuildType>::value &&
!std::is_same<ValueMask, typename NanoLeafT::BuildType>::value &&
!std::is_same<Fp4, typename NanoLeafT::BuildType>::value &&
!std::is_same<Fp8, typename NanoLeafT::BuildType>::value &&
!std::is_same<Fp16,typename NanoLeafT::BuildType>::value &&
!std::is_same<FpN, typename NanoLeafT::BuildType>::value,
OpenNode0*>::type
process(const NanoLeafT* node);
template<int LEVEL, typename NanoBuildT>
util::disable_if_t<BuildTraits<NanoBuildT>::is_index || LEVEL != 0>
process(trait::OpenNodeT<NanoBuildT, LEVEL>*, const NanoNodeT<NanoBuildT, LEVEL>*);
template <typename NanoLeafT>
typename std::enable_if<std::is_same<Fp4, typename NanoLeafT::BuildType>::value ||
std::is_same<Fp8, typename NanoLeafT::BuildType>::value ||
std::is_same<Fp16,typename NanoLeafT::BuildType>::value ||
std::is_same<FpN, typename NanoLeafT::BuildType>::value,
OpenNode0*>::type
process(const NanoLeafT* node);
template <typename NanoLeafT>
typename std::enable_if<std::is_same<ValueMask, typename NanoLeafT::BuildType>::value,
OpenNode0*>::type
process(const NanoLeafT* node);
template <typename NanoLeafT>
typename std::enable_if<std::is_same<bool, typename NanoLeafT::BuildType>::value,
OpenNode0*>::type
process(const NanoLeafT* node);
/// converts nanovdb value types to openvdb value types, e.g. nanovdb::Vec3f& -> openvdb::Vec3f&
static const OpenValueT& Convert(const NanoValueT &v) {return reinterpret_cast<const OpenValueT&>(v);}
static const OpenValueT* Convert(const NanoValueT *v) {return reinterpret_cast<const OpenValueT*>(v);}
template<int LEVEL, typename NanoIndexT, typename NanoValueT>
util::enable_if_t<BuildTraits<NanoIndexT>::is_index && LEVEL == 0>
process(trait::OpenNodeT<NanoValueT, LEVEL>*, const NanoNodeT<NanoIndexT, LEVEL>*, const NanoValueT*);
}; // NanoToOpenVDB class
template<typename NanoBuildT>
typename NanoToOpenVDB<NanoBuildT>::OpenGridT::Ptr
NanoToOpenVDB<NanoBuildT>::operator()(const NanoGrid<NanoBuildT>& grid, int /*verbose*/)
{
// since the input nanovdb grid might use nanovdb types (Coord, Mask, Vec3) we cast to use openvdb types
const NanoGridT *srcGrid = reinterpret_cast<const NanoGridT*>(&grid);
// ================================================================================================
auto dstGrid = openvdb::createGrid<OpenGridT>(Convert(srcGrid->tree().background()));
dstGrid->setName(srcGrid->gridName()); // set grid name
switch (srcGrid->gridClass()) { // set grid class
case nanovdb::GridClass::LevelSet:
dstGrid->setGridClass(openvdb::GRID_LEVEL_SET);
break;
case nanovdb::GridClass::FogVolume:
dstGrid->setGridClass(openvdb::GRID_FOG_VOLUME);
break;
case nanovdb::GridClass::Staggered:
dstGrid->setGridClass(openvdb::GRID_STAGGERED);
break;
case nanovdb::GridClass::PointIndex:
throw std::runtime_error("NanoToOpenVDB does not yet support PointIndexGrids");
case nanovdb::GridClass::PointData:
throw std::runtime_error("NanoToOpenVDB does not yet support PointDataGrids");
default:
dstGrid->setGridClass(openvdb::GRID_UNKNOWN);
}
// set transform
const nanovdb::Map& nanoMap = reinterpret_cast<const GridData*>(srcGrid)->mMap;
auto mat = openvdb::math::Mat4<double>::identity();
template<typename NanoBuildT>
util::disable_if_t<BuildTraits<NanoBuildT>::is_index, trait::OpenGridPtr<NanoBuildT>>
NanoToOpenVDB::operator()(const NanoGrid<NanoBuildT>& srcGrid)
{
// Create an empty OpenVDB destination grid
auto dstGrid = openvdb::createGrid<trait::OpenGrid<NanoBuildT>>(*trait::mapPtr(&srcGrid.tree().background()));
dstGrid->setName(srcGrid.gridName()); // set grid name
dstGrid->setGridClass(trait::toOpenGridClass(srcGrid.gridClass()));
// set world to index transform
const Map& nanoMap = reinterpret_cast<const GridData&>(srcGrid).mMap;
auto mat = openvdb::math::Mat4<double>::identity();
mat.setMat3(openvdb::math::Mat3<double>(nanoMap.mMatD));
mat = mat.transpose(); // the 3x3 in nanovdb is transposed relative to openvdb's 3x3
mat.setTranslation(openvdb::math::Vec3<double>(nanoMap.mVecD));
dstGrid->setTransform(openvdb::math::Transform::createLinearTransform(mat)); // calls simplify!
// process root node
// process root node and recursively call its child inner nodes
auto &root = dstGrid->tree().root();
auto *data = srcGrid->tree().root().data();
auto *data = srcGrid.tree().root().data();
for (uint32_t i=0; i<data->mTableSize; ++i) {
auto *tile = data->tile(i);
if (tile->isChild()) {
root.addChild( this->process( data->getChild(tile)) );
auto *dstChild = new trait::OpenNodeT<NanoBuildT, 2>();// un-initialized
this->template process<2, NanoBuildT>( dstChild, data->getChild(tile) );
root.addChild( dstChild );
} else {
root.addTile(tile->origin(), Convert(tile->value), tile->state);
root.addTile(trait::mapCoord(tile->origin()), *trait::mapPtr(&tile->value), tile->state);
}
}
return dstGrid;
}
}// NanoToOpenVDB::operator()(const NanoGrid<NanoBuildT>& grid)
template<typename T>
template<typename SrcNodeT, typename DstNodeT>
DstNodeT*
NanoToOpenVDB<T>::processNode(const SrcNodeT *srcNode)
template<typename NanoIndexT, typename NanoValueT>
util::enable_if_t<BuildTraits<NanoIndexT>::is_index, trait::OpenGridPtr<NanoValueT>>
NanoToOpenVDB::operator()(const NanoGrid<NanoIndexT>& indexGrid,
const NanoValueT *sideCar,
GridClass gridClass,
const char *name)
{
DstNodeT *dstNode = new DstNodeT(); // un-initialized for fast construction
dstNode->setOrigin(srcNode->origin());
const auto& childMask = srcNode->childMask();
const_cast<typename DstNodeT::NodeMaskType&>(dstNode->getValueMask()) = srcNode->valueMask();
// Create an empty OpenVDB destination grid
auto dstGrid = openvdb::createGrid<trait::OpenGrid<NanoValueT>>(*trait::mapPtr(sideCar));//background is always the first value
if (name) dstGrid->setName(name); // set grid name
dstGrid->setGridClass(trait::toOpenGridClass(gridClass));
// set world to index transform
const Map& nanoMap = reinterpret_cast<const GridData&>(indexGrid).mMap;
auto mat = openvdb::math::Mat4<double>::identity();
mat.setMat3(openvdb::math::Mat3<double>(nanoMap.mMatD));
mat = mat.transpose(); // the 3x3 in nanovdb is transposed relative to openvdb's 3x3
mat.setTranslation(openvdb::math::Vec3<double>(nanoMap.mVecD));
dstGrid->setTransform(openvdb::math::Transform::createLinearTransform(mat)); // calls simplify!
// process root node and recursively call its child inner nodes
auto &root = dstGrid->tree().root();
auto *data = indexGrid.tree().root().data();
for (uint32_t i=0; i<data->mTableSize; ++i) {
auto *tile = data->tile(i);
if (tile->isChild()) {
auto *dstChild = new trait::OpenNodeT<NanoValueT, 2>();// un-initialized
this->template process<2, NanoIndexT, NanoValueT>( dstChild, data->getChild(tile), sideCar );
root.addChild( dstChild );
} else {
root.addTile(trait::mapCoord(tile->origin()), *trait::mapPtr(sideCar + tile->value), tile->state);
}
}
if constexpr(util::is_same_v<NanoIndexT, ValueOnIndex>) {
if (gridClass == GridClass::LevelSet) openvdb::tools::signedFloodFill(dstGrid->tree());
}
return dstGrid;
}// NanoToOpenVDB::operator()(const NanoGrid<ValueIndex>& grid, const NanoValueT*)
template<typename NanoIndexT>
util::enable_if_t<BuildTraits<NanoIndexT>::is_index, openvdb::GridBase::Ptr>
NanoToOpenVDB::operator()(const NanoGrid<NanoIndexT>& idxGrid, int blindDataID)
{
for (uint32_t i=0; i<idxGrid.blindDataCount(); ++i) {
if (blindDataID >= 0 && int(i) != blindDataID) continue;
const auto &metaData = idxGrid.blindMetaData(i);
if (metaData.mDataClass != GridBlindDataClass::ChannelArray ||
idxGrid.valueCount() != metaData.mValueCount) continue;
const GridClass gridClass = toGridClass(metaData.mSemantic);
switch (metaData.mDataType){
case GridType::Float:
NANOVDB_ASSERT(metaData.mValueSize == sizeof(float));
return (*this)(idxGrid, idxGrid.template getBlindData<float>(i), gridClass);
case GridType::Double:
NANOVDB_ASSERT(metaData.mValueSize == sizeof(double));
return (*this)(idxGrid, idxGrid.template getBlindData<double>(i), gridClass);
case GridType::Int32:
NANOVDB_ASSERT(metaData.mValueSize == sizeof(int32_t));
return (*this)(idxGrid, idxGrid.template getBlindData<int32_t>(i), gridClass);
case GridType::Vec3f:
NANOVDB_ASSERT(metaData.mValueSize == sizeof(Vec3f));
return (*this)(idxGrid, idxGrid.template getBlindData<Vec3f>(i), gridClass);
default:// required to avoid compiler warning
break;
}
}
OPENVDB_THROW(openvdb::RuntimeError, "No valid sidecar located in the blind data!");
}// NanoToOpenVDB::operator()(const NanoGrid<NanoIndexT>& idxGrid)
// ================================================================================================
template<int LEVEL, typename NanoBuildT>
util::disable_if_t<BuildTraits<NanoBuildT>::is_index || LEVEL == 0>
NanoToOpenVDB::process(trait::OpenNodeT<NanoBuildT, LEVEL> *dstNode,
const NanoNodeT<NanoBuildT, LEVEL> *srcNode)
{
using DstNodeT = trait::OpenNodeT<NanoBuildT, LEVEL>;
using SrcNodeT = NanoNodeT<NanoBuildT, LEVEL>;
dstNode->setOrigin(trait::mapCoord(srcNode->origin()));
const auto &childMask = trait::mapMask(srcNode->childMask());
const auto &valueMask = trait::mapMask(srcNode->valueMask());
const_cast<typename DstNodeT::NodeMaskType&>(dstNode->getValueMask()) = valueMask;
const_cast<typename DstNodeT::NodeMaskType&>(dstNode->getChildMask()) = childMask;
auto* dstTable = const_cast<typename DstNodeT::UnionType*>(dstNode->getTable());
auto* srcData = srcNode->data();
@@ -198,13 +336,15 @@ NanoToOpenVDB<T>::processNode(const SrcNodeT *srcNode)
if (childMask.isOn(n)) {
childNodes.emplace_back(n, srcData->getChild(n));
} else {
dstTable[n].setValue(Convert(srcData->mTable[n].value));
dstTable[n].setValue(*trait::mapPtr(&srcData->mTable[n].value));
}
}
auto kernel = [&](const auto& r) {
for (auto i = r.begin(); i != r.end(); ++i) {
auto &p = childNodes[i];
dstTable[p.first].setChild( this->process(p.second) );
auto* dstChild = new typename DstNodeT::ChildNodeType();// un-initialized
this->template process<LEVEL-1, NanoBuildT>(dstChild, p.second);
dstTable[p.first].setChild( dstChild );
}
};
@@ -213,129 +353,179 @@ NanoToOpenVDB<T>::processNode(const SrcNodeT *srcNode)
#else
util::forEach(0, childCount, 1, kernel);
#endif
return dstNode;
} // processNode
} // NanoToOpenVDB::process(const SrcNodeT *srcNode, DstNodeT *dstNode)
template<typename T>
template <typename NanoLeafT>
inline typename std::enable_if<!std::is_same<bool, typename NanoLeafT::BuildType>::value &&
!std::is_same<ValueMask, typename NanoLeafT::BuildType>::value &&
!std::is_same<Fp4, typename NanoLeafT::BuildType>::value &&
!std::is_same<Fp8, typename NanoLeafT::BuildType>::value &&
!std::is_same<Fp16,typename NanoLeafT::BuildType>::value &&
!std::is_same<FpN, typename NanoLeafT::BuildType>::value,
typename NanoToOpenVDB<T>::OpenNode0*>::type
NanoToOpenVDB<T>::process(const NanoLeafT *srcNode)
template<int LEVEL, typename NanoIndexT, typename NanoValueT>
util::enable_if_t<BuildTraits<NanoIndexT>::is_index && LEVEL != 0>
NanoToOpenVDB::process(trait::OpenNodeT<NanoValueT, LEVEL> *dstNode,
const NanoNodeT<NanoIndexT, LEVEL> *srcNode,
const NanoValueT* sideCar)
{
static_assert(std::is_same<NanoLeafT, NanoNode0>::value, "NanoToOpenVDB<FpN>::process assert failed");
OpenNode0* dstNode = new OpenNode0(); // un-initialized for fast construction
dstNode->setOrigin(srcNode->origin());
dstNode->setValueMask(srcNode->valueMask());
const auto* src = Convert(srcNode->data()->mValues);// doesn't work for compressed data, bool or ValueMask
for (auto *dst = dstNode->buffer().data(), *end = dst + OpenNode0::SIZE; dst != end; dst += 4, src += 4) {
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
using DstNodeT = trait::OpenNodeT<NanoValueT, LEVEL>;
using SrcNodeT = NanoNodeT<NanoIndexT, LEVEL>;
dstNode->setOrigin(trait::mapCoord(srcNode->origin()));
const auto &childMask = trait::mapMask(srcNode->childMask());
const auto &valueMask = trait::mapMask(srcNode->valueMask());
const_cast<typename DstNodeT::NodeMaskType&>(dstNode->getValueMask()) = valueMask;
const_cast<typename DstNodeT::NodeMaskType&>(dstNode->getChildMask()) = childMask;
auto* dstTable = const_cast<typename DstNodeT::UnionType*>(dstNode->getTable());
auto* srcData = srcNode->data();
std::vector<std::pair<uint32_t, const typename SrcNodeT::ChildNodeType*>> childNodes;
const auto childCount = childMask.countOn();
childNodes.reserve(childCount);
for (uint32_t n = 0; n < DstNodeT::NUM_VALUES; ++n) {
if (childMask.isOn(n)) {
childNodes.emplace_back(n, srcData->getChild(n));
} else {
dstTable[n].setValue(*trait::mapPtr(sideCar + srcData->mTable[n].value));
}
}
auto kernel = [&](const auto& r) {
for (auto i = r.begin(); i != r.end(); ++i) {
auto &p = childNodes[i];
auto *dstChild = new typename DstNodeT::ChildNodeType();// un-initialized
this->template process<LEVEL-1, NanoIndexT, NanoValueT>(dstChild, p.second, sideCar);
dstTable[p.first].setChild( dstChild );
}
};
return dstNode;
} // process(NanoNode0)
#if 0
kernel(Range1D(0, childCount));
#else
util::forEach(0, childCount, 1, kernel);
#endif
} // NanoToOpenVDB::process(const SrcNodeT* srcNode, const ValueT* sideCar)
template<typename T>
template <typename NanoLeafT>
inline typename std::enable_if<std::is_same<Fp4, typename NanoLeafT::BuildType>::value ||
std::is_same<Fp8, typename NanoLeafT::BuildType>::value ||
std::is_same<Fp16,typename NanoLeafT::BuildType>::value ||
std::is_same<FpN, typename NanoLeafT::BuildType>::value,
typename NanoToOpenVDB<T>::OpenNode0*>::type
NanoToOpenVDB<T>::process(const NanoLeafT *srcNode)
// ================================================================================================
template<int LEVEL, typename NanoBuildT>
util::disable_if_t<BuildTraits<NanoBuildT>::is_index || LEVEL != 0>
NanoToOpenVDB::process(trait::OpenNodeT<NanoBuildT, LEVEL> *dstLeaf,
const NanoNodeT<NanoBuildT, LEVEL> *srcLeaf)
{
static_assert(std::is_same<NanoLeafT, NanoNode0>::value, "NanoToOpenVDB<T>::process assert failed");
OpenNode0* dstNode = new OpenNode0(); // un-initialized for fast construction
dstNode->setOrigin(srcNode->origin());
dstNode->setValueMask(srcNode->valueMask());
float *dst = dstNode->buffer().data();
for (int i=0; i!=512; i+=4) {
*dst++ = srcNode->getValue(i);
*dst++ = srcNode->getValue(i+1);
*dst++ = srcNode->getValue(i+2);
*dst++ = srcNode->getValue(i+3);
dstLeaf->setOrigin(trait::mapCoord(srcLeaf->origin()));
dstLeaf->setValueMask(trait::mapMask(srcLeaf->valueMask()));
if constexpr(!BuildTraits<NanoBuildT>::is_special) {
const auto *src = trait::mapPtr(srcLeaf->data()->mValues);// doesn't work for compressed data, bool or ValueMask
#if 1
std::copy(src, src + 512, dstLeaf->buffer().data());
#else
for (auto *dst = dstLeaf->buffer().data(), *end = dst + 512; dst != end; dst += 4, src += 4) {
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
}
#endif
} else if constexpr(BuildTraits<NanoBuildT>::is_Fp) {
float *dst = dstLeaf->buffer().data();
for (int i=0; i!=512; i+=4) {
*dst++ = srcLeaf->getValue(i);
*dst++ = srcLeaf->getValue(i+1);
*dst++ = srcLeaf->getValue(i+2);
*dst++ = srcLeaf->getValue(i+3);
}
} else if constexpr(util::is_same_v<NanoBuildT, bool>) {
reinterpret_cast<openvdb::util::NodeMask<3>&>(dstLeaf->buffer()) = trait::mapMask(srcLeaf->data()->mValues);
} else if constexpr(!util::is_same_v<NanoBuildT, ValueMask>) {
OPENVDB_THROW(openvdb::RuntimeError, "Unsupported NanoBuildT in NanoToOpenVDB::process(NanoLeaf<NanoBuildT>)");
}
}// NanoToOpenVDB::process(const NanoLeaf<NanoBuildT>* srcLeaf)
return dstNode;
} // process(NanoNode0)
template<typename T>
template <typename NanoLeafT>
inline typename std::enable_if<std::is_same<ValueMask, typename NanoLeafT::BuildType>::value,
typename NanoToOpenVDB<T>::OpenNode0*>::type
NanoToOpenVDB<T>::process(const NanoLeafT *srcNode)
template<int LEVEL, typename NanoIndexT, typename NanoValueT>
util::enable_if_t<BuildTraits<NanoIndexT>::is_index && LEVEL == 0>
NanoToOpenVDB::process(trait::OpenNodeT<NanoValueT, LEVEL> *dstLeaf,
const NanoNodeT<NanoIndexT, LEVEL> *srcLeaf,
const NanoValueT *sideCar)
{
static_assert(std::is_same<NanoLeafT, NanoNode0>::value, "NanoToOpenVDB<ValueMask>::process assert failed");
OpenNode0* dstNode = new OpenNode0(); // un-initialized for fast construction
dstNode->setOrigin(srcNode->origin());
dstNode->setValueMask(srcNode->valueMask());
dstLeaf->setOrigin(trait::mapCoord(srcLeaf->origin()));
dstLeaf->setValueMask(trait::mapMask(srcLeaf->valueMask()));
return dstNode;
} // process(NanoNode0)
if constexpr(!BuildTraits<NanoValueT>::is_special) {
if constexpr(util::is_same_v<NanoIndexT, ValueIndex>) {
const auto *src = trait::mapPtr(sideCar + srcLeaf->data()->mOffset);
#if 1
std::copy(src, src + 512, dstLeaf->buffer().data());
#else
for (auto *dst = dstLeaf->buffer().data(), *end = dst + 512; dst != end; dst += 4, src += 4) {
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
}
#endif
} else {
const auto* src = trait::mapPtr(sideCar);
auto *dst = dstLeaf->buffer().data();
for (int i = 0; i < 512; i += 4) {
*dst++ = src[srcLeaf->getValue(i )];
*dst++ = src[srcLeaf->getValue(i+1)];
*dst++ = src[srcLeaf->getValue(i+2)];
*dst++ = src[srcLeaf->getValue(i+3)];
}
}
} else {
OPENVDB_THROW(openvdb::RuntimeError, "Unsupported NanoValueT in NanoToOpenVDB::process(NanoLeaf<NanoIndexT>, NanoValueT*)");
}
}// NanoToOpenVDB::process(const NanoLeaf<NanoIndexT>* srcLeaf, const NanoValueT *sideCar)
template<typename T>
template <typename NanoLeafT>
inline typename std::enable_if<std::is_same<bool, typename NanoLeafT::BuildType>::value,
typename NanoToOpenVDB<T>::OpenNode0*>::type
NanoToOpenVDB<T>::process(const NanoLeafT *srcNode)
{
static_assert(std::is_same<NanoLeafT, NanoNode0>::value, "NanoToOpenVDB<ValueMask>::process assert failed");
OpenNode0* dstNode = new OpenNode0(); // un-initialized for fast construction
dstNode->setOrigin(srcNode->origin());
dstNode->setValueMask(srcNode->valueMask());
reinterpret_cast<openvdb::util::NodeMask<3>&>(dstNode->buffer()) = srcNode->data()->mValues;
return dstNode;
} // process(NanoNode0)
// ================================================================================================
template<typename NanoBuildT>
inline typename openvdb::Grid<typename openvdb::tree::Tree4<typename ConvertTrait<NanoBuildT>::Type>::Type>::Ptr
nanoToOpenVDB(const NanoGrid<NanoBuildT>& grid, int verbose)
auto nanoToOpenVDB(const NanoGrid<NanoBuildT>& grid)
{
NanoToOpenVDB<NanoBuildT> tmp;
return tmp(grid, verbose);
NanoToOpenVDB tmp;
return tmp(grid);
}
template<typename NanoBuildT, typename NanoValueT>
util::enable_if_t<BuildTraits<NanoBuildT>::is_index, trait::OpenGridPtr<NanoValueT>>
nanoToOpenVDB(const NanoGrid<NanoBuildT>& grid,
const NanoValueT *sideCar,
GridClass gridClass,
const char *gridName)
{
NanoToOpenVDB tmp;
return tmp(grid, sideCar, gridClass, gridName);
}
template<typename BufferT>
openvdb::GridBase::Ptr
nanoToOpenVDB(const GridHandle<BufferT>& handle, int verbose, uint32_t n)
nanoToOpenVDB(const GridHandle<BufferT>& handle, uint32_t n)
{
if (auto grid = handle.template grid<float>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<double>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<int32_t>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<int64_t>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<bool>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
} else if (auto grid = handle.template grid<nanovdb::Fp4>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
} else if (auto grid = handle.template grid<nanovdb::Fp8>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
} else if (auto grid = handle.template grid<nanovdb::Fp16>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
} else if (auto grid = handle.template grid<nanovdb::FpN>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
} else if (auto grid = handle.template grid<nanovdb::ValueMask>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
} else if (auto grid = handle.template grid<nanovdb::Vec3f>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
} else if (auto grid = handle.template grid<nanovdb::Vec3d>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
} else if (auto grid = handle.template grid<nanovdb::Vec4f>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
} else if (auto grid = handle.template grid<nanovdb::Vec4d>(n)) {
return tools::nanoToOpenVDB(*grid, verbose);
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<Fp4>(n)) {
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<Fp8>(n)) {
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<Fp16>(n)) {
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<FpN>(n)) {
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<ValueMask>(n)) {
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<Vec3f>(n)) {
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<Vec3d>(n)) {
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<Vec4f>(n)) {
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<Vec4d>(n)) {
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<ValueOnIndex>(n)) {
return tools::nanoToOpenVDB(*grid);
} else if (auto grid = handle.template grid<ValueIndex>(n)) {
return tools::nanoToOpenVDB(*grid);
} else {
OPENVDB_THROW(openvdb::RuntimeError, "Unsupported NanoVDB grid type!");
}
@@ -346,19 +536,17 @@ nanoToOpenVDB(const GridHandle<BufferT>& handle, int verbose, uint32_t n)
/// @brief Forward declaration of free-standing function that de-serializes a typed NanoVDB grid into an OpenVDB Grid
template<typename NanoBuildT>
[[deprecated("Use nanovdb::tools::nanoToOpenVDB instead.")]]
typename openvdb::Grid<typename openvdb::tree::Tree4<typename ConvertTrait<NanoBuildT>::Type>::Type>::Ptr
nanoToOpenVDB(const NanoGrid<NanoBuildT>& grid, int verbose = 0)
tools::trait::OpenGridPtr<NanoBuildT> nanoToOpenVDB(const NanoGrid<NanoBuildT>& grid)
{
return tools::nanoToOpenVDB(grid, verbose);
return tools::nanoToOpenVDB(grid);
}
/// @brief Forward declaration of free-standing function that de-serializes a NanoVDB GridHandle into an OpenVDB GridBase
template<typename BufferT>
[[deprecated("Use nanovdb::tools::nanoToOpenVDB instead.")]]
openvdb::GridBase::Ptr
nanoToOpenVDB(const GridHandle<BufferT>& handle, int verbose = 0, uint32_t n = 0)
openvdb::GridBase::Ptr nanoToOpenVDB(const GridHandle<BufferT>& handle, uint32_t n = 0)
{
return tools::nanoToOpenVDB(handle, verbose, n);
return tools::nanoToOpenVDB(handle, n);
}
} // namespace nanovdb
+148
View File
@@ -0,0 +1,148 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
/*!
\file nanovdb/tools/VoxelBlockManager.h
\author Eftychios Sifakis
\date July 24, 2025
\brief Implements a handle class holding the metadata involved in invocations
of the VoxelBlockManager structure.
The VoxelBlockManager is an acceleration structure for sequential access
and stencil operations over solely the active voxels of an OnIndexGrid
which enables SIMT parallelism independent of occupancy.
*/
#ifndef NANOVDB_VOXELBLOCKMANAGER_H_HAS_BEEN_INCLUDED
#define NANOVDB_VOXELBLOCKMANAGER_H_HAS_BEEN_INCLUDED
#include <nanovdb/NanoVDB.h>
#include <nanovdb/util/cuda/Util.h>
namespace nanovdb {
namespace tools {
/// @brief This class serves to manage the raw memory buffers for the metadata of a NanoVDB VoxelBlockManager
template<typename BufferT>
class VoxelBlockManagerHandle
{
BufferT mFirstLeafID;
BufferT mJumpMap;
uint64_t mBlockCount{0};
uint64_t mFirstOffset{0};
uint64_t mLastOffset{0};
uint32_t mLowerCount{0};
public:
/// @brief Move constructor from metadata buffers
VoxelBlockManagerHandle(BufferT&& firstLeafID, BufferT&& jumpMap, const uint64_t blockCount,
const uint64_t firstOffset, const uint64_t lastOffset, const uint64_t lowerCount) {
mFirstLeafID = std::move(firstLeafID);
mJumpMap = std::move(jumpMap);
mBlockCount = blockCount;
mFirstOffset = firstOffset;
mLastOffset = lastOffset;
mLowerCount = lowerCount;
}
VoxelBlockManagerHandle() = default;
VoxelBlockManagerHandle(const VoxelBlockManagerHandle&) = delete;
VoxelBlockManagerHandle& operator=(const VoxelBlockManagerHandle&) = delete;
VoxelBlockManagerHandle& operator=(VoxelBlockManagerHandle&& other) noexcept {
mFirstLeafID = std::move(other.mFirstLeafID);
mJumpMap = std::move(other.mJumpMap);
mBlockCount = other.mblockCount;
mFirstOffset = other.mfirstOffset;
mLastOffset = other.mlastOffset;
mLowerCount = other.mlowerCount;
return *this;
}
VoxelBlockManagerHandle(VoxelBlockManagerHandle&& other) noexcept {
mFirstLeafID = std::move(other.mFirstLeafID);
mJumpMap = std::move(other.mJumpMap);
mBlockCount = other.mblockCount;
mFirstOffset = other.mfirstOffset;
mLastOffset = other.mlastOffset;
mLowerCount = other.mlowerCount;
}
~VoxelBlockManagerHandle() { this->reset(); }
/// @brief clear the buffer
void reset() { mFirstLeafID.clear(); mJumpMap.clear(); mBlockCount = 0; }
/// @brief Returns a non-const pointer to the firstLeafID device-hosted data
///
/// @warning Note that the return pointer can be NULL if the VoxelBlockManagerHandle was not initialized
template<typename U = BufferT>
typename util::enable_if<BufferTraits<U>::hasDeviceDual, uint32_t*>::type
deviceFirstLeafID() { return static_cast<uint32_t*>(mFirstLeafID.deviceData()); }
/// @brief Returns a const pointer to the firstLeafID device-hosted data
///
/// @warning Note that the return pointer can be NULL if the VoxelBlockManagerHandle was not initialized
template<typename U = BufferT>
typename util::enable_if<BufferTraits<U>::hasDeviceDual, const uint32_t*>::type
deviceFirstLeafID() const { return static_cast<const uint32_t*>(mFirstLeafID.deviceData()); }
/// @brief Returns a non-const pointer to the jumpMap device-hosted data
///
/// @warning Note that the return pointer can be NULL if the VoxelBlockManagerHandle was not initialized
template<typename U = BufferT>
typename util::enable_if<BufferTraits<U>::hasDeviceDual, uint64_t*>::type
deviceJumpMap() { return static_cast<uint64_t*>(mJumpMap.deviceData()); }
/// @brief Returns a const pointer to the jumpMap device-hosted data
///
/// @warning Note that the return pointer can be NULL if the VoxelBlockManagerHandle was not initialized
template<typename U = BufferT>
typename util::enable_if<BufferTraits<U>::hasDeviceDual, const uint64_t*>::type
deviceJumpMap() const { return static_cast<const uint64_t*>(mJumpMap.deviceData()); }
/// @brief Returns the number of voxel blocks in the VoxelBlockManager
uint64_t blockCount() const { return mBlockCount; }
/// @brief Sets the first and last voxel indices (linear offsets) associated with this VoxelBlockManager
void setOffsets(const uint64_t firstOffset, const uint64_t lastOffset) { mFirstOffset = firstOffset; mLastOffset = lastOffset; }
/// @brief Returns the first voxel index (linear offset) associated with this VoxelBlockManager
uint64_t firstOffset() const { return mFirstOffset; }
/// @brief Returns the last voxel index (linear offset) associated with this VoxelBlockManager
uint64_t lastOffset() const { return mLastOffset; }
/// @brief Sets the count of lower internal nodes in the IndexGrid associated with this VoxelBlockManager
void setLowerCount(const uint32_t lowerCount) { mLowerCount = lowerCount; }
/// @brief Returns the count of lower internal nodes in the IndexGrid associated with this VoxelBlockManager
uint32_t lowerCount() const { return mLowerCount; }
/// @brief Allocates new buffers for metadata, adequate for storing a VoxelBlockManager of the specified block size
template<int BlockWidthLog2, typename U = BufferT>
typename util::enable_if<BufferTraits<U>::hasDeviceDual, void>::type
deviceResize(const uint64_t blockCount, cudaStream_t stream = 0) {
static constexpr int BlockWidth = 1 << BlockWidthLog2;
reset();
int device = 0;
cudaCheck(cudaGetDevice(&device));
mBlockCount = blockCount;
mFirstLeafID = BufferT::create(blockCount*sizeof(uint32_t), nullptr, device, stream);
mJumpMap = BufferT::create(blockCount*(BlockWidth/8), nullptr, device, stream);
}
}; // VoxelBlockManagerHandle
} // namespace tools
} // namespace nanovdb
#if defined(__CUDACC__)
#include <nanovdb/tools/cuda/VoxelBlockManager.cuh>
#endif// defined(__CUDACC__)
#endif // NANOVDB_VOXELBLOCKMANAGER_H_HAS_BEEN_INCLUDED
+3 -3
View File
@@ -14,8 +14,8 @@
to only include it in .cu files (or other .cuh files)
*/
#ifndef NVIDIA_TOOLS_CUDA_ADDBLINDDATA_CUH_HAS_BEEN_INCLUDED
#define NVIDIA_TOOLS_CUDA_ADDBLINDDATA_CUH_HAS_BEEN_INCLUDED
#ifndef NANOVDB_TOOLS_CUDA_ADDBLINDDATA_CUH_HAS_BEEN_INCLUDED
#define NANOVDB_TOOLS_CUDA_ADDBLINDDATA_CUH_HAS_BEEN_INCLUDED
#include <nanovdb/NanoVDB.h>
#include <nanovdb/cuda/DeviceBuffer.h>
@@ -143,4 +143,4 @@ cudaAddBlindData(const NanoGrid<BuildT> *d_grid,
}// namespace nanovdb
#endif // NVIDIA_TOOLS_CUDA_ADDBLINDDATA_CUH_HAS_BEEN_INCLUDED
#endif // NANOVDB_TOOLS_CUDA_ADDBLINDDATA_CUH_HAS_BEEN_INCLUDED
+252
View File
@@ -0,0 +1,252 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
/*!
\file nanovdb/tools/cuda/CoarsenGrid.cuh
\authors Efty Sifakis
\brief 2x Topological coarsening of NanoVDB indexGrids on the device
\warning The header file contains cuda device code so be sure
to only include it in .cu files (or other .cuh files)
*/
#ifndef NVIDIA_TOOLS_CUDA_COARSENGRID_CUH_HAS_BEEN_INCLUDED
#define NVIDIA_TOOLS_CUDA_COARSENGRID_CUH_HAS_BEEN_INCLUDED
#include <cub/cub.cuh>
#include <nanovdb/NanoVDB.h>
#include <nanovdb/GridHandle.h>
#include <nanovdb/tools/cuda/TopologyBuilder.cuh>
#include <nanovdb/util/cuda/DeviceGridTraits.cuh>
#include <nanovdb/util/cuda/Morphology.cuh>
#include <nanovdb/util/cuda/Timer.h>
#include <nanovdb/util/cuda/Util.h>
namespace nanovdb {
namespace tools::cuda {
template <typename BuildT>
class CoarsenGrid
{
using GridT = NanoGrid<BuildT>;
using TreeT = NanoTree<BuildT>;
using RootT = NanoRoot<BuildT>;
using UpperT = NanoUpper<BuildT>;
public:
/// @brief Constructor
/// @param deviceGrid source device grid to be coarsened
/// @param stream optional CUDA stream (defaults to CUDA stream 0)
CoarsenGrid(const GridT* d_srcGrid, cudaStream_t stream = 0)
: mBuilder(stream), mStream(stream), mTimer(stream), mDeviceSrcGrid(d_srcGrid) {}
/// @brief Toggle on and off verbose mode
/// @param level Verbose level: 0=quiet, 1=timing, 2=benchmarking
void setVerbose(int level = 1) { mVerbose = level; }
/// @brief Set the mode for checksum computation, which is disabled by default
/// @param mode Mode of checksum computation
void setChecksum(CheckMode mode = CheckMode::Disable){mBuilder.mChecksum = mode;}
/// @brief Creates a handle to the coarsened grid
/// @tparam BufferT Buffer type used for allocation of the grid handle
/// @param buffer optional buffer (currently ignored)
/// @return returns a handle with a grid of type NanoGrid<BuildT>
template<typename BufferT = nanovdb::cuda::DeviceBuffer>
GridHandle<BufferT>
getHandle(const BufferT &buffer = BufferT());
private:
void coarsenRoot();
void coarsenInternalNodes();
void processGridTreeRoot();
void coarsenLeafNodes();
static constexpr unsigned int mNumThreads = 128;// for kernels spawned via lambdaKernel (others may specialize)
static unsigned int numBlocks(unsigned int n) {return (n + mNumThreads - 1) / mNumThreads;}
TopologyBuilder<BuildT> mBuilder;
cudaStream_t mStream{0};
util::cuda::Timer mTimer;
int mVerbose{0};
const GridT *mDeviceSrcGrid;
TreeData mSrcTreeData;
};// tools::cuda::CoarsenGrid<BuildT>
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
template<typename BufferT>
GridHandle<BufferT>
CoarsenGrid<BuildT>::getHandle(const BufferT &pool)
{
// Copy TreeData from GPU -> CPU
cudaStreamSynchronize(mStream);
mSrcTreeData = util::cuda::DeviceGridTraits<BuildT>::getTreeData(mDeviceSrcGrid);
// Ensure that the input grid contains no tile values
if (mSrcTreeData.mTileCount[2] || mSrcTreeData.mTileCount[1] || mSrcTreeData.mTileCount[0])
throw std::runtime_error("Topological operations not supported on grids with value tiles");
// Coarsen root node
if (mVerbose==1) mTimer.start("\nCoarsening root node");
coarsenRoot();
// Allocate memory for coarsened upper/lower masks
if (mVerbose==1) mTimer.restart("Allocating internal node mask buffers");
mBuilder.allocateInternalMaskBuffers(mStream);
// Coarsen masks of upper/lower nodes
if (mVerbose==1) mTimer.restart("Coarsening internal nodes");
coarsenInternalNodes();
// Enumerate tree nodes
if (mVerbose==1) mTimer.restart("Count coarsened tree nodes");
mBuilder.countNodes(mStream);
cudaStreamSynchronize(mStream);
// Allocate new device grid buffer for coarsened result
if (mVerbose==1) mTimer.restart("Allocating coarsened grid buffer");
auto buffer = mBuilder.getBuffer(pool, mStream);
// Process GridData/TreeData/RootData of coarsened result
if (mVerbose==1) mTimer.restart("Processing grid/tree/root");
processGridTreeRoot();
// Process upper nodes of coarsened result
if (mVerbose==1) mTimer.restart("Processing upper nodes");
mBuilder.processUpperNodes(mStream);
// Process lower nodes of coarsened result
if (mVerbose==1) mTimer.restart("Processing lower nodes");
mBuilder.processLowerNodes(mStream);
// Coarsen leaf node active masks into new topology
if (mVerbose==1) mTimer.restart("Coarsen leaf nodes");
coarsenLeafNodes();
// Process bounding boxes
if (mVerbose==1) mTimer.restart("Processing bounding boxes");
mBuilder.processBBox(mStream);
// Post-process Grid/Tree data
if (mVerbose==1) mTimer.restart("Post-processing grid/tree data");
mBuilder.postProcessGridTree(mStream);
if (mVerbose==1) mTimer.stop();
cudaStreamSynchronize(mStream);
return GridHandle<BufferT>(std::move(buffer));
}// CoarsenGrid<BuildT>::getHandle
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void CoarsenGrid<BuildT>::coarsenRoot()
{
// This method coarsens the root tiles, to accommodate for the overall downsamping operation.
int device = 0;
cudaGetDevice(&device);
std::map<uint64_t, typename RootT::DataType::Tile> coarsenedTiles;
// This encoding scheme mirrors the one used in PointsToGrid; note that it is different from Tile::key
auto coordToKey = [](const Coord &ijk)->uint64_t{
// Note: int32_t has a range of -2^31 to 2^31 - 1 whereas uint32_t has a range of 0 to 2^32 - 1
static constexpr int64_t kOffset = 1 << 31;
return (uint64_t(uint32_t(int64_t(ijk[2]) + kOffset) >> 12) ) | // z is the lower 21 bits
(uint64_t(uint32_t(int64_t(ijk[1]) + kOffset) >> 12) << 21) | // y is the middle 21 bits
(uint64_t(uint32_t(int64_t(ijk[0]) + kOffset) >> 12) << 42); // x is the upper 21 bits
};// coordToKey lambda functor
if (mSrcTreeData.mVoxelCount) { // If the input grid is not empty
// Make a host copy of the Root topology
auto deviceSrcRoot = static_cast<const RootT*>(util::PtrAdd(mDeviceSrcGrid, GridT::memUsage() + mSrcTreeData.mNodeOffset[3]));
uint64_t rootSize = mSrcTreeData.mNodeOffset[2] - mSrcTreeData.mNodeOffset[3];
auto srcRootBuffer = nanovdb::HostBuffer::create(rootSize);
cudaCheck(cudaMemcpyAsync(srcRootBuffer.data(), deviceSrcRoot, rootSize, cudaMemcpyDeviceToHost, mStream));
auto srcRoot = static_cast<RootT*>(srcRootBuffer.data());
// Add all root tiles, reordering if necessary
for (uint32_t t = 0; t < srcRoot->tileCount(); t++) {
auto coarsenedOrigin = util::morphology::coarsenCoord(srcRoot->tile(t)->origin());
auto sortKey = coordToKey(coarsenedOrigin); // key used in the radix sort, in accordance with PointsToGrid
auto tileKey = RootT::CoordToKey(coarsenedOrigin); // encoding used in the NanoVDB tile
typename RootT::Tile coarsenedTile{tileKey}; // Only the key value is needed; child pointer & value will be unused
coarsenedTiles.emplace(sortKey, coarsenedTile);
}
}
// Package the new root topology into a RootNode plus Tile list; upload to the GPU
uint64_t rootSize = RootT::memUsage(coarsenedTiles.size());
mBuilder.mProcessedRoot = nanovdb::cuda::DeviceBuffer::create(rootSize);
auto coarsenedRootPtr = static_cast<RootT*>(mBuilder.mProcessedRoot.data());
coarsenedRootPtr->mTableSize = coarsenedTiles.size();
uint32_t t = 0;
for (const auto& [key, tile] : coarsenedTiles)
*coarsenedRootPtr->tile(t++) = tile;
mBuilder.mProcessedRoot.deviceUpload(device, mStream, false);
}// CoarsenGrid<BuildT>::coarsenRoot
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void CoarsenGrid<BuildT>::coarsenInternalNodes()
{
// Computes the masks of upper and (densified) lower internal nodes, as a result of the coarsening operation
// Masks of lower internal nodes are densified in the sense that a serialized array of them is allocated,
// as if every upper node had a full set of 32^3 lower children
if (auto srcLeafCount = mSrcTreeData.mNodeCount[0]) { // Unless it's an empty grid
util::cuda::lambdaKernel<<<numBlocks(srcLeafCount), mNumThreads, 0, mStream>>>(
srcLeafCount, util::morphology::cuda::CoarsenInternalNodesFunctor<BuildT>(),
mDeviceSrcGrid, mBuilder.deviceProcessedRoot(), mBuilder.mUpperMasks.deviceData(), mBuilder.mLowerMasks.deviceData() );
}
}// CoarsenGrid<BuildT>::coarsenInternalNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template <typename BuildT>
void CoarsenGrid<BuildT>::processGridTreeRoot()
{
// Copy GridData from source grid
// By convention: this will duplicate grid name and map. Others will be reset later
cudaCheck(cudaMemcpyAsync(&mBuilder.data()->getGrid(), mDeviceSrcGrid->data(), GridT::memUsage(), cudaMemcpyDeviceToDevice, mStream));
util::cuda::lambdaKernel<<<1, 1, 0, mStream>>>(1, topology::detail::BuildGridTreeRootFunctor<BuildT>(), mBuilder.deviceData());
cudaCheckError();
}// CoarsenGrid<BuildT>::processGridTreeRoot
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void CoarsenGrid<BuildT>::coarsenLeafNodes()
{
// Coarsens the active masks of the source grid (as indicated at the leaf level), into a new grid that
// has been already topologically coarsened to include all necessary leaf nodes.
auto srcLeafCount = mSrcTreeData.mNodeCount[0];
if (srcLeafCount) { // Unless grid is empty
util::cuda::lambdaKernel<<<numBlocks(srcLeafCount), mNumThreads, 0, mStream>>>(
srcLeafCount, util::morphology::cuda::CoarsenLeafMasksFunctor<BuildT>(), mDeviceSrcGrid, &mBuilder.data()->getGrid());
}
// Update leaf offsets and prefix sums
mBuilder.processLeafOffsets(mStream);
}// CoarsenGrid<BuildT>::coarsenLeafNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
}// namespace tools::cuda
}// namespace nanovdb
#endif // NVIDIA_TOOLS_CUDA_COARSENGRID_CUH_HAS_BEEN_INCLUDED
+296
View File
@@ -0,0 +1,296 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
/*!
\file nanovdb/tools/cuda/DilateGrid.cuh
\authors Efty Sifakis
\brief Morphological dilation of NanoVDB indexGrids on the device
\warning The header file contains cuda device code so be sure
to only include it in .cu files (or other .cuh files)
*/
#ifndef NVIDIA_TOOLS_CUDA_DILATEGRID_CUH_HAS_BEEN_INCLUDED
#define NVIDIA_TOOLS_CUDA_DILATEGRID_CUH_HAS_BEEN_INCLUDED
#include <cub/cub.cuh>
#include <nanovdb/NanoVDB.h>
#include <nanovdb/GridHandle.h>
#include <nanovdb/tools/cuda/TopologyBuilder.cuh>
#include <nanovdb/util/cuda/DeviceGridTraits.cuh>
#include <nanovdb/util/cuda/Morphology.cuh>
#include <nanovdb/util/cuda/Timer.h>
#include <nanovdb/util/cuda/Util.h>
namespace nanovdb {
namespace tools::cuda {
template <typename BuildT>
class DilateGrid
{
using GridT = NanoGrid<BuildT>;
using TreeT = NanoTree<BuildT>;
using RootT = NanoRoot<BuildT>;
using UpperT = NanoUpper<BuildT>;
public:
/// @brief Constructor
/// @param deviceGrid source device grid to be dilated
/// @param stream optional CUDA stream (defaults to CUDA stream 0)
DilateGrid(const GridT* d_srcGrid, cudaStream_t stream = 0)
: mBuilder(stream), mStream(stream), mTimer(stream), mDeviceSrcGrid(d_srcGrid) {}
/// @brief Toggle on and off verbose mode
/// @param level Verbose level: 0=quiet, 1=timing, 2=benchmarking
void setVerbose(int level = 1) { mVerbose = level; }
/// @brief Set the mode for checksum computation, which is disabled by default
/// @param mode Mode of checksum computation
void setChecksum(CheckMode mode = CheckMode::Disable){mBuilder.mChecksum = mode;}
/// @brief Set type of dilation operation
/// @param op: NN_FACE=face neighbors, NN_FACE_EDGE=face and edge neibhros, NN_FACE_EDGE_VERTEX=26-connected neighbors
void setOperation(morphology::NearestNeighbors op) { mOp = op; }
/// @brief Creates a handle to the dilated grid
/// @tparam BufferT Buffer type used for allocation of the grid handle
/// @param buffer optional buffer (currently ignored)
/// @return returns a handle with a grid of type NanoGrid<BuildT>
template<typename BufferT = nanovdb::cuda::DeviceBuffer>
GridHandle<BufferT>
getHandle(const BufferT &buffer = BufferT());
private:
void dilateRoot();
void dilateInternalNodes();
void processGridTreeRoot();
void dilateLeafNodes();
static constexpr unsigned int mNumThreads = 128;// for kernels spawned via lambdaKernel (others may specialize)
static unsigned int numBlocks(unsigned int n) {return (n + mNumThreads - 1) / mNumThreads;}
TopologyBuilder<BuildT> mBuilder;
cudaStream_t mStream{0};
util::cuda::Timer mTimer;
int mVerbose{0};
const GridT *mDeviceSrcGrid;
morphology::NearestNeighbors mOp{morphology::NN_FACE_EDGE_VERTEX};
TreeData mSrcTreeData;
};// tools::cuda::DilateGrid<BuildT>
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
template<typename BufferT>
GridHandle<BufferT>
DilateGrid<BuildT>::getHandle(const BufferT &pool)
{
// Copy TreeData from GPU -> CPU
cudaStreamSynchronize(mStream);
mSrcTreeData = util::cuda::DeviceGridTraits<BuildT>::getTreeData(mDeviceSrcGrid);
// Ensure that the input grid contains no tile values
if (mSrcTreeData.mTileCount[2] || mSrcTreeData.mTileCount[1] || mSrcTreeData.mTileCount[0])
throw std::runtime_error("Topological operations not supported on grids with value tiles");
// Speculatively dilate root node
if (mVerbose==1) mTimer.start("\nDilating root node");
dilateRoot();
// Allocate memory for dilated upper/lower masks
if (mVerbose==1) mTimer.restart("Allocating internal node mask buffers");
mBuilder.allocateInternalMaskBuffers(mStream);
// Dilate masks of upper/lower nodes
if (mVerbose==1) mTimer.restart("Dilate internal nodes");
dilateInternalNodes();
// Enumerate tree nodes
if (mVerbose==1) mTimer.restart("Count dilated tree nodes");
mBuilder.countNodes(mStream);
cudaStreamSynchronize(mStream);
// Allocate new device grid buffer for dilated result
if (mVerbose==1) mTimer.restart("Allocating dilated grid buffer");
auto buffer = mBuilder.getBuffer(pool, mStream);
// Process GridData/TreeData/RootData of dilated result
if (mVerbose==1) mTimer.restart("Processing grid/tree/root");
processGridTreeRoot();
// Process upper nodes of dilated result
if (mVerbose==1) mTimer.restart("Processing upper nodes");
mBuilder.processUpperNodes(mStream);
// Process lower nodes of dilated result
if (mVerbose==1) mTimer.restart("Processing lower nodes");
mBuilder.processLowerNodes(mStream);
// Dilate leaf node active masks into new topology
if (mVerbose==1) mTimer.restart("Dilating leaf nodes");
dilateLeafNodes();
// Process bounding boxes
if (mVerbose==1) mTimer.restart("Processing bounding boxes");
mBuilder.processBBox(mStream);
// Post-process Grid/Tree data
if (mVerbose==1) mTimer.restart("Post-processing grid/tree data");
mBuilder.postProcessGridTree(mStream);
if (mVerbose==1) mTimer.stop();
cudaStreamSynchronize(mStream);
return GridHandle<BufferT>(std::move(buffer));
}// DilateGrid<BuildT>::getHandle
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void DilateGrid<BuildT>::dilateRoot()
{
// This method conservatively and speculatively dilates the root tiles, to accommodate
// any new root nodes that might be introduced by the dilation operation.
// The index-space bounding box of each tile is examined, and if it is within a 1-pixel of
// intersecting any of the 26-connected neighboring root tiles, those are preemptively
// introduced into the root topology.
// (As of the present implementation this presumes a maximum of 1-voxel radius in dilation)
// Root tiles that were preemptively introduced, but end up having no active contents will
// be pruned in later stages of processing.
int device = 0;
cudaGetDevice(&device);
std::map<uint64_t, typename RootT::DataType::Tile> dilatedTiles;
// This encoding scheme mirrors the one used in PointsToGrid; note that it is different from Tile::key
auto coordToKey = [](const Coord &ijk)->uint64_t{
// Note: int32_t has a range of -2^31 to 2^31 - 1 whereas uint32_t has a range of 0 to 2^32 - 1
static constexpr int64_t kOffset = 1 << 31;
return (uint64_t(uint32_t(int64_t(ijk[2]) + kOffset) >> 12) ) | // z is the lower 21 bits
(uint64_t(uint32_t(int64_t(ijk[1]) + kOffset) >> 12) << 21) | // y is the middle 21 bits
(uint64_t(uint32_t(int64_t(ijk[0]) + kOffset) >> 12) << 42); // x is the upper 21 bits
};// coordToKey lambda functor
if (mSrcTreeData.mVoxelCount) { // If the input grid is not empty
// Make a host copy of the source topology RootNode *and* the Upper Nodes (needed for BBox'es)
// TODO: Consider avoiding to copy the entire set of upper nodes
auto deviceSrcRoot = static_cast<const RootT*>(util::PtrAdd(mDeviceSrcGrid, GridT::memUsage() + mSrcTreeData.mNodeOffset[3]));
uint64_t rootAndUpperSize = mSrcTreeData.mNodeOffset[1] - mSrcTreeData.mNodeOffset[3];
auto srcRootAndUpperBuffer = nanovdb::HostBuffer::create(rootAndUpperSize);
cudaCheck(cudaMemcpyAsync(srcRootAndUpperBuffer.data(), deviceSrcRoot, rootAndUpperSize, cudaMemcpyDeviceToHost, mStream));
auto srcRootAndUpper = static_cast<RootT*>(srcRootAndUpperBuffer.data());
// For each original root tile, consider adding those tiles in its 26-connected neighborhood
for (uint32_t t = 0; t < srcRootAndUpper->tileCount(); t++) {
auto srcUpper = srcRootAndUpper->getChild(srcRootAndUpper->tile(t));
const auto dilatedBBox = srcUpper->bbox().expandBy(1); // TODO: update/specialize if larger dilation neighborhoods are used
static constexpr int32_t rootTileDim = UpperT::DIM; // 4096
for (int di = -rootTileDim; di <= rootTileDim; di += rootTileDim)
for (int dj = -rootTileDim; dj <= rootTileDim; dj += rootTileDim)
for (int dk = -rootTileDim; dk <= rootTileDim; dk += rootTileDim) {
auto testBBox = nanovdb::CoordBBox::createCube(srcUpper->origin().offsetBy(di,dj,dk), rootTileDim);
auto sortKey = coordToKey(testBBox.min()); // key used in the radix sort, in accordance with PointsToGrid
auto tileKey = RootT::CoordToKey(testBBox.min()); // encoding used in the NanoVDB tile
if (testBBox.hasOverlap(dilatedBBox) & (dilatedTiles.count(sortKey) == 0)) {
typename RootT::Tile neighborTile{tileKey}; // Only the key value is needed; child pointer & value will be unused
dilatedTiles.emplace(sortKey, neighborTile);
}
}
}
}
// Package the new root topology into a RootNode plus Tile list; upload to the GPU
uint64_t rootSize = RootT::memUsage(dilatedTiles.size());
mBuilder.mProcessedRoot = nanovdb::cuda::DeviceBuffer::create(rootSize);
auto dilatedRootPtr = static_cast<RootT*>(mBuilder.mProcessedRoot.data());
dilatedRootPtr->mTableSize = dilatedTiles.size();
uint32_t t = 0;
for (const auto& [key, tile] : dilatedTiles)
*dilatedRootPtr->tile(t++) = tile;
mBuilder.mProcessedRoot.deviceUpload(device, mStream, false);
}// DilateGrid<BuildT>::dilateRoot
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void DilateGrid<BuildT>::dilateInternalNodes()
{
// Computes the masks of upper and (densified) lower internal nodes, as a result of the dilation operation
// Masks of lower internal nodes are densified in the sense that a serialized array of them is allocated,
// as if every upper node had a full set of 32^3 lower children
if (mSrcTreeData.mNodeCount[1]) { // Unless it's an empty grid
if (mOp == morphology::NN_FACE) {
using Op = util::morphology::cuda::DilateInternalNodesFunctor<BuildT, morphology::NN_FACE>;
util::cuda::operatorKernel<Op>
<<<dim3(mSrcTreeData.mNodeCount[1],Op::SlicesPerLowerNode,1), Op::MaxThreadsPerBlock, 0, mStream>>>
(mDeviceSrcGrid, mBuilder.deviceProcessedRoot(), mBuilder.deviceUpperMasks(), mBuilder.deviceLowerMasks()); }
else if (mOp == morphology::NN_FACE_EDGE) {
using Op = util::morphology::cuda::DilateInternalNodesFunctor<BuildT, morphology::NN_FACE_EDGE>;
util::cuda::operatorKernel<Op>
<<<dim3(mSrcTreeData.mNodeCount[1],Op::SlicesPerLowerNode,1), Op::MaxThreadsPerBlock, 0, mStream>>>
(mDeviceSrcGrid, mBuilder.deviceProcessedRoot(), mBuilder.deviceUpperMasks(), mBuilder.deviceLowerMasks()); }
else if (mOp == morphology::NN_FACE_EDGE_VERTEX) {
using Op = util::morphology::cuda::DilateInternalNodesFunctor<BuildT, morphology::NN_FACE_EDGE_VERTEX>;
util::cuda::operatorKernel<Op>
<<<dim3(mSrcTreeData.mNodeCount[1],Op::SlicesPerLowerNode,1), Op::MaxThreadsPerBlock, 0, mStream>>>
(mDeviceSrcGrid, mBuilder.deviceProcessedRoot(), mBuilder.deviceUpperMasks(), mBuilder.deviceLowerMasks()); }
}
}// DilateGrid<BuildT>::dilateInternalNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template <typename BuildT>
void DilateGrid<BuildT>::processGridTreeRoot()
{
// Copy GridData from source grid
// By convention: this will duplicate grid name and map. Others will be reset later
cudaCheck(cudaMemcpyAsync(&mBuilder.data()->getGrid(), mDeviceSrcGrid->data(), GridT::memUsage(), cudaMemcpyDeviceToDevice, mStream));
util::cuda::lambdaKernel<<<1, 1, 0, mStream>>>(1, topology::detail::BuildGridTreeRootFunctor<BuildT>(), mBuilder.deviceData());
cudaCheckError();
}// DilateGrid<BuildT>::processGridTreeRoot
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void DilateGrid<BuildT>::dilateLeafNodes()
{
// Dilates the active masks of the source grid (as indicated at the leaf level), into a new grid that
// has been already topologically dilated to include all necessary leaf nodes.
if (mBuilder.data()->nodeCount[1]) { // Unless output grid is empty
if (mOp == morphology::NN_FACE) {
using Op = util::morphology::cuda::DilateLeafNodesFunctor<BuildT, morphology::NN_FACE>;
util::cuda::operatorKernel<Op>
<<<dim3(mBuilder.data()->nodeCount[1],Op::SlicesPerLowerNode,1), Op::MaxThreadsPerBlock, 0, mStream>>>
(mDeviceSrcGrid, static_cast<GridT*>(mBuilder.data()->d_bufferPtr)); }
else if (mOp == morphology::NN_FACE_EDGE)
throw std::runtime_error("dilateLeafNodes() not implemented for NN_FACE_EDGE stencil");
else if (mOp == morphology::NN_FACE_EDGE_VERTEX) {
using Op = util::morphology::cuda::DilateLeafNodesFunctor<BuildT, morphology::NN_FACE_EDGE_VERTEX>;
util::cuda::operatorKernel<Op>
<<<dim3(mBuilder.data()->nodeCount[1],Op::SlicesPerLowerNode,1), Op::MaxThreadsPerBlock>>>
(mDeviceSrcGrid, static_cast<GridT*>(mBuilder.data()->d_bufferPtr)); }
}
// Update leaf offsets and prefix sums
mBuilder.processLeafOffsets(mStream);
}// DilateGrid<BuildT>::dilateLeafNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
}// namespace tools::cuda
}// namespace nanovdb
#endif // NVIDIA_TOOLS_CUDA_DILATEGRID_CUH_HAS_BEEN_INCLUDED
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -72,7 +72,7 @@ inline void updateChecksum(GridData *d_gridData, cudaStream_t stream = 0)
namespace util::cuda {
/// @brief Cuda kernel that computes CRC32 checksums of blocks of data using a look-up-table
/// @param d_data device pointer to raw data from wich to compute the CRC32 checksums
/// @param d_data device pointer to raw data from which to compute the CRC32 checksums
/// @param d_blockCRC device pointer to array of @c blockCount checksums for each block
/// @param blockCount number of blocks and checksums
/// @param blockSize size of each block in bytes
@@ -85,7 +85,7 @@ __global__ void crc32Kernel(const T *d_data, uint32_t* d_blockCRC, uint32_t bloc
}
/// @brief Cuda kernel that computes CRC32 checksums of blocks of data (without using a look-up-table)
/// @param d_data device pointer to raw data from wich to compute the CRC32 checksums
/// @param d_data device pointer to raw data from which to compute the CRC32 checksums
/// @param d_blockCRC device pointer to array of @c blockCount checksums for each block
/// @param blockCount number of blocks and checksums
/// @param blockSize size of each block in bytes
@@ -374,7 +374,7 @@ inline Checksum getChecksum(const GridData *d_gridData, cudaStream_t stream)
/// @param stream optional cuda stream (defaults to zero)
/// @return The actual mode used for checksum computation. Eg. if @c d_gridData is NULL (or @c mode = CheckMode::Empty)
/// then CheckMode::Empty is always returned. Else if the grid has no nodes or blind data CheckMode::Partial
/// is always returnd (even if @c mode = CheckMode::Full).
/// is always returned (even if @c mode = CheckMode::Full).
inline void updateChecksum(GridData *d_gridData, CheckMode mode, cudaStream_t stream)
{
NANOVDB_ASSERT(d_gridData);
+8 -9
View File
@@ -32,7 +32,7 @@ void updateGridStats(NanoGrid<BuildT> *d_grid, StatsMode mode = StatsMode::Defau
//================================================================================================
/// @brief Allows for the construction of NanoVDB grids without any dependecy
/// @brief Allows for the construction of NanoVDB grids without any dependency
template<typename BuildT, typename StatsT = Stats<typename NanoGrid<BuildT>::ValueType>>
class GridStats
{
@@ -84,20 +84,19 @@ template<typename BuildT, typename StatsT, int LEVEL>
__global__ void processInternal(NodeManager<BuildT> *d_nodeMgr, StatsT *d_stats)
{
using ChildT = typename NanoNode<BuildT,LEVEL-1>::type;
const uint32_t tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid >= d_nodeMgr->nodeCount(LEVEL)) return;
auto &d_node = d_nodeMgr->template node<LEVEL>(tid);
uint32_t nodeID = blockIdx.x * blockDim.x + threadIdx.x;// thread id (reused below to avoid compiler warning)
if (nodeID >= d_nodeMgr->nodeCount(LEVEL)) return;
auto &d_node = d_nodeMgr->template node<LEVEL>(nodeID);
auto &bbox = d_node.mBBox;
bbox = CoordBBox();// empty bbox
StatsT stats;
uint32_t childID = 0u;
for (auto it = d_node.beginChild(); it; ++it) {
auto &child = *it;
bbox.expand( child.bbox() );
if constexpr(StatsT::hasAverage()) {
childID = *reinterpret_cast<uint32_t*>(&child.mMinimum);
StatsT &s = d_stats[childID];
nodeID = *reinterpret_cast<uint32_t*>(&child.mMinimum);
StatsT &s = d_stats[nodeID];
s.setStats(child);
stats.add(s);
} else if constexpr(StatsT::hasMinMax()) {
@@ -112,8 +111,8 @@ __global__ void processInternal(NodeManager<BuildT> *d_nodeMgr, StatsT *d_stats)
if constexpr(StatsT::hasStats()) stats.add(*it, ChildT::NUM_VALUES);
}
if constexpr(StatsT::hasAverage()) {
d_stats[childID] = stats;
*reinterpret_cast<uint32_t*>(&d_node.mMinimum) = childID;
d_stats[nodeID] = stats;
*reinterpret_cast<uint32_t*>(&d_node.mMinimum) = nodeID;
} else if constexpr(StatsT::hasMinMax()) {
stats.setStats(d_node);
}
+5 -5
View File
@@ -14,8 +14,8 @@
to only include it in .cu files (or other .cuh files)
*/
#ifndef NVIDIA_TOOLS_CUDA_INDEXTOGRID_CUH_HAS_BEEN_INCLUDED
#define NVIDIA_TOOLS_CUDA_INDEXTOGRID_CUH_HAS_BEEN_INCLUDED
#ifndef NANOVDB_TOOLS_CUDA_INDEXTOGRID_CUH_HAS_BEEN_INCLUDED
#define NANOVDB_TOOLS_CUDA_INDEXTOGRID_CUH_HAS_BEEN_INCLUDED
#include <nanovdb/NanoVDB.h>
#include <nanovdb/cuda/DeviceBuffer.h>
@@ -31,7 +31,7 @@ namespace tools::cuda {// ======================================================
/// @tparam DstBuildT Build time of the destination/output Grid
/// @tparam SrcBuildT Build type of the source/input IndexGrid
/// @tparam BufferT Type of the buffer used for allocation of the destination Grid
/// @param d_srcGrid Device pointer to source/input IndexGrid, i.e. SrcBuildT={ValueIndex,ValueOnIndex,ValueIndexMask,ValueOnIndexMask}
/// @param d_srcGrid Device pointer to source/input IndexGrid, i.e. SrcBuildT={ValueIndex,ValueOnIndex}
/// @param d_srcValues Device pointer to an array of values
/// @param pool Memory pool used to create a buffer for the destination/output Grid
/// @param stream optional CUDA stream (defaults to CUDA stream 0
@@ -233,7 +233,6 @@ __global__ void processNodesKernel(typename IndexToGrid<SrcBuildT>::NodeAccessor
if (srcGrid.hasStdDeviation()) dstNode.mStdDevi = srcValues[srcNode.mStdDevi];
}
}
const uint64_t nodeSkip = nodeAcc->nodeCount[LEVEL] - blockIdx.x, srcOff = sizeof(SrcNodeT)*nodeSkip, dstOff = sizeof(DstNodeT)*nodeSkip;// offset to first node of child type
const int off = blockDim.x*blockDim.y*threadIdx.x + blockDim.x*threadIdx.y;
for (int threadIdx_z=0; threadIdx_z<blockDim.x; ++threadIdx_z) {
const int i = off + threadIdx_z;
@@ -241,6 +240,7 @@ __global__ void processNodesKernel(typename IndexToGrid<SrcBuildT>::NodeAccessor
if constexpr(sizeof(SrcNodeT)==sizeof(DstNodeT) && sizeof(SrcChildT)==sizeof(DstChildT)) {
dstNode.mTable[i].child = srcNode.mTable[i].child;
} else {
const uint64_t nodeSkip = nodeAcc->nodeCount[LEVEL] - blockIdx.x, srcOff = sizeof(SrcNodeT)*nodeSkip, dstOff = sizeof(DstNodeT)*nodeSkip;// offset to first node of child type
const uint64_t childID = (srcNode.mTable[i].child - srcOff)/sizeof(SrcChildT);
dstNode.mTable[i].child = dstOff + childID*sizeof(DstChildT);
}
@@ -415,4 +415,4 @@ cudaCreateNanoGrid(const NanoGrid<SrcBuildT> *d_srcGrid, const typename BuildToV
}// nanovdb namespace ===================================================================
#endif // NVIDIA_TOOLS_CUDA_INDEXTOGRID_CUH_HAS_BEEN_INCLUDED
#endif // NANOVDB_TOOLS_CUDA_INDEXTOGRID_CUH_HAS_BEEN_INCLUDED
+284
View File
@@ -0,0 +1,284 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
/*!
\file nanovdb/tools/cuda/MergeGrids.cuh
\authors Efty Sifakis
\brief Morphological union of NanoVDB indexGrids on the device
\warning The header file contains cuda device code so be sure
to only include it in .cu files (or other .cuh files)
*/
#ifndef NVIDIA_TOOLS_CUDA_MERGEGRIDS_CUH_HAS_BEEN_INCLUDED
#define NVIDIA_TOOLS_CUDA_MERGEGRIDS_CUH_HAS_BEEN_INCLUDED
#include <cub/cub.cuh>
#include <nanovdb/NanoVDB.h>
#include <nanovdb/GridHandle.h>
#include <nanovdb/tools/cuda/TopologyBuilder.cuh>
#include <nanovdb/util/cuda/DeviceGridTraits.cuh>
#include <nanovdb/util/cuda/Morphology.cuh>
#include <nanovdb/util/cuda/Timer.h>
#include <nanovdb/util/cuda/Util.h>
namespace nanovdb {
namespace tools::cuda {
template <typename BuildT>
class MergeGrids
{
using GridT = NanoGrid<BuildT>;
using TreeT = NanoTree<BuildT>;
using RootT = NanoRoot<BuildT>;
using UpperT = NanoUpper<BuildT>;
public:
/// @brief Constructor
/// @param d_srcGrid1 first source device grid to be merged
/// @param d_srcGrid2 second source device grid to be merged
/// @param stream optional CUDA stream (defaults to CUDA stream 0)
MergeGrids(const GridT* d_srcGrid1, const GridT* d_srcGrid2, cudaStream_t stream = 0)
: mBuilder(stream), mStream(stream), mTimer(stream), mDeviceSrcGrid1(d_srcGrid1), mDeviceSrcGrid2(d_srcGrid2) {}
/// @brief Toggle on and off verbose mode
/// @param level Verbose level: 0=quiet, 1=timing, 2=benchmarking
void setVerbose(int level = 1) { mVerbose = level; }
/// @brief Set the mode for checksum computation, which is disabled by default
/// @param mode Mode of checksum computation
void setChecksum(CheckMode mode = CheckMode::Disable){mBuilder.mChecksum = mode;}
/// @brief Creates a handle to the merged grid
/// @tparam BufferT Buffer type used for allocation of the grid handle
/// @param buffer optional buffer (currently ignored)
/// @return returns a handle with a grid of type NanoGrid<BuildT>
template<typename BufferT = nanovdb::cuda::DeviceBuffer>
GridHandle<BufferT>
getHandle(const BufferT &buffer = BufferT());
private:
void mergeRoot();
void mergeInternalNodes();
void processGridTreeRoot();
void mergeLeafNodes();
static constexpr unsigned int mNumThreads = 128;// for kernels spawned via lambdaKernel (others may specialize)
static unsigned int numBlocks(unsigned int n) {return (n + mNumThreads - 1) / mNumThreads;}
TopologyBuilder<BuildT> mBuilder;
cudaStream_t mStream{0};
util::cuda::Timer mTimer;
int mVerbose{0};
const GridT *mDeviceSrcGrid1;
const GridT *mDeviceSrcGrid2;
TreeData mSrcTreeData1;
TreeData mSrcTreeData2;
};// tools::cuda::MergeGrids<BuildT>
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
template<typename BufferT>
GridHandle<BufferT>
MergeGrids<BuildT>::getHandle(const BufferT &pool)
{
// Copy TreeData from GPU -> CPU
cudaStreamSynchronize(mStream);
mSrcTreeData1 = util::cuda::DeviceGridTraits<BuildT>::getTreeData(mDeviceSrcGrid1);
mSrcTreeData2 = util::cuda::DeviceGridTraits<BuildT>::getTreeData(mDeviceSrcGrid2);
// Ensure that the input grid contains no tile values
if (mSrcTreeData1.mTileCount[2] || mSrcTreeData1.mTileCount[1] || mSrcTreeData1.mTileCount[0] ||
mSrcTreeData2.mTileCount[2] || mSrcTreeData2.mTileCount[1] || mSrcTreeData2.mTileCount[0])
throw std::runtime_error("Topological operations not supported on grids with value tiles");
// Merge root nodes
if (mVerbose==1) mTimer.start("\nMerging root nodes");
mergeRoot();
// Allocate memory for merged upper/lower masks
if (mVerbose==1) mTimer.restart("Allocating internal node mask buffers");
mBuilder.allocateInternalMaskBuffers(mStream);
// Merge masks of upper/lower nodes
if (mVerbose==1) mTimer.restart("Merge internal nodes");
mergeInternalNodes();
// Enumerate tree nodes
if (mVerbose==1) mTimer.restart("Count merged tree nodes");
mBuilder.countNodes(mStream);
cudaStreamSynchronize(mStream);
// Allocate new device grid buffer for merged result
if (mVerbose==1) mTimer.restart("Allocating merged grid buffer");
auto buffer = mBuilder.getBuffer(pool, mStream);
// Process GridData/TreeData/RootData of merged result
if (mVerbose==1) mTimer.restart("Processing grid/tree/root");
processGridTreeRoot();
// Process upper nodes of merged result
if (mVerbose==1) mTimer.restart("Processing upper nodes");
mBuilder.processUpperNodes(mStream);
// Process lower nodes of merged result
if (mVerbose==1) mTimer.restart("Processing lower nodes");
mBuilder.processLowerNodes(mStream);
// Merge leaf node active masks into new topology
if (mVerbose==1) mTimer.restart("Merging leaf nodes");
mergeLeafNodes();
// Process bounding boxes
if (mVerbose==1) mTimer.restart("Processing bounding boxes");
mBuilder.processBBox(mStream);
// Post-process Grid/Tree data
if (mVerbose==1) mTimer.restart("Post-processing grid/tree data");
mBuilder.postProcessGridTree(mStream);
if (mVerbose==1) mTimer.stop();
cudaStreamSynchronize(mStream);
return GridHandle<BufferT>(std::move(buffer));
}// MergeGrids<BuildT>::getHandle
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void MergeGrids<BuildT>::mergeRoot()
{
// Creates a new merged tree root with the merged tiles of the two input root topologies
int device = 0;
cudaGetDevice(&device);
std::map<uint64_t, typename RootT::DataType::Tile> mergedTiles;
// This encoding scheme mirrors the one used in PointsToGrid; note that it is different from Tile::key
auto coordToKey = [](const Coord &ijk)->uint64_t{
// Note: int32_t has a range of -2^31 to 2^31 - 1 whereas uint32_t has a range of 0 to 2^32 - 1
static constexpr int64_t kOffset = 1 << 31;
return (uint64_t(uint32_t(int64_t(ijk[2]) + kOffset) >> 12) ) | // z is the lower 21 bits
(uint64_t(uint32_t(int64_t(ijk[1]) + kOffset) >> 12) << 21) | // y is the middle 21 bits
(uint64_t(uint32_t(int64_t(ijk[0]) + kOffset) >> 12) << 42); // x is the upper 21 bits
};// coordToKey lambda functor
// Make a host copy of the source root topology RootNode for both inputs
// Then, merge tiles of two sources in a sorted container
if (mSrcTreeData1.mVoxelCount) { // If the first input is not a null grid
// Make a host copy of the Root topology
auto deviceSrcRoot1 = static_cast<const RootT*>(util::PtrAdd(mDeviceSrcGrid1, GridT::memUsage() + mSrcTreeData1.mNodeOffset[3]));
uint64_t rootSize1 = mSrcTreeData1.mNodeOffset[2] - mSrcTreeData1.mNodeOffset[3];
auto srcRootBuffer1 = nanovdb::HostBuffer::create(rootSize1);
cudaCheck(cudaMemcpyAsync(srcRootBuffer1.data(), deviceSrcRoot1, rootSize1, cudaMemcpyDeviceToHost, mStream));
auto srcRoot1 = static_cast<RootT*>(srcRootBuffer1.data());
// Add all root tiles, reordering if necessary
for (uint32_t t = 0; t < srcRoot1->tileCount(); t++) {
auto tile = srcRoot1->tile(t);
auto sortKey = coordToKey(tile->origin());
mergedTiles.emplace(sortKey, *tile);
}
}
if (mSrcTreeData2.mVoxelCount) { // If the first input is not a null grid
// Make a host copy of the Root topology
auto deviceSrcRoot2 = static_cast<const RootT*>(util::PtrAdd(mDeviceSrcGrid2, GridT::memUsage() + mSrcTreeData2.mNodeOffset[3]));
uint64_t rootSize2 = mSrcTreeData2.mNodeOffset[2] - mSrcTreeData2.mNodeOffset[3];
auto srcRootBuffer2 = nanovdb::HostBuffer::create(rootSize2);
cudaCheck(cudaMemcpyAsync(srcRootBuffer2.data(), deviceSrcRoot2, rootSize2, cudaMemcpyDeviceToHost, mStream));
auto srcRoot2 = static_cast<RootT*>(srcRootBuffer2.data());
// Add all root tiles, reordering if necessary
for (uint32_t t = 0; t < srcRoot2->tileCount(); t++) {
auto tile = srcRoot2->tile(t);
auto sortKey = coordToKey(tile->origin());
mergedTiles.emplace(sortKey, *tile);
}
}
// Package the new root topology into a RootNode plus Tile list; upload to the GPU
uint64_t rootSize = RootT::memUsage(mergedTiles.size());
mBuilder.mProcessedRoot = nanovdb::cuda::DeviceBuffer::create(rootSize);
auto mergedRootPtr = static_cast<RootT*>(mBuilder.mProcessedRoot.data());
mergedRootPtr->mTableSize = mergedTiles.size();
uint32_t t = 0;
for (const auto& [key, tile] : mergedTiles)
*mergedRootPtr->tile(t++) = tile;
mBuilder.mProcessedRoot.deviceUpload(device, mStream, false);
}// MergeGrids<BuildT>::mergeRoot
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void MergeGrids<BuildT>::mergeInternalNodes()
{
// Merges the masks of upper and lower nodes from both input topologies into the
// densified, pre-allocated mask arrays of the merged result
using Op = util::morphology::cuda::MergeInternalNodesFunctor<BuildT>;
if (mSrcTreeData1.mNodeCount[1]) { // Unless the first grid to merge is empty
util::cuda::operatorKernel<Op>
<<<mSrcTreeData1.mNodeCount[1], Op::MaxThreadsPerBlock, 0, mStream>>>
(mDeviceSrcGrid1, mBuilder.deviceProcessedRoot(), mBuilder.deviceUpperMasks(), mBuilder.deviceLowerMasks());
}
if (mSrcTreeData2.mNodeCount[1]) { // Unless the second grid to merge is empty
util::cuda::operatorKernel<Op>
<<<mSrcTreeData2.mNodeCount[1], Op::MaxThreadsPerBlock, 0, mStream>>>
(mDeviceSrcGrid2, mBuilder.deviceProcessedRoot(), mBuilder.deviceUpperMasks(), mBuilder.deviceLowerMasks());
}
}// MergeGrids<BuildT>::mergeInternalNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template <typename BuildT>
void MergeGrids<BuildT>::processGridTreeRoot()
{
// Copy GridData from first source grid
// TODO: Check for instances where extra processing is needed
// TODO: check that the second grid input has consistent GridData, too
cudaCheck(cudaMemcpyAsync(&mBuilder.data()->getGrid(), mDeviceSrcGrid1->data(), GridT::memUsage(), cudaMemcpyDeviceToDevice, mStream));
util::cuda::lambdaKernel<<<1, 1, 0, mStream>>>(1, topology::detail::BuildGridTreeRootFunctor<BuildT>(), mBuilder.deviceData());
cudaCheckError();
}// MergeGrids<BuildT>::processGridTreeRoot
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void MergeGrids<BuildT>::mergeLeafNodes()
{
using Op = util::morphology::cuda::MergeLeafNodesFunctor<BuildT>;
if (mSrcTreeData1.mNodeCount[1]) { // Unless first input grid is empty
util::cuda::operatorKernel<Op>
<<<dim3(mSrcTreeData1.mNodeCount[1],Op::SlicesPerLowerNode,1), Op::MaxThreadsPerBlock, 0, mStream>>>
(mDeviceSrcGrid1, static_cast<GridT*>(mBuilder.data()->d_bufferPtr));
}
if (mSrcTreeData2.mNodeCount[1]) { // Unless second input grid is empty
util::cuda::operatorKernel<Op>
<<<dim3(mSrcTreeData2.mNodeCount[1],Op::SlicesPerLowerNode,1), Op::MaxThreadsPerBlock, 0, mStream>>>
(mDeviceSrcGrid2, static_cast<GridT*>(mBuilder.data()->d_bufferPtr));
}
// Update leaf offsets and prefix sums
mBuilder.processLeafOffsets(mStream);
}// MergeGrids<BuildT>::mergeLeafNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
}// namespace tools::cuda
}// namespace nanovdb
#endif // NVIDIA_TOOLS_CUDA_MERGEGRIDS_CUH_HAS_BEEN_INCLUDED
File diff suppressed because it is too large Load Diff
+255
View File
@@ -0,0 +1,255 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
/*!
\file nanovdb/tools/cuda/PruneGrid.cuh
\authors Efty Sifakis
\brief Morphological pruning of NanoVDB indexGrids using leaf-indexed mask
\warning The header file contains cuda device code so be sure
to only include it in .cu files (or other .cuh files)
*/
#ifndef NVIDIA_TOOLS_CUDA_PRUNEGRID_CUH_HAS_BEEN_INCLUDED
#define NVIDIA_TOOLS_CUDA_PRUNEGRID_CUH_HAS_BEEN_INCLUDED
#include <cub/cub.cuh>
#include <nanovdb/NanoVDB.h>
#include <nanovdb/GridHandle.h>
#include <nanovdb/tools/cuda/TopologyBuilder.cuh>
#include <nanovdb/util/cuda/DeviceGridTraits.cuh>
#include <nanovdb/util/cuda/Morphology.cuh>
#include <nanovdb/util/cuda/Timer.h>
#include <nanovdb/util/cuda/Util.h>
namespace nanovdb {
namespace tools::cuda {
template <typename BuildT>
class PruneGrid
{
using GridT = NanoGrid<BuildT>;
using TreeT = NanoTree<BuildT>;
using RootT = NanoRoot<BuildT>;
using UpperT = NanoUpper<BuildT>;
public:
/// @brief Constructor
/// @param d_srcGrid source device grid to be pruned
/// @param d_srcLeafMask sidecar array of leaf masks for voxels to retain
/// @param stream optional CUDA stream (defaults to CUDA stream 0)
PruneGrid(const GridT* d_srcGrid, const Mask<3>* d_srcLeafMask, cudaStream_t stream = 0)
: mBuilder(stream), mStream(stream), mTimer(stream), mDeviceSrcGrid(d_srcGrid), mDeviceSrcLeafMask(d_srcLeafMask) {}
/// @brief Toggle on and off verbose mode
/// @param level Verbose level: 0=quiet, 1=timing, 2=benchmarking
void setVerbose(int level = 1) { mVerbose = level; }
/// @brief Set the mode for checksum computation, which is disabled by default
/// @param mode Mode of checksum computation
void setChecksum(CheckMode mode = CheckMode::Disable){mBuilder.mChecksum = mode;}
/// @brief Creates a handle to the pruned grid
/// @tparam BufferT Buffer type used for allocation of the grid handle
/// @param buffer optional buffer (currently ignored)
/// @return returns a handle with a grid of type NanoGrid<BuildT>
template<typename BufferT = nanovdb::cuda::DeviceBuffer>
GridHandle<BufferT>
getHandle(const BufferT &buffer = BufferT());
private:
void pruneRoot();
void pruneInternalNodes();
void processGridTreeRoot();
void pruneLeafNodes();
static constexpr unsigned int mNumThreads = 128;// for kernels spawned via lambdaKernel (others may specialize)
static unsigned int numBlocks(unsigned int n) {return (n + mNumThreads - 1) / mNumThreads;}
TopologyBuilder<BuildT> mBuilder;
cudaStream_t mStream{0};
util::cuda::Timer mTimer;
int mVerbose{0};
const GridT *mDeviceSrcGrid;
const Mask<3> *mDeviceSrcLeafMask;
TreeData mSrcTreeData;
};// tools::cuda::PruneGrid<BuildT>
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
template<typename BufferT>
GridHandle<BufferT>
PruneGrid<BuildT>::getHandle(const BufferT &pool)
{
// Copy TreeData from GPU -> CPU
cudaStreamSynchronize(mStream);
mSrcTreeData = util::cuda::DeviceGridTraits<BuildT>::getTreeData(mDeviceSrcGrid);
// Ensure that the input grid contains no tile values
if (mSrcTreeData.mTileCount[2] || mSrcTreeData.mTileCount[1] || mSrcTreeData.mTileCount[0])
throw std::runtime_error("Topological operations not supported on grids with value tiles");
// Speculatively prune root node
if (mVerbose==1) mTimer.start("\nPrune root node");
pruneRoot();
// Allocate memory for pruned upper/lower masks
if (mVerbose==1) mTimer.restart("Allocating internal node mask buffers");
mBuilder.allocateInternalMaskBuffers(mStream);
// Prune masks of upper/lower nodes
if (mVerbose==1) mTimer.restart("Prune internal nodes");
pruneInternalNodes();
// Enumerate tree nodes
if (mVerbose==1) mTimer.restart("Count pruned tree nodes");
mBuilder.countNodes(mStream);
cudaStreamSynchronize(mStream);
// Allocate new device grid buffer for pruned result
if (mVerbose==1) mTimer.restart("Allocating pruned grid buffer");
auto buffer = mBuilder.getBuffer(pool, mStream);
// Process GridData/TreeData/RootData of pruned result
if (mVerbose==1) mTimer.restart("Processing grid/tree/root");
processGridTreeRoot();
// Process upper nodes of pruned result
if (mVerbose==1) mTimer.restart("Processing upper nodes");
mBuilder.processUpperNodes(mStream);
// Process lower nodes of pruned result
if (mVerbose==1) mTimer.restart("Processing lower nodes");
mBuilder.processLowerNodes(mStream);
// Prune active masks of leaf nodes and rebuild offsets
if (mVerbose==1) mTimer.restart("Pruning leaf nodes");
pruneLeafNodes();
// Process bounding boxes
if (mVerbose==1) mTimer.restart("Processing bounding boxes");
mBuilder.processBBox(mStream);
// Post-process Grid/Tree data
if (mVerbose==1) mTimer.restart("Post-processing grid/tree data");
mBuilder.postProcessGridTree(mStream);
if (mVerbose==1) mTimer.stop();
cudaStreamSynchronize(mStream);
return GridHandle<BufferT>(std::move(buffer));
}// PruneGrid<BuildT>::getHandle
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void PruneGrid<BuildT>::pruneRoot()
{
// This method conservatively (and trivially) prunes the root tile table.
// For this simple approximation, it is assumed that all root tiles currently present will presist,
// and they will be pruned at a later stage if deemed empty.
int device = 0;
cudaGetDevice(&device);
std::map<uint64_t, typename RootT::DataType::Tile> prunedTiles;
// This encoding scheme mirrors the one used in PointsToGrid; note that it is different from Tile::key
auto coordToKey = [](const Coord &ijk)->uint64_t{
// Note: int32_t has a range of -2^31 to 2^31 - 1 whereas uint32_t has a range of 0 to 2^32 - 1
static constexpr int64_t kOffset = 1 << 31;
return (uint64_t(uint32_t(int64_t(ijk[2]) + kOffset) >> 12) ) | // z is the lower 21 bits
(uint64_t(uint32_t(int64_t(ijk[1]) + kOffset) >> 12) << 21) | // y is the middle 21 bits
(uint64_t(uint32_t(int64_t(ijk[0]) + kOffset) >> 12) << 42); // x is the upper 21 bits
};// coordToKey lambda functor
if (mSrcTreeData.mVoxelCount) { // If the input grid is not empty
// Make a host copy of the source topology RootNode
auto deviceSrcRoot = static_cast<const RootT*>(util::PtrAdd(mDeviceSrcGrid, GridT::memUsage() + mSrcTreeData.mNodeOffset[3]));
uint64_t rootSize = mSrcTreeData.mNodeOffset[2] - mSrcTreeData.mNodeOffset[3];
auto srcRootBuffer = nanovdb::HostBuffer::create(rootSize);
cudaCheck(cudaMemcpyAsync(srcRootBuffer.data(), deviceSrcRoot, rootSize, cudaMemcpyDeviceToHost, mStream));
auto srcRoot = static_cast<RootT*>(srcRootBuffer.data());
// Add all root tiles, reordering if necessary
for (uint32_t t = 0; t < srcRoot->tileCount(); t++) {
auto tile = srcRoot->tile(t);
auto sortKey = coordToKey(tile->origin());
prunedTiles.emplace(sortKey, *tile);
}
}
// Package the duplicated root topology into a RootNode plus Tile list; upload to the GPU
uint64_t rootSize = RootT::memUsage(prunedTiles.size());
mBuilder.mProcessedRoot = nanovdb::cuda::DeviceBuffer::create(rootSize);
auto prunedRootPtr = static_cast<RootT*>(mBuilder.mProcessedRoot.data());
prunedRootPtr->mTableSize = prunedTiles.size();
uint32_t t = 0;
for (const auto& [key, tile] : prunedTiles)
*prunedRootPtr->tile(t++) = tile;
mBuilder.mProcessedRoot.deviceUpload(device, mStream, false);
}// PruneGrid<BuildT>::pruneRoot
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void PruneGrid<BuildT>::pruneInternalNodes()
{
// Computes the masks of upper and (densified) lower internal nodes, as a result of the pruning operation
// Masks of lower internal nodes are densified in the sense that a serialized array of them is allocated,
// as if every upper node had a full set of 32^3 lower children
if (auto srcLeafCount = mSrcTreeData.mNodeCount[0]) { // Unless it's an empty grid
util::cuda::lambdaKernel<<<numBlocks(srcLeafCount), mNumThreads, 0, mStream>>>(
srcLeafCount, util::morphology::cuda::PruneInternalNodesFunctor<BuildT>(),
mDeviceSrcGrid, mBuilder.deviceProcessedRoot(), mDeviceSrcLeafMask, mBuilder.mUpperMasks.deviceData(), mBuilder.mLowerMasks.deviceData() );
}
}// PruneGrid<BuildT>::pruneInternalNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template <typename BuildT>
void PruneGrid<BuildT>::processGridTreeRoot()
{
// Copy GridData from source grid
// By convention: this will duplicate grid name and map. Others will be reset later
cudaCheck(cudaMemcpyAsync(&mBuilder.data()->getGrid(), mDeviceSrcGrid->data(), GridT::memUsage(), cudaMemcpyDeviceToDevice, mStream));
util::cuda::lambdaKernel<<<1, 1, 0, mStream>>>(1, topology::detail::BuildGridTreeRootFunctor<BuildT>(), mBuilder.deviceData());
cudaCheckError();
}// PruneGrid<BuildT>::processGridTreeRoot
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void PruneGrid<BuildT>::pruneLeafNodes()
{
// Prunes the active masks of the source grid to the intersection with the leaf-mask sidecar
// followed by rebuilding the leaf offsets
auto srcLeafCount = mSrcTreeData.mNodeCount[0];
if (srcLeafCount) { // Unless grid is empty
util::cuda::lambdaKernel<<<numBlocks(srcLeafCount), mNumThreads, 0, mStream>>>(
srcLeafCount, util::morphology::cuda::PruneLeafMasksFunctor<BuildT>(), mDeviceSrcGrid, &mBuilder.data()->getGrid(), mDeviceSrcLeafMask);
}
// Update leaf offsets and prefix sums
mBuilder.processLeafOffsets(mStream);
}// PruneGrid<BuildT>::pruneLeafNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
}// namespace tools::cuda
}// namespace nanovdb
#endif // NVIDIA_TOOLS_CUDA_PRUNEGRID_CUH_HAS_BEEN_INCLUDED
+267
View File
@@ -0,0 +1,267 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
/*!
\file nanovdb/tools/cuda/RefineGrid.cuh
\authors Efty Sifakis
\brief 2x Topological refinement of NanoVDB indexGrids on the device
\warning The header file contains cuda device code so be sure
to only include it in .cu files (or other .cuh files)
*/
#ifndef NVIDIA_TOOLS_CUDA_REFINEGRID_CUH_HAS_BEEN_INCLUDED
#define NVIDIA_TOOLS_CUDA_REFINEGRID_CUH_HAS_BEEN_INCLUDED
#include <cub/cub.cuh>
#include <nanovdb/NanoVDB.h>
#include <nanovdb/GridHandle.h>
#include <nanovdb/tools/cuda/TopologyBuilder.cuh>
#include <nanovdb/util/cuda/DeviceGridTraits.cuh>
#include <nanovdb/util/cuda/Morphology.cuh>
#include <nanovdb/util/cuda/Timer.h>
#include <nanovdb/util/cuda/Util.h>
namespace nanovdb {
namespace tools::cuda {
template <typename BuildT>
class RefineGrid
{
using GridT = NanoGrid<BuildT>;
using TreeT = NanoTree<BuildT>;
using RootT = NanoRoot<BuildT>;
using UpperT = NanoUpper<BuildT>;
public:
/// @brief Constructor
/// @param deviceGrid source device grid to be refined
/// @param stream optional CUDA stream (defaults to CUDA stream 0)
RefineGrid(const GridT* d_srcGrid, cudaStream_t stream = 0)
: mBuilder(stream), mStream(stream), mTimer(stream), mDeviceSrcGrid(d_srcGrid) {}
/// @brief Toggle on and off verbose mode
/// @param level Verbose level: 0=quiet, 1=timing, 2=benchmarking
void setVerbose(int level = 1) { mVerbose = level; }
/// @brief Set the mode for checksum computation, which is disabled by default
/// @param mode Mode of checksum computation
void setChecksum(CheckMode mode = CheckMode::Disable){mBuilder.mChecksum = mode;}
/// @brief Creates a handle to the refined grid
/// @tparam BufferT Buffer type used for allocation of the grid handle
/// @param buffer optional buffer (currently ignored)
/// @return returns a handle with a grid of type NanoGrid<BuildT>
template<typename BufferT = nanovdb::cuda::DeviceBuffer>
GridHandle<BufferT>
getHandle(const BufferT &buffer = BufferT());
private:
void refineRoot();
void refineInternalNodes();
void processGridTreeRoot();
void refineLeafNodes();
static constexpr unsigned int mNumThreads = 128;// for kernels spawned via lambdaKernel (others may specialize)
static unsigned int numBlocks(unsigned int n) {return (n + mNumThreads - 1) / mNumThreads;}
TopologyBuilder<BuildT> mBuilder;
cudaStream_t mStream{0};
util::cuda::Timer mTimer;
int mVerbose{0};
const GridT *mDeviceSrcGrid;
TreeData mSrcTreeData;
};// tools::cuda::RefineGrid<BuildT>
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
template<typename BufferT>
GridHandle<BufferT>
RefineGrid<BuildT>::getHandle(const BufferT &pool)
{
// Copy TreeData from GPU -> CPU
cudaStreamSynchronize(mStream);
mSrcTreeData = util::cuda::DeviceGridTraits<BuildT>::getTreeData(mDeviceSrcGrid);
// Ensure that the input grid contains no tile values
if (mSrcTreeData.mTileCount[2] || mSrcTreeData.mTileCount[1] || mSrcTreeData.mTileCount[0])
throw std::runtime_error("Topological operations not supported on grids with value tiles");
// Speculatively refine root node
if (mVerbose==1) mTimer.start("\nRefining root node");
refineRoot();
// Allocate memory for refined upper/lower masks
if (mVerbose==1) mTimer.restart("Allocating internal node mask buffers");
mBuilder.allocateInternalMaskBuffers(mStream);
// Refine masks of upper/lower nodes
if (mVerbose==1) mTimer.restart("Refining internal nodes");
refineInternalNodes();
// Enumerate tree nodes
if (mVerbose==1) mTimer.restart("Count refined tree nodes");
mBuilder.countNodes(mStream);
cudaStreamSynchronize(mStream);
// Allocate new device grid buffer for refined result
if (mVerbose==1) mTimer.restart("Allocating refined grid buffer");
auto buffer = mBuilder.getBuffer(pool, mStream);
// Process GridData/TreeData/RootData of refined result
if (mVerbose==1) mTimer.restart("Processing grid/tree/root");
processGridTreeRoot();
// Process upper nodes of refined result
if (mVerbose==1) mTimer.restart("Processing upper nodes");
mBuilder.processUpperNodes(mStream);
// Process lower nodes of refined result
if (mVerbose==1) mTimer.restart("Processing lower nodes");
mBuilder.processLowerNodes(mStream);
// Refine leaf node active masks into new topology
if (mVerbose==1) mTimer.restart("Refining leaf nodes");
refineLeafNodes();
// Process bounding boxes
if (mVerbose==1) mTimer.restart("Processing bounding boxes");
mBuilder.processBBox(mStream);
// Post-process Grid/Tree data
if (mVerbose==1) mTimer.restart("Post-processing grid/tree data");
mBuilder.postProcessGridTree(mStream);
if (mVerbose==1) mTimer.stop();
cudaStreamSynchronize(mStream);
return GridHandle<BufferT>(std::move(buffer));
}// RefineGrid<BuildT>::getHandle
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void RefineGrid<BuildT>::refineRoot()
{
// This method conservatively and speculatively refines the root tiles, to accommodate
// any new root nodes that might be introduced by the upsampling operation.
// The index-space bounding box of each tile is examined, and if it overlaps any of the 2048^3-sized octants
// of the tile, a corresponding new tile is preemptively introduced into the root topology.
// Root tiles that were preemptively introduced, but end up having no active contents will
// be pruned in later stages of processing.
int device = 0;
cudaGetDevice(&device);
std::map<uint64_t, typename RootT::DataType::Tile> refinedTiles;
// This encoding scheme mirrors the one used in PointsToGrid; note that it is different from Tile::key
auto coordToKey = [](const Coord &ijk)->uint64_t{
// Note: int32_t has a range of -2^31 to 2^31 - 1 whereas uint32_t has a range of 0 to 2^32 - 1
static constexpr int64_t kOffset = 1 << 31;
return (uint64_t(uint32_t(int64_t(ijk[2]) + kOffset) >> 12) ) | // z is the lower 21 bits
(uint64_t(uint32_t(int64_t(ijk[1]) + kOffset) >> 12) << 21) | // y is the middle 21 bits
(uint64_t(uint32_t(int64_t(ijk[0]) + kOffset) >> 12) << 42); // x is the upper 21 bits
};// coordToKey lambda functor
if (mSrcTreeData.mVoxelCount) { // If the input grid is not empty
// Make a host copy of the source topology RootNode *and* the Upper Nodes (needed for BBox'es)
// TODO: Consider avoiding to copy the entire set of upper nodes
auto deviceSrcRoot = static_cast<const RootT*>(util::PtrAdd(mDeviceSrcGrid, GridT::memUsage() + mSrcTreeData.mNodeOffset[3]));
uint64_t rootAndUpperSize = mSrcTreeData.mNodeOffset[1] - mSrcTreeData.mNodeOffset[3];
auto srcRootAndUpperBuffer = nanovdb::HostBuffer::create(rootAndUpperSize);
cudaCheck(cudaMemcpyAsync(srcRootAndUpperBuffer.data(), deviceSrcRoot, rootAndUpperSize, cudaMemcpyDeviceToHost, mStream));
auto srcRootAndUpper = static_cast<RootT*>(srcRootAndUpperBuffer.data());
// For each original root tile, consider adding those tiles in its 26-connected neighborhood
for (uint32_t t = 0; t < srcRootAndUpper->tileCount(); t++) {
auto srcUpper = srcRootAndUpper->getChild(srcRootAndUpper->tile(t));
const auto tileBBox = srcUpper->bbox();
for (int di = 0; di <= 2048; di += 2048)
for (int dj = 0; dj <= 2048; dj += 2048)
for (int dk = 0; dk <= 2048; dk += 2048) {
const auto octantBBox = nanovdb::CoordBBox::createCube(srcUpper->origin().offsetBy(di,dj,dk), 2048);
if (tileBBox.hasOverlap(octantBBox)) {
auto refinedOrigin = octantBBox.min()+octantBBox.min();
auto sortKey = coordToKey(refinedOrigin); // key used in the radix sort, in accordance with PointsToGrid
auto tileKey = RootT::CoordToKey(refinedOrigin); // encoding used in the NanoVDB tile
typename RootT::Tile refinedTile{tileKey}; // Only the key value is needed; child pointer & value will be unused
refinedTiles.emplace(sortKey, refinedTile);
}
}
}
}
// Package the new root topology into a RootNode plus Tile list; upload to the GPU
uint64_t rootSize = RootT::memUsage(refinedTiles.size());
mBuilder.mProcessedRoot = nanovdb::cuda::DeviceBuffer::create(rootSize);
auto refinedRootPtr = static_cast<RootT*>(mBuilder.mProcessedRoot.data());
refinedRootPtr->mTableSize = refinedTiles.size();
uint32_t t = 0;
for (const auto& [key, tile] : refinedTiles)
*refinedRootPtr->tile(t++) = tile;
mBuilder.mProcessedRoot.deviceUpload(device, mStream, false);
}// RefineGrid<BuildT>::refineRoot
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void RefineGrid<BuildT>::refineInternalNodes()
{
// Computes the masks of upper and (densified) lower internal nodes, as a result of the refinement operation
// Masks of lower internal nodes are densified in the sense that a serialized array of them is allocated,
// as if every upper node had a full set of 32^3 lower children
if (auto srcLeafCount = mSrcTreeData.mNodeCount[0]) { // Unless it's an empty grid
util::cuda::lambdaKernel<<<numBlocks(srcLeafCount), mNumThreads, 0, mStream>>>(
srcLeafCount, util::morphology::cuda::RefineInternalNodesFunctor<BuildT>(),
mDeviceSrcGrid, mBuilder.deviceProcessedRoot(), mBuilder.mUpperMasks.deviceData(), mBuilder.mLowerMasks.deviceData() );
}
}// RefineGrid<BuildT>::refineInternalNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template <typename BuildT>
void RefineGrid<BuildT>::processGridTreeRoot()
{
// Copy GridData from source grid
// By convention: this will duplicate grid name and map. Others will be reset later
cudaCheck(cudaMemcpyAsync(&mBuilder.data()->getGrid(), mDeviceSrcGrid->data(), GridT::memUsage(), cudaMemcpyDeviceToDevice, mStream));
util::cuda::lambdaKernel<<<1, 1, 0, mStream>>>(1, topology::detail::BuildGridTreeRootFunctor<BuildT>(), mBuilder.deviceData());
cudaCheckError();
}// RefineGrid<BuildT>::processGridTreeRoot
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void RefineGrid<BuildT>::refineLeafNodes()
{
// Refines the active masks of the source grid (as indicated at the leaf level), into a new grid that
// has been already topologically refined to include all necessary leaf nodes.
auto srcLeafCount = mSrcTreeData.mNodeCount[0];
if (srcLeafCount) { // Unless grid is empty
util::cuda::lambdaKernel<<<numBlocks(srcLeafCount), mNumThreads, 0, mStream>>>(
srcLeafCount, util::morphology::cuda::RefineLeafMasksFunctor<BuildT>(), mDeviceSrcGrid, &mBuilder.data()->getGrid());
}
// Update leaf offsets and prefix sums
mBuilder.processLeafOffsets(mStream);
}// RefineGrid<BuildT>::refineLeafNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
}// namespace tools::cuda
}// namespace nanovdb
#endif // NVIDIA_TOOLS_CUDA_REFINEGRID_CUH_HAS_BEEN_INCLUDED
+72 -41
View File
@@ -10,7 +10,7 @@
\brief Performs signed flood-fill operation on the hierarchical tree structure on the device
\todo This tools needs to handle the (extremely) rare case when root node
\todo This tools needs to handle the (extremely) rare case when the root node
needs to be modified during the signed flood fill operation. This happens
when the root-table needs to be expanded with tile values (of size 4096^3)
that are completely inside the implicit surface.
@@ -24,9 +24,11 @@
#include <nanovdb/NanoVDB.h>
#include <nanovdb/GridHandle.h>
#include <nanovdb/cuda/UnifiedBuffer.h>
#include <nanovdb/util/cuda/Timer.h>
#include <nanovdb/util/cuda/Util.h>
#include <nanovdb/tools/cuda/GridChecksum.cuh>
#include <nanovdb/io/IO.h>// debugging needs io of Coord
namespace nanovdb {
@@ -41,8 +43,6 @@ template<typename BuildT>
typename util::enable_if<BuildTraits<BuildT>::is_float, void>::type
signedFloodFill(NanoGrid<BuildT> *d_grid, bool verbose = false, cudaStream_t stream = 0);
namespace {// anonymous namespace
template<typename BuildT>
class SignedFloodFill
{
@@ -65,47 +65,77 @@ private:
//================================================================================================
namespace kernels {// kernels namespace
template <typename ValueT>
struct RootChild {
Coord ijk;// origin of the child node
uint32_t idx;// linear offset for the child node
ValueT val[2];// first and last values as defined by child->getFirstValue/getLastValue
RootChild(Coord i=Coord(), uint32_t j=0) : ijk(i), idx(j) {};// c-tor
bool operator()(const RootChild &a, const RootChild &b) const { return a.ijk < b.ijk; }// for sorting
};
// CPU kernel!
template<typename BuildT>
__global__ void processRootKernel(NanoTree<BuildT> *tree)
{
// auto &root = tree->root();
/*
using ChildT = typename RootT::ChildNodeType;
// Insert the child nodes into a map sorted according to their origin
std::map<Coord, ChildT*> nodeKeys;
typename RootT::ChildOnIter it = root.beginChildOn();
for (; it; ++it) nodeKeys.insert(std::pair<Coord, ChildT*>(it.getCoord(), &(*it)));
static const Index DIM = RootT::ChildNodeType::DIM;
void processRoot(NanoTree<BuildT> *d_tree)
{// the root needs special care since unlike other nodes it's sparse and not dense!
using TreeT = NanoTree<BuildT>;
using RootT = NanoRoot<BuildT>;
using TileT = typename RootT::Tile;
using ValueT = typename RootT::ValueType;
using ChildT = RootChild<ValueT>;
static const int dim = int(RootT::ChildNodeType::DIM);
// First copy the tree and root and then its tiles, which is of unknown size
nanovdb::cuda::UnifiedBuffer uBuffer(sizeof(TreeT) + sizeof(RootT), sizeof(TreeT) + sizeof(RootT) + 64*sizeof(TileT));
cudaCheck(cudaMemcpy(uBuffer.data(), d_tree, uBuffer.size(), cudaMemcpyDeviceToHost));// copy Tree and Root (minus tiles)
if (!uBuffer.data<TreeT>()->isRootNext()) throw std::runtime_error("ERROR: expected no padding between tree and root!");
if ( uBuffer.data<TreeT>()->root().tileCount() == 0) return;// empty root node so nothing to do
uBuffer.resize(sizeof(TreeT) + uBuffer.data<TreeT>()->root().memUsage());// likely does nothing since we reserved 64 tiles
RootT *root = &uBuffer.data<TreeT>()->root();
cudaCheck(cudaMemcpy(root + 1, (char*)(d_tree + 1) + sizeof(RootT), root->tileCount()*sizeof(TileT), cudaMemcpyDeviceToHost));// copy tiles
// Sort the child nodes of the root in lexicographic order
nanovdb::cuda::UnifiedBuffer nodeBuffer(root->tileCount()*sizeof(ChildT));// potential over-allocation
auto *first = nodeBuffer.data<ChildT>(), *last = first;
for (auto it=root->beginChild(); it; ++it) *last++ = ChildT(it.getCoord(), it.pos());
if (last - first < 2) return;// zero or one child node so nothing to do!
std::sort(first, last, ChildT());// lexicographic ordering
// We employ a simple z-scanline algorithm that inserts inactive tiles with
// the inside value if they are sandwiched between inside child nodes only!
typename std::map<Coord, ChildT*>::const_iterator b = nodeKeys.begin(), e = nodeKeys.end();
if ( b == e ) return;
for (typename std::map<Coord, ChildT*>::const_iterator a = b++; b != e; ++a, ++b) {
Coord d = b->first - a->first; // delta of neighboring coordinates
if (d[0]!=0 || d[1]!=0 || d[2]==Int32(DIM)) continue;// not same z-scanline or neighbors
const ValueT fill[] = { a->second->getLastValue(), b->second->getFirstValue() };
if (!(fill[0] < 0) || !(fill[1] < 0)) continue; // scanline isn't inside
Coord c = a->first + Coord(0u, 0u, DIM);
for (; c[2] != b->first[2]; c[2] += DIM) root.addTile(c, mInside, false);
for (ChildT *a = first, *b = a+1; b!=last; ++a, ++b) {// loop over pairs of adjacent child nodes
const Coord d = b->ijk - a->ijk;// coord delta of adjacent child nodes
if (d[0]!=0 || d[1]!=0 || d[2]==dim) continue;// not same z-scanline or they are neighbors
util::cuda::lambdaKernel<<<1, 1>>>(1, [=] __device__(size_t) {
a->val[1] = root->getChild(root->tile(a->idx))->getLastValue();
b->val[0] = root->getChild(root->tile(b->idx))->getFirstValue();
});
cudaCheck(cudaDeviceSynchronize());// required for host access to RootChild::val[2]
if (a->val[1] > 0 || b->val[0] > 0) continue; // scanline is not inside a surface
for (Coord c = a->ijk.offsetBy(0,0,dim); c[2] != b->ijk[2]; c[2] += dim) {
TileT *tile = root->probeTile(c);
if (!tile) throw std::runtime_error("ERROR: missing internal tile! Please add them and try again!");
tile->setValue(c, false, -root->background());
}
}
*/
//root.setBackground(mOutside, /*updateChildNodes=*/false);
}// processRootKernel
cudaCheck(cudaMemcpy(d_tree + 1, root, root->memUsage(), cudaMemcpyHostToDevice));// copy tiles back to device
}// processRoot
//================================================================================================
template<typename BuildT, int LEVEL>
__global__ void processNodeKernel(NanoTree<BuildT> *tree, size_t count)
__global__ void processNode(NanoTree<BuildT> *d_tree, size_t count)
{
using NodeT = typename NanoNode<BuildT, LEVEL>::type;
const int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid >= count) return;
const uint32_t nValue = tid & (NodeT::SIZE - 1u);
auto &node = *(tree->template getFirstNode<LEVEL>() + (tid >> (3*NodeT::LOG2DIM)));
auto &node = *(d_tree->template getFirstNode<LEVEL>() + (tid >> (3*NodeT::LOG2DIM)));
const auto &mask = node.childMask();
if (mask.isOn(nValue)) return;// ignore if child
auto value = tree->background();// initiate to outside value
auto value = d_tree->background();// initiate to outside value
auto n = mask.template findNext<true>(nValue);
if (n < NodeT::SIZE) {
if (node.getChild(n)->getFirstValue() < 0) value = -value;
@@ -115,25 +145,25 @@ __global__ void processNodeKernel(NanoTree<BuildT> *tree, size_t count)
value = -value;
}
node.setValue(nValue, value);
}// processNodeKernel
}// processNode
//================================================================================================
template<typename BuildT>
__global__ void processLeafKernel(NanoTree<BuildT> *tree, size_t count)
__global__ void processLeaf(NanoTree<BuildT> *d_tree, size_t count)
{
using LeafT = NanoLeaf<BuildT>;
const size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid >= count) return;
const uint32_t nVoxel = tid & (LeafT::SIZE - 1u);
auto *leaf = tree->getFirstLeaf() + (tid >> (3*LeafT::LOG2DIM));
auto *leaf = d_tree->getFirstLeaf() + (tid >> (3*LeafT::LOG2DIM));
const auto &mask = leaf->valueMask();
if (mask.isOn(nVoxel)) return;
auto *buffer = leaf->mValues;
auto n = mask.template findNext<true>(nVoxel);
if (n == LeafT::SIZE && (n = mask.template findPrev<true>(nVoxel)) == LeafT::SIZE) n = 0u;
buffer[nVoxel] = buffer[n]<0 ? -tree->background() : tree->background();
}// processLeafKernel
buffer[nVoxel] = buffer[n]<0 ? -d_tree->background() : d_tree->background();
}// processLeaf
//================================================================================================
@@ -145,7 +175,7 @@ __global__ void cpyNodeCountKernel(NanoGrid<BuildT> *d_grid, uint64_t *d_count)
*d_count = d_grid->tree().root().tileCount();
}
}// anonymous namespace
}// kernels namespace
//================================================================================================
@@ -156,29 +186,29 @@ void SignedFloodFill<BuildT>::operator()(NanoGrid<BuildT> *d_grid)
NANOVDB_ASSERT(d_grid);
uint64_t count[4], *d_count = nullptr;
cudaCheck(util::cuda::mallocAsync((void**)&d_count, 4*sizeof(uint64_t), mStream));
cpyNodeCountKernel<BuildT><<<1, 1, 0, mStream>>>(d_grid, d_count);
kernels::cpyNodeCountKernel<BuildT><<<1, 1, 0, mStream>>>(d_grid, d_count);
cudaCheckError();
cudaCheck(cudaMemcpyAsync(&count, d_count, 4*sizeof(uint64_t), cudaMemcpyDeviceToHost, mStream));
cudaCheck(util::cuda::freeAsync(d_count, mStream));
static const int threadsPerBlock = 128;
auto blocksPerGrid = [&](size_t count)->uint32_t{return (count + (threadsPerBlock - 1)) / threadsPerBlock;};
auto *tree = reinterpret_cast<NanoTree<BuildT>*>(d_grid + 1);
auto *d_tree = reinterpret_cast<NanoTree<BuildT>*>(d_grid + 1);
if (mVerbose) mTimer.start("\nProcess leaf nodes");
processLeafKernel<BuildT><<<blocksPerGrid(count[0]<<9), threadsPerBlock, 0, mStream>>>(tree, count[0]<<9);
kernels::processLeaf<BuildT><<<blocksPerGrid(count[0]<<9), threadsPerBlock, 0, mStream>>>(d_tree, count[0]<<9);
cudaCheckError();
if (mVerbose) mTimer.restart("Process lower internal nodes");
processNodeKernel<BuildT,1><<<blocksPerGrid(count[1]<<12), threadsPerBlock, 0, mStream>>>(tree, count[1]<<12);
kernels::processNode<BuildT,1><<<blocksPerGrid(count[1]<<12), threadsPerBlock, 0, mStream>>>(d_tree, count[1]<<12);
cudaCheckError();
if (mVerbose) mTimer.restart("Process upper internal nodes");
processNodeKernel<BuildT,2><<<blocksPerGrid(count[2]<<15), threadsPerBlock, 0, mStream>>>(tree, count[2]<<15);
kernels::processNode<BuildT,2><<<blocksPerGrid(count[2]<<15), threadsPerBlock, 0, mStream>>>(d_tree, count[2]<<15);
cudaCheckError();
//if (mVerbose) mTimer.restart("Process root node");
//processRootKernel<BuildT><<<1, 1, 0, mStream>>>(tree);
if (mVerbose) mTimer.restart("Process root node");
kernels::processRoot<BuildT>(d_tree);
if (mVerbose) mTimer.stop();
cudaCheckError();
}// SignedFloodFill::operator()
@@ -196,6 +226,7 @@ signedFloodFill(NanoGrid<BuildT> *d_grid, bool verbose, cudaStream_t stream)
if (cs.isFull()) {// CheckMode::Partial checksum is unaffected
updateChecksum(d_gridData, CheckMode::Full, stream);
}
cudaCheck(cudaStreamSynchronize(stream));
}
}// namespace tools::cuda
@@ -0,0 +1,563 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
/*!
\file nanovdb/tools/cuda/TopologyBuilder.cuh
\authors Efty Sifakis
\brief Shared functionality of (mostly morphology) operators that alter the voxel content of grids
\warning The header file contains cuda device code so be sure
to only include it in .cu files (or other .cuh files)
*/
#ifndef NVIDIA_TOOLS_CUDA_TOPOLOGYBUILDER_CUH_HAS_BEEN_INCLUDED
#define NVIDIA_TOOLS_CUDA_TOPOLOGYBUILDER_CUH_HAS_BEEN_INCLUDED
#include <nanovdb/NanoVDB.h>
#include <nanovdb/cuda/TempPool.h>
#include <nanovdb/cuda/DeviceBuffer.h>
#include <nanovdb/util/cuda/Morphology.cuh>
namespace nanovdb {
namespace tools::cuda {
template <typename BuildT>
class TopologyBuilder
{
static_assert(nanovdb::BuildTraits<BuildT>::is_onindex);// For now, only OnIndexGrids supported
using GridT = NanoGrid<BuildT>;
using TreeT = NanoTree<BuildT>;
using RootT = NanoRoot<BuildT>;
using UpperT = NanoUpper<BuildT>;
using LowerT = NanoLower<BuildT>;
using LeafT = NanoLeaf<BuildT>;
public:
TopologyBuilder(cudaStream_t stream)
{
mData = nanovdb::cuda::DeviceBuffer::create(sizeof(Data));
}
struct Data {
void *d_bufferPtr;
uint64_t grid, tree, root, upper, lower, leaf, size;// byte offsets to nodes in buffer
uint32_t nodeCount[3];// 0=leaf,1=lower, 2=upper
uint32_t *d_upperOffsets;
__hostdev__ GridT& getGrid() const {return *util::PtrAdd<GridT>(d_bufferPtr, grid);}
__hostdev__ TreeT& getTree() const {return *util::PtrAdd<TreeT>(d_bufferPtr, tree);}
__hostdev__ RootT& getRoot() const {return *util::PtrAdd<RootT>(d_bufferPtr, root);}
__hostdev__ UpperT& getUpper(int i) const {return *(util::PtrAdd<UpperT>(d_bufferPtr, upper)+i);}
__hostdev__ LowerT& getLower(int i) const {return *(util::PtrAdd<LowerT>(d_bufferPtr, lower)+i);}
__hostdev__ LeafT& getLeaf(int i) const {return *(util::PtrAdd<LeafT>(d_bufferPtr, leaf)+i);}
};// Data
void allocateInternalMaskBuffers(cudaStream_t stream);
void countNodes(cudaStream_t stream);
template<typename BufferT>
BufferT getBuffer(const BufferT &buffer, cudaStream_t stream);
void processUpperNodes(cudaStream_t stream);
void processLowerNodes(cudaStream_t stream);
void processLeafOffsets(cudaStream_t stream);
void processBBox(cudaStream_t stream);
void postProcessGridTree(cudaStream_t stream);
nanovdb::cuda::DeviceBuffer mProcessedRoot;
nanovdb::cuda::DeviceBuffer mUpperMasks;
nanovdb::cuda::DeviceBuffer mLowerMasks;
nanovdb::cuda::DeviceBuffer mUpperOffsets;
nanovdb::cuda::DeviceBuffer mLowerOffsets;
nanovdb::cuda::DeviceBuffer mLeafOffsets;
nanovdb::cuda::DeviceBuffer mVoxelOffsets;
nanovdb::cuda::DeviceBuffer mLowerParents;
nanovdb::cuda::DeviceBuffer mLeafParents;
nanovdb::cuda::DeviceBuffer mData;
CheckMode mChecksum{CheckMode::Disable};
auto deviceProcessedRoot() { return static_cast<RootT*>(mProcessedRoot.deviceData()); }
auto hostProcessedRoot() { return static_cast<RootT*>(mProcessedRoot.data()); }
void* deviceUpperMasks() { return mUpperMasks.deviceData(); }
void* deviceLowerMasks() { return mLowerMasks.deviceData(); }
Data* data() { return static_cast<Data*>(mData.data()); }
Data* deviceData() { return static_cast<Data*>(mData.deviceData()); }
private:
static constexpr unsigned int mNumThreads = 128;// for kernels spawned via lambdaKernel (others may specialize)
static unsigned int numBlocks(unsigned int n) {return (n + mNumThreads - 1) / mNumThreads;}
nanovdb::cuda::TempDevicePool mTempDevicePool;
};// tools::cuda::TopologyBuilder<BuildT>
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Define utility macro used to call cub functions that use dynamic temporary storage
#ifndef CALL_CUBS
#ifdef _WIN32
#define CALL_CUBS(func, ...) \
cudaCheck(cub::func(nullptr, mTempDevicePool.requestedSize(), __VA_ARGS__, stream)); \
mTempDevicePool.reallocate(stream); \
cudaCheck(cub::func(mTempDevicePool.data(), mTempDevicePool.size(), __VA_ARGS__, stream));
#else// ndef _WIN32
#define CALL_CUBS(func, args...) \
cudaCheck(cub::func(nullptr, mTempDevicePool.requestedSize(), args, stream)); \
mTempDevicePool.reallocate(stream); \
cudaCheck(cub::func(mTempDevicePool.data(), mTempDevicePool.size(), args, stream));
#endif// ifdef _WIN32
#endif// ifndef CALL_CUBS
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void TopologyBuilder<BuildT>::allocateInternalMaskBuffers(cudaStream_t stream)
{
if (hostProcessedRoot()->tileCount() == 0) return; // Processing empty grid(s); nothing to allocate
// Allocate (and zero-fill) buffers large enough to hold:
// (a) The serialized masks of all upper nodes, for all tiles in the updated root node, and
// (b) The serialized masks of all densified lower nodes, as if every upper node had a full set of 32^3 lower children
int device = 0;
cudaGetDevice(&device);
uint64_t upperSize = hostProcessedRoot()->tileCount() * sizeof(Mask<5>);
uint64_t lowerSize = hostProcessedRoot()->tileCount() * Mask<5>::SIZE * sizeof(Mask<4>);
mUpperMasks = nanovdb::cuda::DeviceBuffer::create(upperSize, nullptr, device, stream);
if (mUpperMasks.deviceData() == nullptr) throw std::runtime_error("Failed to allocate upper mask buffer on device");
cudaCheck(cudaMemsetAsync(mUpperMasks.deviceData(), 0, upperSize, stream));
mLowerMasks = nanovdb::cuda::DeviceBuffer::create( lowerSize, nullptr, device, stream );
if (mLowerMasks.deviceData() == nullptr) throw std::runtime_error("Failed to allocate lower mask buffer on device");
cudaCheck(cudaMemsetAsync(mLowerMasks.deviceData(), 0, lowerSize, stream));
}// TopologyBuilder<BuildT>::allocateInternalMaskBuffers
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template<typename BuildT>
void TopologyBuilder<BuildT>::countNodes(cudaStream_t stream)
{
auto processedTileCount = hostProcessedRoot()->tileCount();
if (processedTileCount == 0) { // Processing empty grid(s); zero nodes at all levels
data()->nodeCount[0] = data()->nodeCount[1] = data()->nodeCount[2] = 0;
return;
}
// Computes prefix sums of (a) non-empty lower nodes, (b) counts of their leaf children,
// and (c) count of the speculatively updated root tiles that have actually been used.
// These are used to reconstruct child offsets for the internal nodes of the updated tree,
// as well as the tile table at the root.
std::size_t size = processedTileCount*Mask<5>::SIZE;
int device = 0;
cudaGetDevice(&device);
nanovdb::cuda::DeviceBuffer upperCountsBuffer = nanovdb::cuda::DeviceBuffer::create(processedTileCount*sizeof(uint32_t), nullptr, device, stream);
nanovdb::cuda::DeviceBuffer lowerCountsBuffer = nanovdb::cuda::DeviceBuffer::create(size*sizeof(uint32_t), nullptr, device, stream);
nanovdb::cuda::DeviceBuffer leafCountsBuffer = nanovdb::cuda::DeviceBuffer::create(size*sizeof(uint32_t), nullptr, device, stream);
using CountType = uint32_t (*)[Mask<5>::SIZE];
auto lowerCounts = reinterpret_cast<CountType>( lowerCountsBuffer.deviceData() );
auto leafCounts = reinterpret_cast<CountType>( leafCountsBuffer.deviceData() );
using Op = util::morphology::cuda::EnumerateNodesFunctor;
util::cuda::operatorKernel<Op>
<<<dim3(processedTileCount, Op::SlicesPerUpperNode, 1), Op::MaxThreadsPerBlock, 0, stream>>>
(deviceUpperMasks(), deviceLowerMasks(), lowerCounts, leafCounts);
mUpperOffsets = nanovdb::cuda::DeviceBuffer::create((processedTileCount+1)*sizeof(uint32_t), nullptr, device, stream);
mLowerOffsets = nanovdb::cuda::DeviceBuffer::create((size+1)*sizeof(uint32_t), nullptr, device, stream);
mLeafOffsets = nanovdb::cuda::DeviceBuffer::create((size+1)*sizeof(uint32_t), nullptr, device, stream);
cudaCheck(cudaMemsetAsync(mLowerOffsets.deviceData(), 0, sizeof(uint32_t), stream));
CALL_CUBS(DeviceScan::InclusiveSum,
static_cast<uint32_t*>(lowerCountsBuffer.deviceData()),
static_cast<uint32_t*>(mLowerOffsets.deviceData())+1,
size);
cudaCheck(cudaMemcpyAsync(&data()->nodeCount[1], static_cast<uint32_t*>(mLowerOffsets.deviceData())+size, sizeof(uint32_t), cudaMemcpyDeviceToHost, stream));
cudaCheck(cudaMemsetAsync(mLeafOffsets.deviceData(), 0, sizeof(uint32_t), stream));
CALL_CUBS(DeviceScan::InclusiveSum,
static_cast<uint32_t*>(leafCountsBuffer.deviceData()),
static_cast<uint32_t*>(mLeafOffsets.deviceData())+1,
size);
cudaCheck(cudaMemcpyAsync(&data()->nodeCount[0], static_cast<uint32_t*>(mLeafOffsets.deviceData())+size, sizeof(uint32_t), cudaMemcpyDeviceToHost, stream));
util::cuda::lambdaKernel<<<numBlocks(processedTileCount), mNumThreads, 0, stream>>>(
processedTileCount,
[] __device__(size_t tileID, CountType lowerOffsets, uint32_t* upperCounts)
{ upperCounts[tileID] = (lowerOffsets[tileID+1][0] > lowerOffsets[tileID][0]) ? 1 : 0; },
static_cast<CountType>(mLowerOffsets.deviceData()),
static_cast<uint32_t*>(upperCountsBuffer.deviceData()));
cudaCheck(cudaMemsetAsync( mUpperOffsets.deviceData(), 0, sizeof(uint32_t), stream));
CALL_CUBS(DeviceScan::InclusiveSum,
static_cast<uint32_t*>(upperCountsBuffer.deviceData()),
static_cast<uint32_t*>(mUpperOffsets.deviceData())+1,
processedTileCount);
cudaCheck(cudaMemcpyAsync(&data()->nodeCount[2], static_cast<uint32_t*>(mUpperOffsets.deviceData())+processedTileCount, sizeof(uint32_t), cudaMemcpyDeviceToHost, stream));
}// TopologyBuilder<BuildT>::countNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template <typename BuildT>
template <typename BufferT>
BufferT TopologyBuilder<BuildT>::getBuffer(const BufferT &pool, cudaStream_t stream)
{
// Allocates a device buffer for the destination grid, once the topology/size of the tree is known
data()->grid = 0;// grid is always stored at the start of the buffer!
data()->tree = GridT::memUsage();// grid ends and tree begins
data()->root = data()->tree + TreeT::memUsage(); // tree ends and root node begins
data()->upper = data()->root + RootT::memUsage(data()->nodeCount[2]);// root node ends and upper internal nodes begin
data()->lower = data()->upper + UpperT::memUsage()*data()->nodeCount[2];// upper internal nodes ends and lower internal nodes begin
data()->leaf = data()->lower + LowerT::memUsage()*data()->nodeCount[1];// lower internal nodes ends and leaf nodes begin
data()->size = data()->leaf + LeafT::DataType::memUsage()*data()->nodeCount[0];// leaf nodes end and blind meta data begins
int device = 0;
cudaGetDevice(&device);
auto buffer = BufferT::create(data()->size, &pool, device, stream);// only allocate buffer on the device
cudaCheck(cudaMemsetAsync(buffer.deviceData(), 0, data()->size, stream));
data()->d_bufferPtr = buffer.deviceData();
if (data()->d_bufferPtr == nullptr) throw std::runtime_error("Failed to allocate grid buffer on the device");
if (data()->nodeCount[2] != 0) // Unless the result is an empty grid
data()->d_upperOffsets = static_cast<uint32_t*>(mUpperOffsets.deviceData());
mData.deviceUpload(device, stream, false);
return buffer;
}// TopologyBuilder<BuildT>::getBuffer
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
namespace topology::detail {
template <typename BuildT>
struct BuildGridTreeRootFunctor
{
__device__
void operator()(size_t, typename TopologyBuilder<BuildT>::Data *d_data) {
// process Root
auto &root = d_data->getRoot();
root.mTableSize = d_data->nodeCount[2];
root.mBackground = NanoRoot<BuildT>::ValueType(0);// background_value
root.mMinimum = root.mMaximum = NanoRoot<BuildT>::ValueType(0);
root.mAverage = root.mStdDevi = NanoRoot<BuildT>::FloatType(0);
root.mBBox = CoordBBox(); // To be further updated after the leaf-level voxel update
// process Tree
auto &tree = d_data->getTree();
tree.setRoot(&root);
if (d_data->nodeCount[2]) {
tree.setFirstNode(&d_data->getUpper(0));
tree.setFirstNode(&d_data->getLower(0));
tree.setFirstNode(&d_data->getLeaf(0));
}
else {
tree.template setFirstNode<NanoUpper<BuildT>>(nullptr);
tree.template setFirstNode<NanoLower<BuildT>>(nullptr);
tree.template setFirstNode<NanoLeaf<BuildT>>(nullptr);
}
tree.mNodeCount[2] = d_data->nodeCount[2];
tree.mNodeCount[1] = d_data->nodeCount[1];
tree.mNodeCount[0] = d_data->nodeCount[0];
tree.mVoxelCount = 0; // Actual voxel count (for non-empty grids) will only be known
// once leaf masks have been processed
tree.mTileCount[2] = tree.mTileCount[1] = tree.mTileCount[0] = 0;
// process Grid
// The GridData header has already been copied from the input;
// reset what is necessary, and assert that others are at the expected values
auto &grid = d_data->getGrid();
#ifdef NANOVDB_USE_NEW_MAGIC_NUMBERS
NANOVDB_ASSERT(grid.mMagic == NANOVDB_MAGIC_GRID);
#else
NANOVDB_ASSERT(grid.mMagic == NANOVDB_MAGIC_NUMB);
#endif
grid.mChecksum.disable(); // all 64 bits ON means checksum is disabled
NANOVDB_ASSERT(grid.mVersion == Version());
NANOVDB_ASSERT(grid.mFlags.isMaskOn(GridFlags::IsBreadthFirst));
grid.mFlags.initMask({GridFlags::IsBreadthFirst}); // expected flags (HasBBox will be set later if grid is non-empty)
grid.mGridIndex = 0u; // Possibly overwriting input; returned grid has batch size 1
grid.mGridCount = 1u; // Possibly overwriting input; returned grid has batch size 1
grid.mGridSize = d_data->size;
// grid.mGridName expected to have been copied verbatim from input
// grid.mMap expected to have been copied verbatim from input
grid.mWorldBBox = Vec3dBBox();// invalid bbox
grid.mVoxelSize = grid.mMap.getVoxelSize();
NANOVDB_ASSERT(grid.mGridClass == GridClass::IndexGrid);
NANOVDB_ASSERT(grid.mGridType == toGridType<BuildT>());
grid.mBlindMetadataOffset = d_data->size; // i.e. no blind data, even if the input grid had any
grid.mBlindMetadataCount = 0u; // i.e. no blind data
NANOVDB_ASSERT(grid.mData0 == 0u); // zero padding
grid.mData1 = 1u; // This will be updated (unless this is an empty grid) after voxels have been processed
#ifdef NANOVDB_USE_NEW_MAGIC_NUMBERS
NANOVDB_ASSERT(grid.mData2 == 0u);
#else
NANOVDB_ASSERT(grid.mData2 == NANOVDB_MAGIC_GRID);
#endif
}
};
}// namespace topology::detail
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
namespace topology::detail {
template <typename BuildT>
struct BuildUpperNodesFunctor
{
__device__
void operator()(size_t processedTileID, typename TopologyBuilder<BuildT>::Data *d_data, NanoRoot<BuildT> *d_processedRoot) {
uint32_t tileID = d_data->d_upperOffsets[processedTileID];
if (tileID != d_data->d_upperOffsets[processedTileID+1]) // if the offsets are the same, this was a speculatively introduced tile which was not necessary
{
auto &root = d_data->getRoot();
auto &dstUpper = d_data->getUpper(tileID);
auto &processedTile = *d_processedRoot->tile(processedTileID);
root.tile(tileID)->setChild( processedTile.origin(), &dstUpper, &root );
dstUpper.mBBox = CoordBBox(); // To be further updated after the operation has been applied at leaf-level
// TODO: Is this accurate? Any other flags that should be set?
dstUpper.mFlags = (uint64_t)GridFlags::HasBBox;
}
}
};
}// namespace topology::detail
template <typename BuildT>
inline void TopologyBuilder<BuildT>::processUpperNodes(cudaStream_t stream)
{
// Connect all newly allocated upper nodes to their respective tiles
// Also fill in any necessary part of the preamble (in InternalData) of upper nodes
auto processedTileCount = hostProcessedRoot()->tileCount();
if (processedTileCount) { // Unless output grid is empty
util::cuda::lambdaKernel<<<numBlocks(processedTileCount), mNumThreads, 0, stream>>>(
processedTileCount, topology::detail::BuildUpperNodesFunctor<BuildT>(), deviceData(), deviceProcessedRoot());
cudaCheckError();
}
}// TopologyBuilder<BuildT>::processUpperNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
template <typename BuildT>
inline void TopologyBuilder<BuildT>::processLowerNodes(cudaStream_t stream)
{
// Fill out the contents of all newly allocated lower nodes (using the densified upper/lower mask arrays)
// Also fill in the preamble (most of LeafData) for their leaf children
auto processedTileCount = hostProcessedRoot()->tileCount();
using CountType = uint32_t (*)[Mask<5>::SIZE];
if (processedTileCount) { // Unless output grid is empty
int device = 0;
cudaGetDevice(&device);
std::size_t lowerCount = data()->nodeCount[1];
mLowerParents = nanovdb::cuda::DeviceBuffer::create(lowerCount*sizeof(uint32_t), nullptr, device, stream);
std::size_t leafCount = data()->nodeCount[0];
mLeafParents = nanovdb::cuda::DeviceBuffer::create(leafCount*sizeof(uint32_t), nullptr, device, stream);
using Op = util::morphology::cuda::ProcessLowerNodesFunctor<BuildT>;
util::cuda::operatorKernel<Op>
<<<dim3(processedTileCount, Op::SlicesPerUpperNode, 1), Op::MaxThreadsPerBlock, 0, stream>>>(
deviceUpperMasks(),
deviceLowerMasks(),
static_cast<uint32_t*>(mUpperOffsets.deviceData()),
static_cast<CountType>(mLowerOffsets.deviceData()),
static_cast<CountType>(mLeafOffsets.deviceData()),
static_cast<GridT*>(data()->d_bufferPtr),
static_cast<uint32_t*>(mLowerParents.deviceData()),
static_cast<uint32_t*>(mLeafParents.deviceData())
);
cudaCheckError();
}
mProcessedRoot.clear(stream);
mUpperMasks.clear(stream);
mLowerMasks.clear(stream);
mLowerOffsets.clear(stream);
mLeafOffsets.clear(stream);
}// TopologyBuilder<BuildT>::processLowerNodes
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
namespace topology::detail {
template <typename BuildT>
struct UpdateLeafVoxelCountsAndPrefixSumFunctor
{
__device__
void operator()(size_t leafID, typename TopologyBuilder<BuildT>::Data *d_data, uint64_t *d_voxelCounts) {
auto &leaf = d_data->getGrid().tree().template getFirstNode<0>()[leafID];
const uint64_t *w = leaf.mValueMask.words();
uint64_t prefixSum = 0, sum = util::countOn(*w++);
prefixSum = sum;
for (int n = 9; n < 55; n += 9) {// n=i*9 where i=1,2,..6
sum += util::countOn(*w++);
prefixSum |= sum << n; }// each pre-fixed sum is encoded in 9 bits
sum += util::countOn(*w);
d_voxelCounts[leafID] = sum;
leaf.mPrefixSum = prefixSum; }
};
template <typename BuildT>
struct UpdateLeafVoxelOffsetsFunctor
{
__device__
void operator()(size_t leafID, typename TopologyBuilder<BuildT>::Data *d_data, uint64_t *d_voxelOffsets) {
auto &leaf = d_data->getGrid().tree().template getFirstNode<0>()[leafID];
leaf.mOffset = d_voxelOffsets[leafID]+1; }
};
}// namespace topology::detail
template<typename BuildT>
inline void TopologyBuilder<BuildT>::processLeafOffsets(cudaStream_t stream)
{
int device = 0;
cudaGetDevice(&device);
std::size_t leafCount = data()->nodeCount[0];
if (leafCount) { // Unless output grid is empty
mVoxelOffsets = nanovdb::cuda::DeviceBuffer::create((leafCount+1)*sizeof(uint64_t), nullptr, device, stream);
cudaCheck(cudaMemsetAsync(mVoxelOffsets.deviceData(), 0, sizeof(uint64_t), stream));
util::cuda::lambdaKernel<<<numBlocks(leafCount), mNumThreads, 0, stream>>>(
leafCount, topology::detail::UpdateLeafVoxelCountsAndPrefixSumFunctor<BuildT>(), deviceData(), static_cast<uint64_t*>(mVoxelOffsets.deviceData())+1);
CALL_CUBS(DeviceScan::InclusiveSum,
static_cast<uint64_t*>(mVoxelOffsets.deviceData())+1,
static_cast<uint64_t*>(mVoxelOffsets.deviceData())+1,
leafCount);
util::cuda::lambdaKernel<<<numBlocks(leafCount), mNumThreads, 0, stream>>>(
leafCount, topology::detail::UpdateLeafVoxelOffsetsFunctor<BuildT>(), deviceData(), static_cast<uint64_t*>(mVoxelOffsets.deviceData()));
}
}// TopologyBuilder<BuildT>::processLeafOffsets
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Undefine utility macro for cub functions
#ifdef CALL_CUBS
#undef CALL_CUBS
#endif
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
namespace topology::detail {
template <typename BuildT>
struct UpdateAndPropagateLeafBBoxFunctor
{
__device__
void operator()(size_t tid, typename TopologyBuilder<BuildT>::Data *d_data, const uint32_t* leafParents) {
auto &lower = d_data->getLower(leafParents[tid]);
auto &leaf = d_data->getLeaf(tid);
leaf.updateBBox();
lower.mBBox.expandAtomic(leaf.bbox());
}
};
template <typename BuildT>
struct PropagateLowerBBoxFunctor
{
__device__
void operator()(size_t tid, typename TopologyBuilder<BuildT>::Data *d_data, const uint32_t* lowerParents) {
auto &upper = d_data->getUpper(lowerParents[tid]);
auto &lower = d_data->getLower(tid);
upper.mBBox.expandAtomic(lower.bbox()); }
};
template <typename BuildT>
struct PropagateUpperBBoxFunctor
{
__device__
void operator()(size_t tid, typename TopologyBuilder<BuildT>::Data *d_data) {
d_data->getRoot().mBBox.expandAtomic(d_data->getUpper(tid).bbox());
}
};
template <typename BuildT>
struct UpdateRootWorldBBoxFunctor
{
__device__
void operator()(size_t tid, typename TopologyBuilder<BuildT>::Data *d_data) {
// TODO: check that the correct semantics are followed in this transformation
auto BBox = d_data->getRoot().mBBox;
BBox.max() += 1;
d_data->getGrid().mFlags.setMaskOn(GridFlags::HasBBox);
d_data->getGrid().mWorldBBox = BBox.transform(d_data->getGrid().data()->mMap);
}
};
}// namespace topology::detail
template <typename BuildT>
inline void TopologyBuilder<BuildT>::processBBox(cudaStream_t stream)
{
if (data()->nodeCount[0] == 0) return; // Output grid is empty; retain empty bounding box
// TODO: Do we need a special case when flags indicates no bounding box?
// update and propagate bbox from leaf -> lower/parent nodes
util::cuda::lambdaKernel<<<numBlocks(data()->nodeCount[0]), mNumThreads, 0, stream>>>(
data()->nodeCount[0], topology::detail::UpdateAndPropagateLeafBBoxFunctor<BuildT>(), deviceData(), static_cast<uint32_t*>(mLeafParents.deviceData()));
mLeafParents.clear(stream);
cudaCheckError();
// propagate bbox from lower -> upper/parent node
util::cuda::lambdaKernel<<<numBlocks(data()->nodeCount[1]), mNumThreads, 0, stream>>>(
data()->nodeCount[1], topology::detail::PropagateLowerBBoxFunctor<BuildT>(), deviceData(), static_cast<uint32_t*>(mLowerParents.deviceData()));
mLowerParents.clear(stream);
cudaCheckError();
// propagate bbox from upper -> root/parent node
util::cuda::lambdaKernel<<<numBlocks(data()->nodeCount[2]), mNumThreads, 0, stream>>>(data()->nodeCount[2], topology::detail::PropagateUpperBBoxFunctor<BuildT>(), deviceData());
cudaCheckError();
// update the world-bbox in the root node
util::cuda::lambdaKernel<<<1, 1, 0, stream>>>(1, topology::detail::UpdateRootWorldBBoxFunctor<BuildT>(), deviceData());
cudaCheckError();
}// TopologyBuilder<BuildT>::processBBox
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
namespace topology::detail {
template <typename BuildT>
struct PostProcessGridTreeFunctor
{
__device__
void operator()(size_t tid, typename TopologyBuilder<BuildT>::Data *d_data, uint64_t* d_voxelOffsets) {
auto& grid = d_data->getGrid();
auto& tree = grid.tree();
auto leafCount = tree.mNodeCount[0];
tree.mVoxelCount = d_voxelOffsets[leafCount];
grid.mData1 = tree.mVoxelCount+1;
}
};
}// namespace topology::detail
template <typename BuildT>
inline void TopologyBuilder<BuildT>::postProcessGridTree(cudaStream_t stream)
{
// Finish updates to GridData/TreeData and (optionally) update checksum
if (data()->nodeCount[0]) // if grid is empty, the default values are correct
util::cuda::lambdaKernel<<<1, 1, 0, stream>>>(1, topology::detail::PostProcessGridTreeFunctor<BuildT>(), deviceData(), static_cast<uint64_t*>(mVoxelOffsets.deviceData()));
cudaCheckError();
mVoxelOffsets.clear(stream);
tools::cuda::updateChecksum((GridData*)data()->d_bufferPtr, mChecksum, stream);
}// TopologyBuilder<BuildT>::postProcessGridTree
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
}// namespace tools::cuda
}// namespace nanovdb
#endif // NVIDIA_TOOLS_CUDA_TOPOLOGYBUILDER_CUH_HAS_BEEN_INCLUDED
@@ -0,0 +1,250 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
/*!
\file nanovdb/tools/VoxelBlockManager.cuh
\author Eftychios Sifakis
\date January 27, 2025
\brief Implements device functions to build and query the VoxelBlockManager.
The VoxelBlockManager is an acceleration structure for sequential access
and stencil operations over solely the active voxels of an OnIndexGrid
which enables SIMT parallelism independent of occupancy.
*/
#include <cuda/atomic>
#include <nanovdb/NanoVDB.h>
#include <nanovdb/util/cuda/Util.h>
#ifndef NANOVDB_VOXELBLOCKMANAGER_CUH_HAS_BEEN_INCLUDED
#define NANOVDB_VOXELBLOCKMANAGER_CUH_HAS_BEEN_INCLUDED
namespace nanovdb {
namespace tools::cuda {
/// @brief This class allows for sequential access to contiguous spans (i.e.
/// blocks) of active voxels in an IndexGrid on the device, and efficient
/// computation of linear offsets of stencils (e.g. 3-wide box stencil).
/// @tparam BlockWidth Number of active voxels in a contiguous span
template <int BlockWidth>
struct VoxelBlockManager
{
// The efficiency of the functions in this class are contingent on
// threadblock-level coordination, which manifests either as using shared
// memory for synchronization, or warp-level shift operations.
// The jumpMap is packed in uint64_t's, one per 64 consecutive bits. Hence
// JumpMapLength is the number of uint64_t that are needed to straddle the
// total BlockWidth bits of the jumpMap.
static constexpr int JumpMapLength = BlockWidth/64;
static constexpr uint32_t UnusedLeafIndex = 0xffffffff;
static constexpr uint16_t UnusedVoxelOffset = 0xffff;
/// @brief Given a grid and the associated jumpMap in global memory, compute
/// the leaf indices and voxel offsets in shared memory. This function
/// writes to shared memory and synchronizes threads and thus should not be
/// called from divergent threads within a thread block.
/// @tparam BuildT Build type of the grid
/// @param grid
/// @param firstLeafID
/// @param jumpMap
/// @param blockFirstOffset
/// @param smem_leafIndex Leaf indices stored in shared memory
/// @param smem_voxelOffset Voxel offsets stored in shared memory
template <class BuildT>
__device__
static typename util::enable_if<BuildTraits<BuildT>::is_index, void>::type
decodeInverseMaps(
NanoGrid<BuildT> *grid,
const uint32_t firstLeafID,
const uint64_t *jumpMap,
const uint64_t blockFirstOffset,
uint32_t *smem_leafIndex,
uint16_t *smem_voxelOffset)
{
// Verify that the nodes can be accessed linearly
NANOVDB_ASSERT(grid->isSequential());
int tID = threadIdx.x;
// Count how many additional leaves (following the one indicated by firstLeafID)
// overlap with this voxel block
int nExtraLeaves = 0;
for (int i = 0; i < JumpMapLength; i++)
nExtraLeaves += util::countOn(jumpMap[i]);
// Initialize leafIndex & voxelOffset to sentinel values
// for blocks that extend beyond the last active voxel in the grid
if (tID < BlockWidth)
#pragma cuda unroll
for (int i = 0; i < BlockWidth; i += blockDim.x) {
smem_leafIndex[i+tID] = UnusedLeafIndex;
smem_voxelOffset[i+tID] = UnusedVoxelOffset;
}
__syncthreads();
NANOVDB_ASSERT(blockDim.x <= 512);
const auto& tree = grid->tree();
// Loop through all leafNodes overlapping the voxel block
// with all threads in threadblock working collaboratively within each leafNode
for (int leafID = firstLeafID; leafID <= firstLeafID + nExtraLeaves; leafID++) {
const auto& leaf = tree.template getFirstNode<0>()[leafID];
const Coord origin = leaf.origin();
for (int threadOffset = 0; threadOffset < 512; threadOffset += blockDim.x) {
int localOffset = threadOffset + tID;
int index = leaf.data()->getValue(localOffset);
if ((index >= blockFirstOffset) && (index < blockFirstOffset + BlockWidth)) {
int blockOffset = index - blockFirstOffset;
// Write inverse map to shared memory; no collisions
smem_leafIndex[blockOffset] = leafID;
smem_voxelOffset[blockOffset] = localOffset;
}
}
}
__syncthreads();
}
/// @brief Given a grid and its decoded voxel map, compute the stencil.
/// This function accesses shared memory but does not synchronize threads
/// so it may be called from divergent threads within a thread block.
/// offsets for a 3x3x3 box stencil.
/// @tparam BuildT Build type of the grid
/// @param grid
/// @param smem_leafIndex Leaf indices stored in shared memory
/// @param smem_voxelOffset Voxel offsets stored in shared memory
/// @param stencilIndices Pointer to output stencil indices. Must have
/// length of at least 27 (corresponding to the 3x3x3 stencil)
template <class BuildT>
__device__
static typename util::enable_if<BuildTraits<BuildT>::is_index, void>::type
computeBoxStencil(
const NanoGrid<BuildT> *grid,
const uint32_t *smem_leafIndex,
const uint16_t *smem_voxelOffset,
uint64_t *stencilIndices)
{
// Verify that the nodes can be accessed linearly
NANOVDB_ASSERT(grid->isSequential());
int tID = threadIdx.x;
const auto& tree = grid->tree();
if (smem_leafIndex[tID] != UnusedLeafIndex) {
// This presumes that leaf nodes are fixed-size and sequentially accessible in memory
const auto& leaf = tree.template getFirstNode<0>()[ smem_leafIndex[tID] ];
const Coord coord = leaf.offsetToGlobalCoord( smem_voxelOffset[tID] );
const auto index = leaf.getValue( smem_voxelOffset[tID] );
for (int di = -1; di <= 1; di++)
for (int dj = -1; dj <= 1; dj++)
for (int dk = -1; dk <= 1; dk++) {
int spokeID = ( di + 1 ) * 9 + ( dj + 1 ) * 3 + dk + 1;
const auto neighbor = coord.offsetBy( di, dj, dk );
stencilIndices[spokeID] = tree.getValue( neighbor );
}
}
}
};
/// @brief This functor calculates the firstLeafID and jumpMap for the
/// VoxelBlockManager over the subset of the Tree nodes specified by
/// firstOffset, lastOffset, and nBlocks.
template<int BlockWidthLog2, int NumThreads>
struct BuildVoxelBlockManagerFunctor
{
static constexpr int BlockWidth = 1 << BlockWidthLog2;
static constexpr int JumpMapLength = BlockWidth/64;
static constexpr int SlicesPerLowerNode = 8;
static constexpr int LeafNodesPerSlice = 4096/SlicesPerLowerNode;
static constexpr int MaxThreadsPerBlock = NumThreads;
static constexpr int MinBlocksPerMultiprocessor = 1;
void __device__
operator()(
uint64_t firstOffset,
uint64_t lastOffset,
int nBlocks,
const NanoGrid<ValueOnIndex> *grid,
uint32_t *firstLeafID,
uint64_t *jumpMap)
{
// Verify that the nodes can be accessed linearly
NANOVDB_ASSERT(grid->isSequential());
using JumpMapType = uint64_t (&)[][JumpMapLength];
int tID = threadIdx.x;
int blockID = blockIdx.x;
int sliceID = blockIdx.y;
const auto& tree = grid->tree();
const auto& lower = tree.getFirstNode<1>()[blockID];
for ( std::size_t jj = sliceID*LeafNodesPerSlice; jj < (sliceID+1)*LeafNodesPerSlice; jj += MaxThreadsPerBlock )
if ( lower.childMask().isOn(jj+tID) )
{
auto& leaf = *lower.getChild(jj+tID);
const auto leafFirstOffset = leaf.data()->firstOffset();
const auto leafValueCount = leaf.data()->valueCount();
const auto leafLastOffset = leafFirstOffset + leafValueCount - 1;
auto leafIndex = util::PtrDiff(&leaf,tree.getFirstNode<0>()) / sizeof(NanoLeaf<ValueOnIndex>);
if ( ( leafFirstOffset > lastOffset ) || (leafLastOffset < firstOffset) ) continue;
int lastBlock = (leafLastOffset - firstOffset) >> BlockWidthLog2;
lastBlock = min(lastBlock, nBlocks-1);
uint64_t firstBlock = (leafFirstOffset < firstOffset) ? 0 :
(leafFirstOffset - firstOffset) >> BlockWidthLog2;
// For all but the first block touched, mark the firstLeaf as being this one
for ( uint64_t b = lastBlock; b > firstBlock; --b )
firstLeafID[b] = leafIndex;
if (leafFirstOffset < firstOffset) { firstLeafID[0] = leafIndex; continue; }
const auto offsetInBlock = (leafFirstOffset - 1) & (BlockWidth - 1);
if ( !offsetInBlock ) {
// If the first leaf starts exactly at the beginning of a
// block, register it in mFirstLeaf too
firstLeafID[firstBlock] = leafIndex;
} else {
// Otherwise, mark it in the jumpMap
// The specific uint64_t in the jumpMap to be marked is at element offset (offsetInBlock>>6), i.e. offsetBlock/64
// and bit offset (offsetInBlock & 0x3f), i.e. offsetInBlock%64
::cuda::atomic_ref<uint64_t> ref(jumpMap[firstBlock * JumpMapLength + (offsetInBlock>>6)]);
ref.fetch_or(0x1ul << (offsetInBlock & 0x3f));
}
}
return;
}
};
/// @brief Helper function to build a VoxelBlockManager on the device
template<int BlockWidthLog2, int NumThreads = 128>
void buildVoxelBlockManager(
uint64_t firstOffset,
uint64_t lastOffset,
int nBlocks,
int lowerCount,
const NanoGrid<ValueOnIndex> *grid,
uint32_t *firstLeafID,
uint64_t *jumpMap,
cudaStream_t stream = 0)
{
using Op = BuildVoxelBlockManagerFunctor<BlockWidthLog2, NumThreads>;
util::cuda::operatorKernel<Op>
<<<dim3(lowerCount,Op::SlicesPerLowerNode,1), NumThreads, 0, stream>>>(
firstOffset, lastOffset, nBlocks, grid, firstLeafID, jumpMap);
}
} // namespace tools::cuda
} // namespace nanovdb
#endif // NANOVDB_VOXELBLOCKMANAGER_CUH_HAS_BEEN_INCLUDED
+12 -1
View File
@@ -7,7 +7,7 @@
#]=======================================================================]
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.24)
project(NanoVDBTests LANGUAGES CXX)
include(GNUInstallDirs)
@@ -33,6 +33,11 @@ endif()
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/data")
if (MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler /bigobj")
endif()
# -----------------------------------------------------------------------------
add_executable(nanovdb_test_nanovdb "TestNanoVDB.cc")
@@ -44,7 +49,13 @@ add_test(nanovdb_unit_test nanovdb_test_nanovdb)
if(NANOVDB_USE_CUDA)
add_executable(nanovdb_test_cuda "TestNanoVDB.cu")
target_link_libraries(nanovdb_test_cuda PRIVATE nanovdb GTest::GTest GTest::Main)
set_target_properties(nanovdb_test_cuda PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
add_test(nanovdb_cuda_unit_test nanovdb_test_cuda)
add_executable(nanovdb_test_mgpu "TestMultiGPU.cu")
target_link_libraries(nanovdb_test_mgpu PRIVATE nanovdb GTest::GTest GTest::Main)
set_target_properties(nanovdb_test_mgpu PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
add_test(nanovdb_mgpu_unit_test nanovdb_test_mgpu)
endif()
# -----------------------------------------------------------------------------
+317
View File
@@ -0,0 +1,317 @@
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: Apache-2.0
#include <nanovdb/NanoVDB.h>
#include <nanovdb/tools/cuda/DistributedPointsToGrid.cuh>
#include <nanovdb/util/Timer.h>
#include <nanovdb/cuda/DeviceMesh.h>
#include <cuda.h>
#include <cuda_runtime_api.h>
#include <gtest/gtest.h>
#include <thread> // for std::thread
#include <thrust/fill.h>
#include <thrust/universal_vector.h>
#include <thrust/execution_policy.h>
/// @brief Tests the correctness of multi-GPU radix sort
TEST(TestNanoVDBMultiGPU, RadixSort)
{
nanovdb::cuda::DeviceMesh deviceMesh;
std::vector<cudaEvent_t> preEvents(deviceMesh.deviceCount());
std::vector<cudaEvent_t> postEvents(deviceMesh.deviceCount());
std::vector<nanovdb::cuda::TempDevicePool> tempDevicePools(deviceMesh.deviceCount());
std::vector<ptrdiff_t> deviceOffsets(deviceMesh.deviceCount());
std::vector<size_t> deviceSizes(deviceMesh.deviceCount());
using KeyT = int;
using ValueT = double;
std::srand(444);
const size_t numItems = 1571;
thrust::universal_vector<KeyT> keysIn(numItems);
thrust::universal_vector<ValueT> valuesIn(numItems);
for (size_t i = 0; i < numItems; ++i)
{
keysIn[i] = rand();
valuesIn[i] = -static_cast<ValueT>(keysIn[i]);
}
thrust::universal_vector<KeyT> keysOut(numItems);
thrust::universal_vector<ValueT> valuesOut(numItems);
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaEventCreateWithFlags(&preEvents[deviceId], cudaEventDisableTiming);
cudaEventCreateWithFlags(&postEvents[deviceId], cudaEventDisableTiming);
auto deviceSize = (numItems + deviceMesh.deviceCount() - 1) / deviceMesh.deviceCount();
deviceOffsets[deviceId] = deviceSize * deviceId;
deviceSizes[deviceId] = std::min(deviceSize, numItems - deviceOffsets[deviceId]);
}
nanovdb::tools::cuda::radixSortAsync(deviceMesh, tempDevicePools.data(), keysIn.data().get(), keysOut.data().get(), valuesIn.data().get(), valuesOut.data().get(), numItems, deviceOffsets.data(), deviceSizes.data(), preEvents.data(), postEvents.data());
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaEventSynchronize(postEvents[deviceId]);
}
for (size_t i = 0; i < numItems -1; ++i) {
EXPECT_LE(keysOut[i], keysOut[i + 1]);
EXPECT_GE(valuesOut[i], valuesOut[i + 1]);
}
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaEventDestroy(postEvents[deviceId]);
cudaEventDestroy(preEvents[deviceId]);
}
}
/// @brief Tests the correctness of multi-GPU exclusive sums against an equivalent CPU implementation
TEST(TestNanoVDBMultiGPU, ExclusiveSum)
{
nanovdb::cuda::DeviceMesh deviceMesh;
std::vector<cudaEvent_t> preEvents(deviceMesh.deviceCount());
std::vector<cudaEvent_t> postEvents(deviceMesh.deviceCount());
std::vector<nanovdb::cuda::TempDevicePool> tempDevicePools(deviceMesh.deviceCount());
std::vector<size_t> deviceSizes(deviceMesh.deviceCount());
thrust::universal_vector<int> input(937);
thrust::universal_vector<int> output(input.size());
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaEventCreateWithFlags(&preEvents[deviceId], cudaEventDisableTiming);
cudaEventCreateWithFlags(&postEvents[deviceId], cudaEventDisableTiming);
auto deviceSize = (input.size() + deviceMesh.deviceCount() - 1) / deviceMesh.deviceCount();
const ptrdiff_t deviceOffset = deviceSize * deviceId;
deviceSizes[deviceId] = std::min(deviceSize, input.size() - deviceOffset);
thrust::fill(thrust::cuda::par.on(stream), input.begin() + deviceOffset, input.begin() + deviceOffset + deviceSizes[deviceId], 0);
thrust::fill(thrust::cuda::par.on(stream), output.begin() + deviceOffset, output.begin() + deviceOffset + deviceSizes[deviceId], 0);
}
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaStreamSynchronize(stream);
}
{
// Set the input indices corresponding to the Fibbonacci sequence to be 1, rest 0
input[0] = 1;
size_t i = 0;
size_t j = 1;
size_t k = i + j;
while(k < input.size()) {
input[k] = 1;
i = j;
j = k;
k = (i + j);
}
}
nanovdb::tools::cuda::exclusiveSumAsync(deviceMesh, tempDevicePools.data(), input.data(), output.data(), deviceSizes.begin(), preEvents.data(), postEvents.data());
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaStreamSynchronize(stream);
}
int accumulator = 0;
for (size_t i = 0; i < output.size(); ++i) {
EXPECT_EQ(output[i], accumulator);
accumulator += input[i];
}
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaEventDestroy(postEvents[deviceId]);
cudaEventDestroy(preEvents[deviceId]);
}
}
/// @brief Tests the correctness of multi-GPU inclusive sums against an equivalent CPU implementation
TEST(TestNanoVDBMultiGPU, InclusiveSum)
{
nanovdb::cuda::DeviceMesh deviceMesh;
std::vector<cudaEvent_t> preEvents(deviceMesh.deviceCount());
std::vector<cudaEvent_t> postEvents(deviceMesh.deviceCount());
std::vector<nanovdb::cuda::TempDevicePool> tempDevicePools(deviceMesh.deviceCount());
std::vector<size_t> deviceSizes(deviceMesh.deviceCount());
thrust::universal_vector<int> input(937);
thrust::universal_vector<int> output(input.size());
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaEventCreateWithFlags(&preEvents[deviceId], cudaEventDisableTiming);
cudaEventCreateWithFlags(&postEvents[deviceId], cudaEventDisableTiming);
auto deviceSize = (input.size() + deviceMesh.deviceCount() - 1) / deviceMesh.deviceCount();
const ptrdiff_t deviceOffset = deviceSize * deviceId;
deviceSizes[deviceId] = std::min(deviceSize, input.size() - deviceOffset);
thrust::fill(thrust::cuda::par.on(stream), input.begin() + deviceOffset, input.begin() + deviceOffset + deviceSizes[deviceId], 0);
thrust::fill(thrust::cuda::par.on(stream), output.begin() + deviceOffset, output.begin() + deviceOffset + deviceSizes[deviceId], 0);
}
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaStreamSynchronize(stream);
}
{
// Set the input indices corresponding to the Fibbonacci sequence to be 1, rest 0
input[0] = 1;
size_t i = 0;
size_t j = 1;
size_t k = i + j;
while(k < input.size()) {
input[k] = 1;
i = j;
j = k;
k = (i + j);
}
}
nanovdb::tools::cuda::inclusiveSumAsync(deviceMesh, tempDevicePools.data(), input.data(), output.data(), deviceSizes.begin(), preEvents.data(), postEvents.data());
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaStreamSynchronize(stream);
}
int accumulator = 0;
for (size_t i = 0; i < output.size(); ++i) {
accumulator += input[i];
EXPECT_EQ(output[i], accumulator);
}
for (const auto& [deviceId, stream] : deviceMesh) {
cudaSetDevice(deviceId);
cudaEventDestroy(postEvents[deviceId]);
cudaEventDestroy(preEvents[deviceId]);
}
}
/// @brief Tests multi-GPU creation of grids for a single dense leaf
TEST(TestNanoVDBMultiGPU, DenseLeaf_DistributedCudaPointsToGrid_UnifiedBuffer)
{
int current = 0;
cudaCheck(cudaGetDevice(&current));
using BufferT = nanovdb::cuda::UnifiedBuffer;
using BuildT = nanovdb::ValueOnIndex;
// Initialize coordinates corresponding to a single dense leaf. In
// DistributedPointsToGrid, individual leaf nodes are resident and
// processed entirely on a single GPU. Thus, the single leaf case results
// in the edge case where one GPU constructs a leaf while the others idle.
const size_t voxelCount = 8 * 8 * 8;
nanovdb::Coord* voxels = nullptr;
const size_t voxelSize = voxelCount * sizeof(nanovdb::Coord);
cudaCheck(cudaMallocManaged(&voxels, voxelSize));
for (int32_t i = 0; i < 8; ++i)
for (int32_t j = 0; j < 8; ++j)
for (int32_t k = 0; k < 8; ++k)
voxels[i * 8 * 8 + j * 8 + k] = nanovdb::Coord(i, j, k);
nanovdb::cuda::DeviceMesh deviceMesh;
nanovdb::tools::cuda::DistributedPointsToGrid<BuildT> converter(deviceMesh);
auto handle = converter.getHandle(voxels, voxelCount);
EXPECT_TRUE(handle.deviceData());// grid exists on the GPU
EXPECT_TRUE(handle.deviceGrid<BuildT>());
EXPECT_FALSE(handle.deviceGrid<int>(0));
EXPECT_TRUE(handle.deviceGrid<BuildT>(0));
EXPECT_FALSE(handle.deviceGrid<BuildT>(1));
EXPECT_TRUE(handle.data());// grid also exists on the CPU
//timer.start("Allocating and copying grid from GPU to CPU");
auto *grid = handle.grid<BuildT>();// grid also exists on the CPU
EXPECT_TRUE(grid);
handle.deviceDownload();// creates a copy on the CPU
EXPECT_TRUE(handle.deviceData());
EXPECT_TRUE(handle.data());
auto *data = handle.gridData();
EXPECT_TRUE(data);
grid = handle.grid<BuildT>();
EXPECT_TRUE(grid);
EXPECT_TRUE(grid->activeVoxelCount() == 512);
EXPECT_EQ(nanovdb::Vec3d(1.0), grid->voxelSize());
cudaCheck(cudaFree(voxels));
cudaSetDevice(current); // restore device so subsequent tests don't fail
}// Large_DistributedCudaPointsToGrid_UnifiedBuffer
/// @brief Tests multi-GPU creation of grids for a large number of randomly sampled voxels
TEST(TestNanoVDBMultiGPU, Large_DistributedCudaPointsToGrid_UnifiedBuffer)
{
int current = 0;
cudaCheck(cudaGetDevice(&current));
using BufferT = nanovdb::cuda::UnifiedBuffer;
using BuildT = nanovdb::ValueOnIndex;
nanovdb::util::Timer timer;
const size_t voxelCount = 1 << 20;// 1048576
nanovdb::Coord* voxels = nullptr;
const size_t voxelSize = voxelCount * sizeof(nanovdb::Coord);
cudaCheck(cudaMallocManaged(&voxels, voxelSize));
{//generate random voxels
std::srand(98765);
const int max = 512, min = -max;
auto op = [&](){return rand() % (max - min) + min;};
for (size_t i = 0; i < voxelCount; ++i)
voxels[i] = nanovdb::Coord(op(), op(), op());
}
nanovdb::cuda::DeviceMesh deviceMesh;
nanovdb::tools::cuda::DistributedPointsToGrid<BuildT> converter(deviceMesh);
auto handle = converter.getHandle(voxels, voxelCount);
// auto handle = nanovdb::tools::cuda::voxelsToGrid<BuildT, nanovdb::Coord*, BufferT>(voxels, voxelCount);
EXPECT_TRUE(handle.deviceData());// grid exists on the GPU
EXPECT_TRUE(handle.deviceGrid<BuildT>());
EXPECT_FALSE(handle.deviceGrid<int>(0));
EXPECT_TRUE(handle.deviceGrid<BuildT>(0));
EXPECT_FALSE(handle.deviceGrid<BuildT>(1));
EXPECT_TRUE(handle.data());// grid also exists on the CPU
//timer.start("Allocating and copying grid from GPU to CPU");
auto *grid = handle.grid<BuildT>();// grid also exists on the CPU
EXPECT_TRUE(grid);
handle.deviceDownload();// creates a copy on the CPU
EXPECT_TRUE(handle.deviceData());
EXPECT_TRUE(handle.data());
auto *data = handle.gridData();
EXPECT_TRUE(data);
grid = handle.grid<BuildT>();
EXPECT_TRUE(grid);
EXPECT_TRUE(grid->valueCount()>0);
EXPECT_EQ(nanovdb::Vec3d(1.0), grid->voxelSize());
//timer.restart("Parallel unit-testing on CPU");
nanovdb::util::forEach(0, voxelCount, 1, [&](const nanovdb::util::Range1D &r){
auto acc = grid->getAccessor();
for (size_t i=r.begin(); i!=r.end(); ++i) {
const nanovdb::Coord &ijk = voxels[i];
EXPECT_TRUE(acc.probeLeaf(ijk)!=nullptr);
EXPECT_TRUE(acc.isActive(ijk));
EXPECT_TRUE(acc.getValue(ijk) > 0u);
const auto *leaf = acc.get<nanovdb::GetLeaf<BuildT>>(ijk);
EXPECT_TRUE(leaf);
const auto offset = leaf->CoordToOffset(ijk);
EXPECT_EQ(ijk, leaf->offsetToGlobalCoord(offset));
}
});
cudaCheck(cudaFree(voxels));
cudaSetDevice(current); // restore device so subsequent tests don't fail
}// Large_DistributedCudaPointsToGrid_UnifiedBuffer
+154 -103
View File
@@ -13,6 +13,7 @@
#include <algorithm> // for std::is_sorted
#include <cmath>
#include <cstdlib>
#include <iomanip> // for std::setw, std::setfill
#include <nanovdb/io/IO.h>
#include <nanovdb/tools/CreateNanoGrid.h>
@@ -231,8 +232,8 @@ using MyTypes = ::testing::Types<float,// GridType::Float =
// GridType::Vec4d = 18
nanovdb::ValueIndex,// GridType::Index = 19
nanovdb::ValueOnIndex,// GridType::OnIndex = 20
nanovdb::ValueIndexMask,// GridType::IndexMask = 21
nanovdb::ValueOnIndexMask,// GridType::OnIndexMask = 22
// nanovdb::ValueIndexMask,// GridType::IndexMask = 21
// nanovdb::ValueOnIndexMask,// GridType::OnIndexMask = 22
nanovdb::Point,// GridType::PointIndex = 23
nanovdb::Vec3u8,// GridType::Vec3u8 = 24
nanovdb::Vec3u16,// GridType::Vec3u16 = 25
@@ -372,8 +373,8 @@ TEST_F(TestNanoVDB, Basic)
TEST_F(TestNanoVDB, toStr)
{
{// toStr(GridType)
EXPECT_EQ(12, nanovdb::strlen<nanovdb::GridType>());
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::Unknown ), "?"), 0 );
EXPECT_EQ(11, nanovdb::strlen<nanovdb::GridType>());
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::Unknown ), "Unknown"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::Float ), "float"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::Double ), "double"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::Int16 ), "int16"), 0 );
@@ -394,8 +395,6 @@ TEST_F(TestNanoVDB, toStr)
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::Vec4d ), "Vec4d"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::Index ), "Index"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::OnIndex ), "OnIndex"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::IndexMask ), "IndexMask"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::OnIndexMask ), "OnIndexMask"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::PointIndex ), "PointIndex"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::Vec3u8 ), "Vec3u8"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::GridType::Vec3u16 ), "Vec3u16"), 0 );
@@ -437,14 +436,12 @@ TEST_F(TestNanoVDB, toStr)
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::Version(12,34,56) ), "12.34.56"), 0 );
}
{// toStr(MagicType)
EXPECT_EQ(25, nanovdb::strlen<nanovdb::MagicType>());
EXPECT_EQ(14, nanovdb::strlen<nanovdb::MagicType>());
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::MagicType::Unknown ), "unknown"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::MagicType::OpenVDB ), "openvdb"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::MagicType::NanoVDB ), "nanovdb"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::MagicType::NanoGrid ), "nanovdb::Grid"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::MagicType::NanoFile ), "nanovdb::File"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::MagicType::NanoNode ), "nanovdb::NodeManager"), 0 );
EXPECT_EQ( strcmp(nanovdb::toStr(mStr, nanovdb::MagicType::NanoFrag ), "fragmented nanovdb::Grid"), 0 );
}
}// toStr
@@ -920,12 +917,8 @@ TEST_F(TestNanoVDB, Traits)
EXPECT_FALSE(test);
test = nanovdb::BuildTraits<nanovdb::ValueIndex>::is_index;
EXPECT_TRUE(test);
test = nanovdb::BuildTraits<nanovdb::ValueIndexMask>::is_index;
EXPECT_TRUE(test);
test = nanovdb::BuildTraits<nanovdb::ValueOnIndex>::is_index;
EXPECT_TRUE(test);
test = nanovdb::BuildTraits<nanovdb::ValueOnIndexMask>::is_index;
EXPECT_TRUE(test);
test = nanovdb::BuildTraits<nanovdb::Fp4>::is_Fp;
EXPECT_TRUE(test);
test = nanovdb::BuildTraits<nanovdb::Fp8>::is_Fp;
@@ -2198,6 +2191,51 @@ TEST_F(TestNanoVDB, RootNode)
}
} // RootNode
TEST_F(TestNanoVDB, GridBlindMetaData)
{
nanovdb::GridBlindMetaData meta0;
EXPECT_TRUE(meta0.isValid());
EXPECT_EQ(0u, meta0.mDataOffset);
EXPECT_TRUE(std::string(meta0.mName).empty());
EXPECT_EQ(nullptr, meta0.getBlindData<float>());
EXPECT_EQ(nullptr, meta0.getBlindData<int>());
const std::string name("this is a test");
nanovdb::GridBlindMetaData meta(0, 10, sizeof(float),
nanovdb::GridBlindDataSemantic::Unknown,
nanovdb::GridBlindDataClass::AttributeArray,
nanovdb::GridType::Float);
EXPECT_TRUE(meta.isValid());
EXPECT_TRUE(std::string(meta.mName).empty());
meta.setName("this is a test");
EXPECT_EQ(std::string(meta.mName), name);
float *data = new float[10];
EXPECT_TRUE(data);
meta.setBlindData(data);
EXPECT_TRUE(meta.isValid());
EXPECT_EQ(std::string(meta.mName), name);
EXPECT_EQ((const void*)data, meta.blindData());
EXPECT_EQ(data, meta.getBlindData<float>());
EXPECT_EQ(nullptr, meta.getBlindData<int>());
auto meta2 = meta;
EXPECT_TRUE(meta2.isValid());
EXPECT_EQ(std::string(meta2.mName), name);
EXPECT_EQ((const void*)data, meta2.blindData());
EXPECT_EQ(data, meta2.getBlindData<float>());
EXPECT_EQ(nullptr, meta2.getBlindData<int>());
nanovdb::GridBlindMetaData meta3(meta);
EXPECT_TRUE(meta3.isValid());
EXPECT_EQ(std::string(meta3.mName), name);
EXPECT_EQ((const void*)data, meta3.blindData());
EXPECT_EQ(data, meta3.getBlindData<float>());
EXPECT_EQ(nullptr, meta3.getBlindData<int>());
delete [] data;
}// GridBlindMetaData
TEST_F(TestNanoVDB, Offsets)
{
{// check GridBlindMetaData
@@ -2520,8 +2558,6 @@ TYPED_TEST(TestOffsets, NanoVDB)
LeafData<Mask> is padded: no
LeafData<Index> is padded: no
LeafData<OnIndex> is padded: no
LeafData<IndexMask> is padded: no
LeafData<OnIndexMask> is padded: no
LeafData<bool> is padded: no
LeafData<PointIndex> is padded: no
LeafData<Vec3u8> is padded: yes
@@ -2583,18 +2619,6 @@ void checkLeaf<nanovdb::ValueIndex>(int &offset)
offset += 8;
}
template<>
void checkLeaf<nanovdb::ValueIndexMask>(int &offset)
{
using DataT = typename nanovdb::LeafNode<nanovdb::ValueIndexMask>::DataType;
EXPECT_EQ(NANOVDB_OFFSETOF(DataT, mOffset), offset);
offset += 8;
EXPECT_EQ(NANOVDB_OFFSETOF(DataT, mPrefixSum), offset);
offset += 8;
EXPECT_EQ(NANOVDB_OFFSETOF(DataT, mMask), offset);
offset += 64;
}
template<>
void checkLeaf<nanovdb::ValueOnIndex>(int &offset)
{
@@ -2605,18 +2629,6 @@ void checkLeaf<nanovdb::ValueOnIndex>(int &offset)
offset += 8;
}
template<>
void checkLeaf<nanovdb::ValueOnIndexMask>(int &offset)
{
using DataT = typename nanovdb::LeafNode<nanovdb::ValueOnIndexMask>::DataType;
EXPECT_EQ(NANOVDB_OFFSETOF(DataT, mOffset), offset);
offset += 8;
EXPECT_EQ(NANOVDB_OFFSETOF(DataT, mPrefixSum), offset);
offset += 8;
EXPECT_EQ(NANOVDB_OFFSETOF(DataT, mMask), offset);
offset += 64;
}
template<>
void checkLeaf<nanovdb::Fp4>(int &offset)
{
@@ -3146,30 +3158,82 @@ TEST_F(TestNanoVDB, CreateNanoGrid_Basic1)
EXPECT_EQ("", std::string(meta->shortGridName()));
EXPECT_EQ(nanovdb::GridType::Float, meta->gridType());
EXPECT_EQ(nanovdb::GridClass::Unknown, meta->gridClass());
auto* dstGrid = handle.grid<float>();
const auto* dstGrid = handle.grid<float>();
EXPECT_TRUE(dstGrid);
EXPECT_EQ("", std::string(dstGrid->gridName()));
EXPECT_EQ(nanovdb::Vec3d(1.0), dstGrid->voxelSize());
EXPECT_EQ(1u, dstGrid->activeVoxelCount());
EXPECT_EQ(1.0f, dstGrid->tree().getValue(ijk));
const auto &dstTree = dstGrid->tree();
EXPECT_EQ(1.0f, dstTree.getValue(ijk));
auto dstAcc = dstGrid->getAccessor();
EXPECT_EQ(1.0f, dstAcc.getValue(ijk));
EXPECT_TRUE(srcAcc.isActive(ijk));
EXPECT_EQ(nanovdb::Coord(1, 2, 3), dstGrid->indexBBox()[0]);
EXPECT_EQ(nanovdb::Coord(1, 2, 3), dstGrid->indexBBox()[1]);
EXPECT_EQ(dstGrid->tree().root().minimum(), 1.0f);// minimum active value
EXPECT_EQ(dstGrid->tree().root().maximum(), 1.0f);// maximum active value
EXPECT_NEAR(dstGrid->tree().root().average(), 1.0f, 1e-6);
EXPECT_NEAR(dstGrid->tree().root().variance(), 0.0f,1e-6);
EXPECT_NEAR(dstGrid->tree().root().stdDeviation(), 0.0f, 1e-6);
EXPECT_EQ(dstTree.root().minimum(), 1.0f);// minimum active value
EXPECT_EQ(dstTree.root().maximum(), 1.0f);// maximum active value
EXPECT_NEAR(dstTree.root().average(), 1.0f, 1e-6);
EXPECT_NEAR(dstTree.root().variance(), 0.0f,1e-6);
EXPECT_NEAR(dstTree.root().stdDeviation(), 0.0f, 1e-6);
EXPECT_FALSE(dstGrid->isEmpty());
EXPECT_FALSE(dstGrid->tree().isEmpty());
EXPECT_FALSE(dstGrid->tree().root().isEmpty());
EXPECT_EQ(1u, dstGrid->tree().nodeCount(0));
EXPECT_EQ(1u, dstGrid->tree().nodeCount(1));
EXPECT_EQ(1u, dstGrid->tree().nodeCount(2));
EXPECT_FALSE(dstTree.isEmpty());
EXPECT_FALSE(dstTree.root().isEmpty());
EXPECT_EQ(1u, dstTree.nodeCount(0));
EXPECT_EQ(1u, dstTree.nodeCount(1));
EXPECT_EQ(1u, dstTree.nodeCount(2));
// Test GetLeaf, GetLower, GetUpper, GetTile, and GetDim random access structs
{// hitting tile and all nodes
const nanovdb::Coord ijk(0,0,0);
EXPECT_EQ(dstTree.getFirstNode<0>(), dstAcc.get<nanovdb::GetLeaf<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<1>(), dstAcc.get<nanovdb::GetLower<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<2>(), dstAcc.get<nanovdb::GetUpper<float>>(ijk));
EXPECT_EQ(dstTree.root().probeTile(ijk), dstAcc.get<nanovdb::GetTile<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<0>(), dstTree.get<nanovdb::GetLeaf<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<1>(), dstTree.get<nanovdb::GetLower<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<2>(), dstTree.get<nanovdb::GetUpper<float>>(ijk));
EXPECT_EQ(dstTree.root().probeTile(ijk), dstTree.get<nanovdb::GetTile<float>>(ijk));
EXPECT_EQ(1u, dstTree.get<nanovdb::GetDim<float>>(ijk));
}
{// missing leaf node
const nanovdb::Coord ijk(8, 8, 8);
EXPECT_EQ(nullptr, dstAcc.get<nanovdb::GetLeaf<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<1>(), dstAcc.get<nanovdb::GetLower<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<2>(), dstAcc.get<nanovdb::GetUpper<float>>(ijk));
EXPECT_EQ(dstTree.root().probeTile(ijk), dstAcc.get<nanovdb::GetTile<float>>(ijk));
EXPECT_EQ(nullptr, dstTree.get<nanovdb::GetLeaf<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<1>(), dstTree.get<nanovdb::GetLower<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<2>(), dstTree.get<nanovdb::GetUpper<float>>(ijk));
EXPECT_EQ(dstTree.root().probeTile(ijk), dstTree.get<nanovdb::GetTile<float>>(ijk));
EXPECT_EQ(8u, dstTree.get<nanovdb::GetDim<float>>(ijk));
}
{// missing leaf and lower node
const nanovdb::Coord ijk(8*16, 8*16, 8*16);
EXPECT_EQ(nullptr, dstAcc.get<nanovdb::GetLeaf<float>>(ijk));
EXPECT_EQ(nullptr, dstAcc.get<nanovdb::GetLower<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<2>(), dstAcc.get<nanovdb::GetUpper<float>>(ijk));
EXPECT_EQ(dstTree.root().probeTile(ijk), dstAcc.get<nanovdb::GetTile<float>>(ijk));
EXPECT_EQ(nullptr, dstTree.get<nanovdb::GetLeaf<float>>(ijk));
EXPECT_EQ(nullptr, dstTree.get<nanovdb::GetLower<float>>(ijk));
EXPECT_EQ(dstTree.getFirstNode<2>(), dstTree.get<nanovdb::GetUpper<float>>(ijk));
EXPECT_EQ(dstTree.root().probeTile(ijk), dstTree.get<nanovdb::GetTile<float>>(ijk));
EXPECT_EQ(128u, dstTree.get<nanovdb::GetDim<float>>(ijk));
}
{// missing tile and all nodes
const nanovdb::Coord ijk(-1, -1,-1);
EXPECT_EQ(nullptr, dstAcc.get<nanovdb::GetLeaf<float>>(ijk));
EXPECT_EQ(nullptr, dstAcc.get<nanovdb::GetLower<float>>(ijk));
EXPECT_EQ(nullptr, dstAcc.get<nanovdb::GetUpper<float>>(ijk));
EXPECT_EQ(nullptr, dstAcc.get<nanovdb::GetTile<float>>(ijk));
EXPECT_EQ(nullptr, dstTree.get<nanovdb::GetLeaf<float>>(ijk));
EXPECT_EQ(nullptr, dstTree.get<nanovdb::GetLower<float>>(ijk));
EXPECT_EQ(nullptr, dstTree.get<nanovdb::GetUpper<float>>(ijk));
EXPECT_EQ(nullptr, dstTree.get<nanovdb::GetTile<float>>(ijk));
EXPECT_EQ(0u, dstTree.get<nanovdb::GetDim<float>>(ijk));
}
}
} // GridBuilderBasic1
} // CreateNanoGrid_Basic1
TEST_F(TestNanoVDB, CreateNanoGrid_addTile)
{
@@ -4145,9 +4209,9 @@ TEST_F(TestNanoVDB, NodeManager)
EXPECT_TRUE(dstGrid->isSequential<GridT::TreeType::Node1>());
EXPECT_TRUE(dstGrid->isSequential<GridT::TreeType::Node0>());
EXPECT_EQ(48u, sizeof(nanovdb::NodeManagerData));
EXPECT_EQ(40u, sizeof(nanovdb::NodeManagerData));
auto nodeMgrHandle = nanovdb::createNodeManager(*dstGrid);
EXPECT_EQ(48u, nodeMgrHandle.size());// since its linear
EXPECT_EQ(40u, nodeMgrHandle.size());// since its linear
auto *nodeMgr = nodeMgrHandle.mgr<float>();
EXPECT_TRUE(nodeMgr);
EXPECT_TRUE(nanovdb::isAligned(nodeMgr));
@@ -4899,8 +4963,6 @@ TEST_F(TestNanoVDB, PNanoVDB_Basic)
EXPECT_EQ((int)nanovdb::GridType::Vec4d, PNANOVDB_GRID_TYPE_VEC4D);
EXPECT_EQ((int)nanovdb::GridType::Index, PNANOVDB_GRID_TYPE_INDEX);
EXPECT_EQ((int)nanovdb::GridType::OnIndex, PNANOVDB_GRID_TYPE_ONINDEX);
EXPECT_EQ((int)nanovdb::GridType::IndexMask, PNANOVDB_GRID_TYPE_INDEXMASK);
EXPECT_EQ((int)nanovdb::GridType::OnIndexMask, PNANOVDB_GRID_TYPE_ONINDEXMASK);
EXPECT_EQ((int)nanovdb::GridType::PointIndex, PNANOVDB_GRID_TYPE_POINTINDEX);
EXPECT_EQ((int)nanovdb::GridType::End, PNANOVDB_GRID_TYPE_END);
@@ -5024,17 +5086,6 @@ void validateLeaf<nanovdb::ValueIndex>(pnanovdb_grid_type_t grid_type)
EXPECT_EQ(NANOVDB_OFFSETOF(leaf_t, mPrefixSum), PNANOVDB_LEAF_OFF_VALUE_MASK + 64 + 8);
}
// template specializations for nanovdb::ValueIndexMask types
template <>
void validateLeaf<nanovdb::ValueIndexMask>(pnanovdb_grid_type_t grid_type)
{
using leaf_t = typename nanovdb::LeafNode<nanovdb::ValueIndexMask>;
EXPECT_EQ(sizeof(leaf_t), (pnanovdb_grid_type_constants[grid_type].leaf_size));
EXPECT_EQ(NANOVDB_OFFSETOF(leaf_t, mOffset), PNANOVDB_LEAF_OFF_VALUE_MASK + 64);
EXPECT_EQ(NANOVDB_OFFSETOF(leaf_t, mPrefixSum), PNANOVDB_LEAF_OFF_VALUE_MASK + 64 + 8);
EXPECT_EQ(NANOVDB_OFFSETOF(leaf_t, mMask), PNANOVDB_LEAF_OFF_VALUE_MASK + 64 + 8 + 8);
}
// template specializations for nanovdb::ValueOnIndex types
template <>
void validateLeaf<nanovdb::ValueOnIndex>(pnanovdb_grid_type_t grid_type)
@@ -5045,17 +5096,6 @@ void validateLeaf<nanovdb::ValueOnIndex>(pnanovdb_grid_type_t grid_type)
EXPECT_EQ(sizeof(leaf_t), (pnanovdb_grid_type_constants[grid_type].leaf_size));
}
// template specializations for nanovdb::ValueOnIndexMask types
template <>
void validateLeaf<nanovdb::ValueOnIndexMask>(pnanovdb_grid_type_t grid_type)
{
using leaf_t = typename nanovdb::LeafNode<nanovdb::ValueOnIndexMask>;
EXPECT_EQ(sizeof(leaf_t), (pnanovdb_grid_type_constants[grid_type].leaf_size));
EXPECT_EQ(NANOVDB_OFFSETOF(leaf_t, mOffset), PNANOVDB_LEAF_OFF_VALUE_MASK + 64);
EXPECT_EQ(NANOVDB_OFFSETOF(leaf_t, mPrefixSum), PNANOVDB_LEAF_OFF_VALUE_MASK + 64 + 8);
EXPECT_EQ(NANOVDB_OFFSETOF(leaf_t, mMask), PNANOVDB_LEAF_OFF_VALUE_MASK + 64 + 8 + 8);
}
// template specializations for nanovdb::Point types
template <>
void validateLeaf<nanovdb::Point>(pnanovdb_grid_type_t grid_type)
@@ -5093,10 +5133,6 @@ TYPED_TEST(TestOffsets, PNanoVDB)
grid_type = PNANOVDB_GRID_TYPE_INDEX;
} else if (std::is_same<nanovdb::ValueOnIndex, TypeParam>::value) {
grid_type = PNANOVDB_GRID_TYPE_ONINDEX;
} else if (std::is_same<nanovdb::ValueIndexMask, TypeParam>::value) {
grid_type = PNANOVDB_GRID_TYPE_INDEXMASK;
} else if (std::is_same<nanovdb::ValueOnIndexMask, TypeParam>::value) {
grid_type = PNANOVDB_GRID_TYPE_ONINDEXMASK;
} else if (std::is_same<bool, TypeParam>::value) {
grid_type = PNANOVDB_GRID_TYPE_BOOLEAN;
} else if (std::is_same<nanovdb::Fp4, TypeParam>::value) {
@@ -5190,9 +5226,9 @@ TYPED_TEST(TestOffsets, PNanoVDB)
EXPECT_EQ(NANOVDB_OFFSETOF(treedata_t, mNodeCount[0]), PNANOVDB_TREE_OFF_NODE_COUNT_LEAF);
EXPECT_EQ(NANOVDB_OFFSETOF(treedata_t, mNodeCount[1]), PNANOVDB_TREE_OFF_NODE_COUNT_LOWER);
EXPECT_EQ(NANOVDB_OFFSETOF(treedata_t, mNodeCount[2]), PNANOVDB_TREE_OFF_NODE_COUNT_UPPER);
EXPECT_EQ(NANOVDB_OFFSETOF(treedata_t, mTileCount[0]), PNANOVDB_TREE_OFF_TILE_COUNT_LEAF);
EXPECT_EQ(NANOVDB_OFFSETOF(treedata_t, mTileCount[1]), PNANOVDB_TREE_OFF_TILE_COUNT_LOWER);
EXPECT_EQ(NANOVDB_OFFSETOF(treedata_t, mTileCount[2]), PNANOVDB_TREE_OFF_TILE_COUNT_UPPER);
EXPECT_EQ(NANOVDB_OFFSETOF(treedata_t, mTileCount[0]), PNANOVDB_TREE_OFF_TILE_COUNT_LOWER);
EXPECT_EQ(NANOVDB_OFFSETOF(treedata_t, mTileCount[1]), PNANOVDB_TREE_OFF_TILE_COUNT_UPPER);
EXPECT_EQ(NANOVDB_OFFSETOF(treedata_t, mTileCount[2]), PNANOVDB_TREE_OFF_TILE_COUNT_ROOT);
EXPECT_EQ(NANOVDB_OFFSETOF(treedata_t, mVoxelCount), PNANOVDB_TREE_OFF_VOXEL_COUNT);
EXPECT_EQ((int)sizeof(pnanovdb_tree_t), PNANOVDB_TREE_SIZE);
@@ -5203,9 +5239,9 @@ TYPED_TEST(TestOffsets, PNanoVDB)
EXPECT_EQ(NANOVDB_OFFSETOF(pnanovdb_tree_t, node_count_leaf), PNANOVDB_TREE_OFF_NODE_COUNT_LEAF);
EXPECT_EQ(NANOVDB_OFFSETOF(pnanovdb_tree_t, node_count_lower), PNANOVDB_TREE_OFF_NODE_COUNT_LOWER);
EXPECT_EQ(NANOVDB_OFFSETOF(pnanovdb_tree_t, node_count_upper), PNANOVDB_TREE_OFF_NODE_COUNT_UPPER);
EXPECT_EQ(NANOVDB_OFFSETOF(pnanovdb_tree_t, tile_count_leaf), PNANOVDB_TREE_OFF_TILE_COUNT_LEAF);
EXPECT_EQ(NANOVDB_OFFSETOF(pnanovdb_tree_t, tile_count_lower), PNANOVDB_TREE_OFF_TILE_COUNT_LOWER);
EXPECT_EQ(NANOVDB_OFFSETOF(pnanovdb_tree_t, tile_count_upper), PNANOVDB_TREE_OFF_TILE_COUNT_UPPER);
EXPECT_EQ(NANOVDB_OFFSETOF(pnanovdb_tree_t, tile_count_root), PNANOVDB_TREE_OFF_TILE_COUNT_ROOT);
EXPECT_EQ(NANOVDB_OFFSETOF(pnanovdb_tree_t, voxel_count), PNANOVDB_TREE_OFF_VOXEL_COUNT);
// background value can start at pad1
@@ -6751,40 +6787,55 @@ TEST_F(TestNanoVDB, NodeIterators)
{// check RootNode::ValueOnInterator
nanovdb::NanoRoot<float>::ValueOnIterator it;
EXPECT_FALSE(it);
it = fltTree.root().beginValueOn();
it = fltRoot.beginValueOn();
EXPECT_FALSE(it);// no active tiles
auto it2 = it;
EXPECT_FALSE(it2);
}
{// check RootNode::ValueOnInterator
{// check RootNode::ChildInterator
nanovdb::NanoRoot<float>::ChildIterator it;
EXPECT_FALSE(it);
it = fltTree.root().beginChild();
it = fltRoot.beginChild();
EXPECT_TRUE(it);
auto it2 = it;
EXPECT_TRUE(it2);
}
}
{// RootNode::TileIterator
nanovdb::NanoRoot<float>::TileIterator it;
EXPECT_FALSE(it);
it = fltRoot.beginTile();
EXPECT_TRUE(it);
EXPECT_EQ(0, it.pos());
EXPECT_EQ(nanovdb::Coord(-4096), it->origin());
auto it2 = it;
EXPECT_TRUE(it2);
EXPECT_EQ(0, it2.pos());
EXPECT_EQ(nanovdb::Coord(-4096), it2->origin());
EXPECT_TRUE(it2.isChild());
EXPECT_FALSE(it2.isValue());
EXPECT_FALSE(it2.isValueOn());
auto it3 = fltRoot.probe(nanovdb::Coord(0));
EXPECT_TRUE(it3);
EXPECT_EQ(8, fltRoot.tileCount());
EXPECT_EQ(7, it3.pos());
EXPECT_EQ(nanovdb::Coord(0), it3->origin());
EXPECT_TRUE(it3.isChild());
EXPECT_FALSE(it3.isValue());
EXPECT_FALSE(it3.isValueOn());
}
}// NodeIterators
// make testNanoVDB && ./unittest/testNanoVDB --gtest_filter="*BasicValueIndexStats*" --gtest_break_on_failure --gtest_repeat=5
TEST_F(TestNanoVDB, BasicValueIndexStats)
{
{
using ValueIndexT = typename nanovdb::NanoLeaf<nanovdb::ValueIndex>::DataType;
using ValueIndexMaskT = typename nanovdb::NanoLeaf<nanovdb::ValueIndexMask>::DataType;
using ValueOnIndexT = typename nanovdb::NanoLeaf<nanovdb::ValueOnIndex>::DataType;
using ValueOnIndexMaskT = typename nanovdb::NanoLeaf<nanovdb::ValueOnIndexMask>::DataType;
const size_t size1 = sizeof(ValueOnIndexT),
size2 = sizeof(ValueOnIndexMaskT),
size3 = sizeof(ValueIndexT),
size4 = sizeof(ValueIndexMaskT);
const size_t size1 = sizeof(ValueOnIndexT), size2 = sizeof(ValueIndexT);
EXPECT_EQ(size1, ValueOnIndexT::memUsage());
EXPECT_EQ(size2, ValueOnIndexMaskT::memUsage());
EXPECT_EQ(size3, ValueIndexT::memUsage());
EXPECT_EQ(size4, ValueIndexMaskT::memUsage());
EXPECT_EQ(64u, size2 - size1);// 512 bits = 64 bytes
EXPECT_EQ(64u, size4 - size3);// 512 bits = 64 bytes
EXPECT_EQ(size2, ValueIndexT::memUsage());
}
EXPECT_TRUE(nanovdb::Version() >= nanovdb::Version(32,3,4));
using SrcGridT = nanovdb::tools::build::Grid<float>;
@@ -7585,7 +7636,7 @@ TEST_F(TestNanoVDB, ChannelIndexGridBuilder)
EXPECT_EQ(idxGrid->valueCount(), idxGrid->blindMetaData(i).mValueCount);
EXPECT_EQ(nanovdb::GridType::Float, idxGrid->blindMetaData(i).mDataType);
EXPECT_EQ(nanovdb::GridBlindDataClass::ChannelArray, idxGrid->blindMetaData(i).mDataClass);
EXPECT_EQ(nanovdb::GridBlindDataSemantic::Unknown, idxGrid->blindMetaData(i).mSemantic);
EXPECT_EQ(nanovdb::GridBlindDataSemantic::LevelSet, idxGrid->blindMetaData(i).mSemantic);
const std::string name = std::string("channel_") + std::to_string(i);
EXPECT_EQ(0, std::strcmp(idxGrid->blindMetaData(i).mName, name.c_str() ));
//mTimer.start("Parallel leaf iterator test of active voxels in channel");
@@ -7606,7 +7657,7 @@ TEST_F(TestNanoVDB, ChannelIndexGridBuilder)
EXPECT_EQ(idxGrid->valueCount(), idxGrid->blindMetaData(i).mValueCount);
EXPECT_EQ(nanovdb::GridType::Float, idxGrid->blindMetaData(i).mDataType);
EXPECT_EQ(nanovdb::GridBlindDataClass::ChannelArray, idxGrid->blindMetaData(i).mDataClass);
EXPECT_EQ(nanovdb::GridBlindDataSemantic::Unknown, idxGrid->blindMetaData(i).mSemantic);
EXPECT_EQ(nanovdb::GridBlindDataSemantic::LevelSet, idxGrid->blindMetaData(i).mSemantic);
const std::string name = std::string("channel_") + std::to_string(i);
EXPECT_EQ(0, std::strcmp(idxGrid->blindMetaData(i).mName, name.c_str() ));
//mTimer.start("Parallel leaf iterator test of active voxels in channel");
+452 -160
View File
@@ -7,6 +7,7 @@
#include <nanovdb/tools/GridBuilder.h>
#include <nanovdb/tools/CreateNanoGrid.h>
#include <nanovdb/tools/CreatePrimitives.h>
#include <nanovdb/tools/VoxelBlockManager.h>
#include <nanovdb/NodeManager.h>
#include <nanovdb/util/cuda/Util.h>
#include <nanovdb/tools/cuda/SignedFloodFill.cuh>
@@ -16,22 +17,30 @@
#include <nanovdb/tools/cuda/GridChecksum.cuh>
#include <nanovdb/tools/cuda/GridValidator.cuh>
#include <nanovdb/tools/cuda/GridStats.cuh>
//#include <nanovdb/tools/cuda/DilateVoxels.cuh>
#include <nanovdb/tools/cuda/DilateGrid.cuh>
#include <nanovdb/tools/cuda/MergeGrids.cuh>
#include <nanovdb/tools/cuda/PruneGrid.cuh>
#include <nanovdb/tools/cuda/CoarsenGrid.cuh>
#include <nanovdb/tools/cuda/RefineGrid.cuh>
#include <nanovdb/util/cuda/Injection.cuh>
#include <nanovdb/util/cuda/Timer.h>
#include <nanovdb/util/Timer.h>
#include <nanovdb/io/IO.h>
#include <nanovdb/cuda/UnifiedBuffer.h>
#include <nanovdb/cuda/DeviceStreamMap.h>
#include <nanovdb/cuda/DeviceMesh.h>
#include <cuda.h>
#include <cuda_runtime_api.h>
#include <gtest/gtest.h>
#include <algorithm>// for std::sort
#include <iomanip> // for std::setw, std::setfill
#include <thread> // for std::thread
namespace nanovdb {// this namespace is required by gtest
namespace test {
// used for testing cuda::DeviceBuffer
void device2host(size_t count)
{
@@ -42,7 +51,7 @@ void device2host(size_t count)
EXPECT_TRUE(buffer.deviceData());
float *d_array = reinterpret_cast<float*>(buffer.deviceData());
constexpr unsigned int num_threads = 256;
unsigned int num_blocks = num_blocks = (static_cast<unsigned int>(count) + num_threads - 1) / num_threads;
unsigned int num_blocks = (static_cast<unsigned int>(count) + num_threads - 1) / num_threads;
nanovdb::util::cuda::lambdaKernel<<<num_blocks, num_threads>>>(count, [=] __device__ (size_t i) {d_array[i] = float(i);});
buffer.deviceDownload();// copy device -> host
EXPECT_EQ(size, buffer.size());
@@ -51,6 +60,46 @@ void device2host(size_t count)
float *array = reinterpret_cast<float*>(buffer.data());
for (size_t i=0; i<count; ++i) EXPECT_EQ(array[i], float(i));
}// device2host
void host2device(size_t count)
{
const size_t size = count * sizeof(float);
auto hostBuffer = nanovdb::HostBuffer(size);
for (size_t i=0; i<count; ++i) *hostBuffer.data<float>(i) = float(i);
int dev;
cudaError_t err = cudaGetDevice(&dev);
if (err != cudaSuccess) printf("kernel cuda error: %d\n", (int)err);
auto devBuffer = nanovdb::cuda::DeviceBuffer::create(hostBuffer, dev);// on device only
EXPECT_EQ(size, devBuffer.size());
EXPECT_FALSE(devBuffer.data());
EXPECT_TRUE(devBuffer.deviceData());
float *d_array = reinterpret_cast<float*>(devBuffer.deviceData());
constexpr unsigned int num_threads = 256;
unsigned int num_blocks = (static_cast<unsigned int>(count) + num_threads - 1) / num_threads;
bool *test, *d_test;
cudaCheck(cudaMallocHost((void**)&test, sizeof(bool)));
cudaCheck(cudaMalloc((void**)&d_test, sizeof(bool)));
*test = true;
cudaCheck(cudaMemcpyAsync(d_test, test, sizeof(bool), cudaMemcpyHostToDevice));// on host only
nanovdb::util::cuda::lambdaKernel<<<num_blocks, num_threads>>>(count, [=] __device__ (size_t i) {
if (d_array[i] != float(i)) *d_test = false;
d_array[i] = float(i) + 1.0f;
});
cudaCheck(cudaMemcpy(test, d_test, sizeof(bool), cudaMemcpyDeviceToHost));
EXPECT_TRUE(*test);
cudaCheck(cudaFreeHost(test));
cudaCheck(cudaFree(d_test));
devBuffer.deviceDownload();// copy device -> host
EXPECT_EQ(size, devBuffer.size());
EXPECT_TRUE(devBuffer.data());
EXPECT_TRUE(devBuffer.deviceData());
for (size_t i=0; i<count; ++i) EXPECT_EQ(*hostBuffer.data<float>(i) + 1.0f, *devBuffer.data<float>(i));
}// host2device
// used for testing cuda::DeviceBuffer
void host2device2host(size_t count)
{
@@ -73,7 +122,7 @@ void host2device2host(size_t count)
EXPECT_TRUE(buffer.deviceData());
float *d_array = reinterpret_cast<float*>(buffer.deviceData());
constexpr unsigned int num_threads = 256;
unsigned int num_blocks = num_blocks = (static_cast<unsigned int>(count) + num_threads - 1) / num_threads;
unsigned int num_blocks = (static_cast<unsigned int>(count) + num_threads - 1) / num_threads;
nanovdb::util::cuda::lambdaKernel<<<num_blocks, num_threads>>>(count, [=] __device__ (size_t i) {
if (d_array[i] != float(i)) *d_test = false;
d_array[i] = float(i) + 1.0f;
@@ -88,11 +137,13 @@ void host2device2host(size_t count)
EXPECT_TRUE(buffer.deviceData());
for (size_t i=0; i<count; ++i) EXPECT_EQ(array[i], float(i) + 1.0f);
}// host2device2host
// used to test cudaStr methods
int signum(int val)
{
return (0 < val) - (val < 0);
}// signum
void cudaStr()
{
const size_t size = 50;
@@ -130,7 +181,9 @@ void cudaStr()
cudaCheck(cudaFree(d_n));
cudaCheck(cudaFree(d_str));
}// cudaStr
}// namespace test
}// namespace nanovdb
TEST(TestNanoVDBCUDA, CudaDeviceBuffer)
@@ -152,6 +205,7 @@ TEST(TestNanoVDBCUDA, CudaDeviceBuffer)
EXPECT_FALSE(buffer.empty());
}
nanovdb::test::device2host(1000);
nanovdb::test::host2device(1000);
nanovdb::test::host2device2host(1000);
}
@@ -164,7 +218,7 @@ __global__ void testKernel(int device)
{
int dev;
cudaError_t err = cudaGetDevice(&dev);
if (err != cudaSuccess) printf("kernel cuda error: %d\n", (int)err);
//if (err != cudaSuccess) printf("kernel cuda error: %d\n", (int)err);
if (dev != device) printf("Error: expected device ID = %i but was called with %i\n", dev, device);
}
@@ -178,7 +232,11 @@ TEST(TestNanoVDBCUDA, DeviceStreamMap)
EXPECT_EQ(count, nanovdb::util::cuda::deviceCount());
EXPECT_EQ(current, nanovdb::util::cuda::currentDevice());
float *ptr = new float;
EXPECT_EQ(cudaInvalidDeviceId, nanovdb::util::cuda::ptrToDevice(ptr));
const int deviceID = nanovdb::util::cuda::ptrToDevice(ptr);
EXPECT_TRUE(cudaInvalidDeviceId == deviceID || cudaCpuDeviceId == deviceID);
EXPECT_GT(0, deviceID);
//EXPECT_EQ(cudaInvalidDeviceId, deviceID);
//EXPECT_EQ(cudaCpuDeviceId, deviceID);
delete ptr;
cudaCheck(cudaMalloc((void**)&ptr, sizeof(float)));
EXPECT_EQ(current, nanovdb::util::cuda::ptrToDevice(ptr));
@@ -228,6 +286,52 @@ TEST(TestNanoVDBCUDA, DeviceStreamMap)
cudaSetDevice(current); // restore device so subsequent tests don't fail
}
TEST(TestNanoVDBCUDA, DeviceMesh)
{
int count = 0, current = 0;
{
cudaCheck(cudaGetDeviceCount(&count));
cudaCheck(cudaGetDevice(&current));
EXPECT_EQ(count, nanovdb::util::cuda::deviceCount());
EXPECT_EQ(current, nanovdb::util::cuda::currentDevice());
}
//std::cout << "Total device count = " << count << std::endl;
nanovdb::cuda::DeviceMesh serialMesh;
EXPECT_EQ(count, serialMesh.deviceCount());
EXPECT_GT(nanovdb::cuda::minDevicePageSize(serialMesh), 0);
// Construction of deviceMesh (and destruction) should not modify current device
EXPECT_EQ(current, nanovdb::util::cuda::currentDevice());
// Serial kernel dispatch with iterator
for (const auto& [device, stream] : serialMesh) {
cudaSetDevice(device);
testKernel<<<1, 1, 0, stream>>>(device);
}
// Wait on each kernel to finish using indexing operation
for (size_t i = 0; i < serialMesh.deviceCount(); ++i) {
cudaSetDevice(serialMesh[i].id);
cudaStreamSynchronize(serialMesh[i].stream);
}
// Test move constructor
nanovdb::cuda::DeviceMesh parallelMesh(std::move(serialMesh));
EXPECT_EQ(count, parallelMesh.deviceCount());
// After move constructor, serialMesh should have no devices
EXPECT_EQ(0, serialMesh.deviceCount());
// Parallel kernel dispatch with iterator
std::vector<std::thread> threads;
for (const auto& [device, stream] : parallelMesh) {
threads.emplace_back([](int device, cudaStream_t stream) {
cudaSetDevice(device);
testKernel<<<1, 1, 0, stream>>>(device);
cudaStreamSynchronize(stream);
}, device, stream);
}
std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
threads.clear();
cudaSetDevice(current); // Restore device so subsequent tests don't fail
}
TEST(TestNanoVDBCUDA, Basic_CudaPointsToGrid_float)
{
using BuildT = float;
@@ -286,6 +390,8 @@ struct ProbeValueNew {
ValueT value;
operator bool() const { return state; }
};
static constexpr int LEVEL = 0;// minimum level for the descent during top-down traversal
using Type = Probe;
__hostdev__ static Probe get(const NanoRoot<BuildT> &root) {
return Probe{false, root.mBackground};
}
@@ -303,24 +409,6 @@ struct ProbeValueNew {
}
};// ProbeValueNew<BuildT>
template <typename BuildT>
struct AccessLeafMask;
// template specialization of AccessLeafMask wrt ValueOnIndexMask
template <>
struct AccessLeafMask<ValueOnIndexMask>{
__hostdev__ static bool get(const NanoRoot<ValueOnIndexMask>&) {return false;}
__hostdev__ static bool get(const typename NanoRoot<ValueOnIndexMask>::Tile&) {return false;}
__hostdev__ static bool get(const NanoUpper<ValueOnIndexMask>&, uint32_t) {return false;}
__hostdev__ static bool get(const NanoLower<ValueOnIndexMask>&, uint32_t) {return false;}
__hostdev__ static bool get(const NanoLeaf<ValueOnIndexMask> &leaf, uint32_t n) {return leaf.mMask.isOn(n);}
__hostdev__ static void set(NanoRoot<ValueOnIndexMask>&) {}
__hostdev__ static void set(typename NanoRoot<ValueOnIndexMask>::Tile&) {}
__hostdev__ static void set(NanoUpper<ValueOnIndexMask>&, uint32_t) {}
__hostdev__ static void set(NanoLower<ValueOnIndexMask>&, uint32_t) {}
__hostdev__ static void set(NanoLeaf<ValueOnIndexMask> &leaf, uint32_t n) {leaf.mMask.setOn(n);}
};// AccessLeafMask<BuildT>
}// end of test namespace
}// end of nanovdb namespace
@@ -383,8 +471,15 @@ TEST(TestNanoVDBCUDA, Basic_CudaPointsToGrid_ValueIndex)
const nanovdb::Coord ijk = coords[i];
const auto *leaf = acc.get<nanovdb::GetLeaf<BuildT>>(ijk);
EXPECT_TRUE(leaf);
EXPECT_EQ(0u, leaf->LEVEL);
const auto offset = leaf->CoordToOffset(ijk);
EXPECT_EQ(ijk, leaf->offsetToGlobalCoord(offset));
const auto *lower = acc.get<nanovdb::GetLower<BuildT>>(ijk);
EXPECT_TRUE(lower);
EXPECT_EQ(1u, lower->LEVEL);
const auto *upper = acc.get<nanovdb::GetUpper<BuildT>>(ijk);
EXPECT_TRUE(upper);
EXPECT_EQ(2u, upper->LEVEL);
}
}// Basic_CudaPointsToGrid_ValueIndex
@@ -393,7 +488,6 @@ TEST(TestNanoVDBCUDA, Basic_CudaPointsToGrid_ValueOnIndex)
using BuildT = nanovdb::ValueOnIndex;
using GridT = nanovdb::NanoGrid<BuildT>;
EXPECT_TRUE(nanovdb::BuildTraits<BuildT>::is_index);
EXPECT_FALSE(nanovdb::BuildTraits<BuildT>::is_indexmask);
EXPECT_TRUE(nanovdb::BuildTraits<BuildT>::is_onindex);
EXPECT_FALSE(nanovdb::BuildTraits<BuildT>::is_offindex);
const size_t num_points = 3;
@@ -486,117 +580,6 @@ TEST(TestNanoVDBCUDA, Basic_CudaPointsToGrid_ValueOnIndex)
}
}// Basic_CudaPointsToGrid_ValueOnIndex
TEST(TestNanoVDBCUDA, Basic_CudaPointsToGrid_ValueOnIndexMask)
{
using BuildT = nanovdb::ValueOnIndexMask;
using GridT = nanovdb::NanoGrid<BuildT>;
EXPECT_TRUE(nanovdb::BuildTraits<BuildT>::is_index);
EXPECT_TRUE(nanovdb::BuildTraits<BuildT>::is_indexmask);
EXPECT_TRUE(nanovdb::BuildTraits<BuildT>::is_onindex);
EXPECT_FALSE(nanovdb::BuildTraits<BuildT>::is_offindex);
const size_t num_points = 3;
nanovdb::Coord coords[num_points] = {nanovdb::Coord(1, 2, 3),
nanovdb::Coord(1, 2, 4),
nanovdb::Coord(8, 2, 3)}, *d_coords = nullptr;
cudaCheck(cudaMalloc(&d_coords, num_points * sizeof(nanovdb::Coord)));
cudaCheck(cudaMemcpy(d_coords, coords, num_points * sizeof(nanovdb::Coord), cudaMemcpyHostToDevice));// CPU -> GPU
#if 0
nanovdb::tools::cuda::PointsToGrid converter;
auto handle = converter.getHandle<BuildT>(d_coords, num_points);
#else
auto handle = nanovdb::tools::cuda::voxelsToGrid<BuildT>(d_coords, num_points);
#endif
cudaCheck(cudaFree(d_coords));
EXPECT_TRUE(handle.deviceData());// grid only exists on the GPU
EXPECT_FALSE(handle.data());// no grid was yet allocated on the CPU
const uint64_t size = sizeof(GridT) +
sizeof(GridT::TreeType) +
GridT::RootType::memUsage(1) +
sizeof(GridT::UpperNodeType) +
sizeof(GridT::LowerNodeType) +
2*sizeof(GridT::LeafNodeType);
EXPECT_EQ(handle.size(), size);
GridT *grid = handle.grid<BuildT>();// no grid on the CPU
EXPECT_FALSE(grid);
handle.deviceDownload();// creates a copy up the CPU
EXPECT_TRUE(handle.deviceData());
EXPECT_TRUE(handle.data());
auto *data = handle.gridData();
EXPECT_TRUE(data);
grid = handle.grid<BuildT>();
EXPECT_TRUE(grid);
EXPECT_EQ(4u, grid->valueCount());
auto acc = grid->getAccessor();
EXPECT_FALSE( acc.isActive(nanovdb::Coord(0,2,3)));
EXPECT_TRUE( acc.isActive(nanovdb::Coord(1,2,3)));
EXPECT_TRUE( acc.isActive(nanovdb::Coord(1,2,4)));
EXPECT_TRUE( acc.isActive(nanovdb::Coord(8,2,3)));
EXPECT_EQ(0u, acc.getValue(nanovdb::Coord(0,2,3)));
EXPECT_EQ(1u, acc.getValue(nanovdb::Coord(1,2,3)));
EXPECT_EQ(2u, acc.getValue(nanovdb::Coord(1,2,4)));
EXPECT_EQ(3u, acc.getValue(nanovdb::Coord(8,2,3)));
using GetT = nanovdb::GetValue<BuildT>;
EXPECT_EQ(0u, acc.get<GetT>(nanovdb::Coord(0,2,3)));
EXPECT_EQ(1u, acc.get<GetT>(nanovdb::Coord(1,2,3)));
EXPECT_EQ(2u, acc.get<GetT>(nanovdb::Coord(1,2,4)));
EXPECT_EQ(3u, acc.get<GetT>(nanovdb::Coord(8,2,3)));
using OpT = nanovdb::test::AccessLeafMask<BuildT>;
EXPECT_EQ(false, acc.get<OpT>(nanovdb::Coord(0,2,3)));
EXPECT_EQ(true, acc.get<OpT>(nanovdb::Coord(1,2,3)));
EXPECT_EQ(true, acc.get<OpT>(nanovdb::Coord(1,2,4)));
EXPECT_EQ(true, acc.get<OpT>(nanovdb::Coord(8,2,3)));
acc.set<OpT>(nanovdb::Coord(1,2,3));
acc.set<OpT>(nanovdb::Coord(8,2,3));
EXPECT_EQ(false, acc.get<OpT>(nanovdb::Coord(0,2,3)));
EXPECT_EQ(true , acc.get<OpT>(nanovdb::Coord(1,2,3)));
EXPECT_EQ(true, acc.get<OpT>(nanovdb::Coord(1,2,4)));
EXPECT_EQ(true, acc.get<OpT>(nanovdb::Coord(8,2,3)));
{
using T = nanovdb::ProbeValue<BuildT>;
uint64_t value = 0;
EXPECT_EQ(false, acc.get<T>(nanovdb::Coord(0,2,3), value) );
EXPECT_EQ(0u, value);
EXPECT_EQ(true, acc.get<T>(nanovdb::Coord(1,2,3), value) );
EXPECT_EQ(1u, value);
EXPECT_EQ(true, acc.get<T>(nanovdb::Coord(1,2,4), value) );
EXPECT_EQ(2u, value);
EXPECT_EQ(true, acc.get<T>(nanovdb::Coord(8,2,3), value) );
EXPECT_EQ(3u, value);
EXPECT_EQ(false, acc.get<T>(nanovdb::Coord(-18,2,3), value) );
EXPECT_EQ(0u, value);
EXPECT_EQ(false, grid->tree().get<T>(nanovdb::Coord(0,2,3), value) );
EXPECT_EQ(0u, value);
EXPECT_EQ(true, grid->tree().get<T>(nanovdb::Coord(1,2,3), value) );
EXPECT_EQ(1u, value);
EXPECT_EQ(true, grid->tree().get<T>(nanovdb::Coord(1,2,4), value) );
EXPECT_EQ(2u, value);
EXPECT_EQ(true, grid->tree().get<T>(nanovdb::Coord(8,2,3), value) );
EXPECT_EQ(3u, value);
EXPECT_EQ(false, grid->tree().get<T>(nanovdb::Coord(-18,2,3), value) );
EXPECT_EQ(0u, value);
}
for (size_t i=0; i<num_points; ++i) {
const nanovdb::Coord ijk = coords[i];
const auto *leaf = acc.get<nanovdb::GetLeaf<BuildT>>(ijk);
EXPECT_TRUE(leaf);
const auto offset = leaf->CoordToOffset(ijk);
EXPECT_EQ(ijk, leaf->offsetToGlobalCoord(offset));
EXPECT_EQ(leaf->mValueMask, leaf->mMask);
}
}// Basic_CudaPointsToGrid_ValueOnIndexMask
TEST(TestNanoVDBCUDA, Large_CudaPointsToGrid_DeviceBuffer)
{
using BuildT = nanovdb::ValueOnIndex;
@@ -674,7 +657,8 @@ TEST(TestNanoVDBCUDA, Large_CudaPointsToGrid_DeviceBuffer)
});
//timer.stop();
}// Large_CudaPointsToGrid_old
cudaCheck(cudaFree(d_coords));
}// Large_CudaPointsToGrid_DeviceBuffer
TEST(TestNanoVDBCUDA, Large_CudaPointsToGrid_UnifiedBuffer)
{
@@ -753,7 +737,8 @@ TEST(TestNanoVDBCUDA, Large_CudaPointsToGrid_UnifiedBuffer)
});
//timer.stop();
}// Large_CudaPointsToGrid_new
cudaCheck(cudaFree(d_coords));
}// Large_CudaPointsToGrid_UnifiedBuffer
TEST(TestNanoVDBCUDA, mergeSplitGrids)
{
@@ -934,14 +919,12 @@ TEST(TestNanoVDBCUDA, CudaIndexGridToGrid_basic)
EXPECT_EQ(1u, idxHdl.buffer().bufferCount());
EXPECT_TRUE(idxHdl.buffer().data(0, cudaCpuDeviceId));
EXPECT_FALSE(idxHdl.buffer().data(0, 0));
EXPECT_FALSE(idxHdl.buffer().data(0, 1));
EXPECT_FALSE(idxHdl.deviceGrid<nanovdb::ValueIndex>());
idxHdl.deviceUpload();
EXPECT_TRUE(idxHdl.deviceGrid<nanovdb::ValueIndex>());
EXPECT_EQ(2u, idxHdl.buffer().bufferCount());
EXPECT_TRUE(idxHdl.buffer().data(0, cudaCpuDeviceId));
EXPECT_TRUE(idxHdl.buffer().data(0, 0));
EXPECT_FALSE(idxHdl.buffer().data(0, 1));
auto *idxGrid = idxHdl.grid<nanovdb::ValueIndex>();
EXPECT_TRUE(idxGrid);
//timer.restart("Create value list on CPU");
@@ -1114,22 +1097,22 @@ TEST(TestNanoVDBCUDA, CudaSignedFloodFill)
{
using BufferT = nanovdb::cuda::DeviceBuffer;
//nanovdb::util::Timer timer("Create FloatGrid on CPU");
auto floatHdl = nanovdb::tools::createLevelSetSphere<float, BufferT>(100);
auto floatHdl = nanovdb::tools::createLevelSetSphere<float, BufferT>(1000);
auto *floatGrid = floatHdl.grid<float>();
EXPECT_TRUE(floatGrid);
auto acc = floatGrid->getAccessor();
EXPECT_FALSE(acc.isActive(nanovdb::Coord(103,0,0)));
EXPECT_TRUE( acc.isActive(nanovdb::Coord(100,0,0)));
EXPECT_FALSE(acc.isActive(nanovdb::Coord( 97,0,0)));
EXPECT_EQ( 3.0f, acc(103,0,0));
EXPECT_EQ( 0.0f, acc(100,0,0));
EXPECT_EQ(-3.0f, acc( 97,0,0));
EXPECT_FALSE(acc.isActive(nanovdb::Coord(1003,0,0)));
EXPECT_TRUE( acc.isActive(nanovdb::Coord(1000,0,0)));
EXPECT_FALSE(acc.isActive(nanovdb::Coord( 997,0,0)));
EXPECT_EQ( 3.0f, acc(1003,0,0));
EXPECT_EQ( 0.0f, acc(1000,0,0));
EXPECT_EQ(-3.0f, acc( 997,0,0));
using OpT = nanovdb::SetVoxel<float>;// only set the voxel value
acc.set<OpT>(nanovdb::Coord(103,0,0),-1.0f);// flip sign and value of inactive voxel
acc.set<OpT>(nanovdb::Coord( 97,0,0), 1.0f);// flip sign and value of inactive voxel
EXPECT_EQ(-1.0f, acc(103,0,0));
EXPECT_EQ( 0.0f, acc(100,0,0));
EXPECT_EQ( 1.0f, acc( 97,0,0));
acc.set<OpT>(nanovdb::Coord(1003,0,0),-1.0f);// flip sign and value of inactive voxel
acc.set<OpT>(nanovdb::Coord( 997,0,0), 1.0f);// flip sign and value of inactive voxel
EXPECT_EQ(-1.0f, acc(1003,0,0));
EXPECT_EQ( 0.0f, acc(1000,0,0));
EXPECT_EQ( 1.0f, acc( 997,0,0));
//timer.restart("Copy FloatGrid from CPU to GPU");
floatHdl.deviceUpload();// CPU -> GPU
auto *d_floatGrid = floatHdl.deviceGrid<float>();
@@ -1143,9 +1126,9 @@ TEST(TestNanoVDBCUDA, CudaSignedFloodFill)
floatGrid = floatHdl.grid<float>();
EXPECT_TRUE(floatGrid);
acc = floatGrid->getAccessor();
EXPECT_EQ( 3.0f, acc(103,0,0));
EXPECT_EQ( 0.0f, acc(100,0,0));
EXPECT_EQ(-3.0f, acc( 97,0,0));
EXPECT_EQ( 3.0f, acc(1003,0,0));
EXPECT_EQ( 0.0f, acc(1000,0,0));
EXPECT_EQ(-3.0f, acc( 997,0,0));
//EXPECT_FALSE(floatGrid->isLexicographic());
EXPECT_TRUE(floatGrid->isBreadthFirst());
}// CudaSignedFloodFill
@@ -2643,7 +2626,7 @@ TEST(TestNanoVDBCUDA, compareNodeOrdering)
auto *upper1 = grid1->tree().getFirstUpper(), *upper2 = grid2->tree().getFirstUpper();
EXPECT_TRUE(upper1);
EXPECT_TRUE(upper2);
for (int i=0; i<grid1->tree().nodeCount(2); ++i) {
for (uint32_t i=0; i<grid1->tree().nodeCount(2); ++i) {
//std::cerr << "#" << i << " origin(CPU)=" << upper1[i].origin() << " origin(GPU)=" << upper2[i].origin() << std::endl;
EXPECT_EQ(upper1[i].origin(), upper2[i].origin());
EXPECT_EQ(upper1[i].valueMask(), upper2[i].valueMask());
@@ -2655,7 +2638,7 @@ TEST(TestNanoVDBCUDA, compareNodeOrdering)
auto *lower1 = grid1->tree().getFirstLower(), *lower2 = grid2->tree().getFirstLower();
EXPECT_TRUE(lower1);
EXPECT_TRUE(lower2);
for (int i=0; i<grid1->tree().nodeCount(1); ++i) {
for (uint32_t i=0; i<grid1->tree().nodeCount(1); ++i) {
EXPECT_EQ(lower1[i].origin(), lower2[i].origin());
EXPECT_EQ(lower1[i].valueMask(), lower2[i].valueMask());
EXPECT_EQ(lower1[i].childMask(), lower2[i].childMask());
@@ -2666,7 +2649,7 @@ TEST(TestNanoVDBCUDA, compareNodeOrdering)
auto *leaf1 = grid1->tree().getFirstLeaf(), *leaf2 = grid2->tree().getFirstLeaf();
EXPECT_TRUE(leaf1);
EXPECT_TRUE(leaf2);
for (int i=0; i<grid1->tree().nodeCount(0); ++i) {
for (uint32_t i=0; i<grid1->tree().nodeCount(0); ++i) {
EXPECT_EQ(leaf1[i].origin(), leaf2[i].origin());
EXPECT_EQ(leaf1[i].valueMask(), leaf2[i].valueMask());
}
@@ -3258,7 +3241,8 @@ TEST(TestNanoVDBCUDA, UnifiedBuffer_basic)
//buffer.deviceDownload(stream);
buffer.prefetch(0, size, cudaCpuDeviceId, stream);
nanovdb::util::Timer timer("Setting values on CPU with unified memory");
for (int i = 0, *x = buffer.data<int>(); i < N; i++) *x++ = 1;
int *x = buffer.data<int>();
for (size_t i = 0; i < N; i++) *x++ = 1;
timer.stop();
}
{// resize unified buffers
@@ -3279,8 +3263,13 @@ TEST(TestNanoVDBCUDA, UnifiedBuffer_basic)
EXPECT_EQ(CUDA_SUCCESS, cudaStreamSynchronize(stream));
int *x = buffer.data<int>();
for (int i = 0; i < N; ++i) EXPECT_EQ(1, *x++);
for (int i = 0; i < N; ++i) EXPECT_EQ(2, *x++);
for (size_t i = 0; i < N; ++i) EXPECT_EQ(1, *x++);
for (size_t i = 0; i < N; ++i) EXPECT_EQ(2, *x++);
nanovdb::cuda::UnifiedBuffer otherBuffer(std::move(buffer));
int *y = otherBuffer.data<int>();
for (size_t i = 0; i < N; ++i) EXPECT_EQ(1, *y++);
for (size_t i = 0; i < N; ++i) EXPECT_EQ(2, *y++);
}// UnifiedBuffer_basic
TEST(TestNanoVDBCUDA, UnifiedBuffer_IO)
@@ -3360,3 +3349,306 @@ TEST(TestNanoVDBCUDA, UnifiedBuffer_createLevelSetSphere)
EXPECT_EQ(nanovdb::Coord(center - radius - 2), dstGrid->indexBBox()[0]);
EXPECT_EQ(nanovdb::Coord(center + radius + 2), dstGrid->indexBBox()[1]);
} // UnifiedBuffer_createLevelSetSphere
template<class BuildT>
__global__ void testComputeStencilNeighborsKernel(
nanovdb::NanoGrid<BuildT> *grid,
uint32_t *firstLeafIDArray,
uint64_t *jumpMapArray,
uint64_t *stencilNeighborsArray)
{
static constexpr int BlockWidth = 128;
static constexpr int JumpMapLength = BlockWidth >> 6;
int bID = blockIdx.x;
int tID = threadIdx.x;
uint32_t &firstLeafID = firstLeafIDArray[bID];
uint64_t *jumpMap = jumpMapArray + JumpMapLength * bID;
int firstOffset = 1;
int blockFirstOffset = firstOffset + bID * BlockWidth;
__shared__ uint32_t leafIndex[BlockWidth];
__shared__ uint16_t voxelOffset[BlockWidth];
nanovdb::tools::cuda::VoxelBlockManager<BlockWidth>::decodeInverseMaps(
grid, firstLeafID, jumpMap, blockFirstOffset, &leafIndex[0], &voxelOffset[0]);
uint64_t localNeighbors[27] = {};
nanovdb::tools::cuda::VoxelBlockManager<BlockWidth>::computeBoxStencil(
grid, &leafIndex[0], &voxelOffset[0], localNeighbors);
__syncthreads();
using StencilNeighborsType = uint64_t (*)[27];
auto stencilNeighbors = reinterpret_cast<StencilNeighborsType>(stencilNeighborsArray+27*BlockWidth*bID);
for (int i = 0; i < 27; i++)
stencilNeighbors[tID][i] = localNeighbors[i];
}
template<class BuildT, class BufferT>
void testVoxelBlockManager()
{
// Create a test domain (21^3 box)
std::vector<nanovdb::Coord> voxels;
nanovdb::CoordBBox bbox = nanovdb::CoordBBox::createCube(125,145); // Coordinates chosen to span more than 1 lower node
for (auto it = bbox.begin(); it; it++) voxels.push_back(*it);
constexpr std::size_t BlockWidthLog2 = 7;
constexpr std::size_t BlockWidth = 1 << BlockWidthLog2; // 128
const std::size_t nVoxels = voxels.size();
const std::size_t nBlocks = (nVoxels + BlockWidth - 1) >> BlockWidthLog2;
nanovdb::Coord *deviceVoxels;
cudaCheck(cudaMalloc(&deviceVoxels, nVoxels * sizeof(nanovdb::Coord)));
cudaCheck(cudaMemcpy(deviceVoxels, voxels.data(), nVoxels * sizeof(nanovdb::Coord), cudaMemcpyHostToDevice)); // CPU -> GPU
auto handle = nanovdb::tools::cuda::voxelsToGrid<BuildT>(deviceVoxels, nVoxels);
cudaCheck(cudaFree(deviceVoxels));
EXPECT_TRUE((handle.deviceData())); // grid only exists on the GPU
auto deviceGrid = handle.template deviceGrid<BuildT>();
EXPECT_FALSE(handle.data()); // no grid was yet allocated on the CPU
handle.deviceDownload();
EXPECT_TRUE(handle.data()); // no grid was yet allocated on the CPU
auto grid = handle.template grid<BuildT>();
EXPECT_TRUE(grid);
std::size_t nLowerNodes = grid->tree().nodeCount(1);
// Construct VBM structure
nanovdb::tools::VoxelBlockManagerHandle<BufferT> vbmHandle;
vbmHandle.template deviceResize<BlockWidthLog2>(nBlocks);
vbmHandle.setOffsets(1, nVoxels);
vbmHandle.setLowerCount(nLowerNodes);
nanovdb::tools::cuda::buildVoxelBlockManager<BlockWidthLog2>(
vbmHandle.firstOffset(), vbmHandle.lastOffset(),
vbmHandle.blockCount(), vbmHandle.lowerCount(),
deviceGrid, vbmHandle.deviceFirstLeafID(), vbmHandle.deviceJumpMap() );
// Compute stencil neighbors
const size_t neighborStencilSize = nBlocks * BlockWidth * 27 * sizeof(uint64_t);
auto neighborStencilBuffer = nanovdb::cuda::DeviceBuffer::create(neighborStencilSize, nullptr, false);
EXPECT_TRUE(neighborStencilBuffer.deviceData());
testComputeStencilNeighborsKernel<<<nBlocks,BlockWidth>>>(deviceGrid,
vbmHandle.deviceFirstLeafID(), vbmHandle.deviceJumpMap(),
reinterpret_cast<uint64_t*>(neighborStencilBuffer.deviceData()));
cudaCheck(cudaGetLastError());
// Verify stencil neighbors against results of standard accessors
neighborStencilBuffer.deviceDownload();
EXPECT_TRUE(neighborStencilBuffer.data());
using StencilNeighborsType = uint64_t (*)[27];
auto stencilNeighbors = reinterpret_cast<StencilNeighborsType>(neighborStencilBuffer.data());
auto acc = grid->getAccessor();
for (const auto& coord : voxels) {
const auto index = acc.getValue(coord);
for (int di = -1; di <= 1; di++)
for (int dj = -1; dj <= 1; dj++)
for (int dk = -1; dk <= 1; dk++) {
int spokeID = ( di + 1 ) * 9 + ( dj + 1 ) * 3 + dk + 1;
const auto neighbor = coord.offsetBy( di, dj, dk );
const auto neighborIndex = acc.getValue(neighbor);
EXPECT_EQ( neighborIndex, stencilNeighbors[index-1][spokeID] ); } }
}
TEST(TestNanoVDBCUDA, VoxelBlockManager_ValueOnIndex)
{
testVoxelBlockManager<nanovdb::ValueOnIndex,nanovdb::cuda::DeviceBuffer>();
}// VoxelBlockManager_ValueOnIndex
TEST(TestNanoVDBCUDA, VoxelBlockManager_ValueOnIndex_UnifiedBuffer)
{
testVoxelBlockManager<nanovdb::ValueOnIndex,nanovdb::cuda::UnifiedBuffer>();
}// VoxelBlockManager_ValueOnIndex_UnifiedBuffer
TEST(TestNanoVDBCUDA, DilateInjectPrune_ValueOnIndex)
{
using BuildT = nanovdb::ValueOnIndex;
// Create an input (original) grid to use as input to dilation op
std::vector<nanovdb::Coord> inputPoints;
inputPoints.emplace_back(1,0,0); // Added nodes after dilation: 3 root, 3 upper, 3 lower, 3 leaf
inputPoints.emplace_back(0,1,1); // Added nodes after dilation: 1 root, 1 upper, 1 lower, 1 leaf
inputPoints.emplace_back(127,127,127); // Added nodes after dilation: 7 lower, 7 leaf
auto inputBuffer = nanovdb::cuda::DeviceBuffer::create( inputPoints.size() * sizeof(nanovdb::Coord), nullptr, false);
EXPECT_FALSE(inputBuffer.data());
EXPECT_TRUE(inputBuffer.deviceData());
cudaCheck(cudaMemcpy(inputBuffer.deviceData(), inputPoints.data(), inputPoints.size() * sizeof(nanovdb::Coord), cudaMemcpyHostToDevice));
nanovdb::tools::cuda::PointsToGrid<BuildT> converter;
converter.setChecksum(nanovdb::CheckMode::Default);
auto inputHandle = converter.getHandle(static_cast<nanovdb::Coord*>(inputBuffer.deviceData()), inputPoints.size());
EXPECT_TRUE(inputHandle.deviceGrid<BuildT>());
auto inputGrid = inputHandle.deviceGrid<BuildT>();
EXPECT_FALSE(inputHandle.grid<BuildT>());
// Perform dilation
nanovdb::tools::cuda::DilateGrid<BuildT> dilator( inputGrid );
dilator.setOperation(nanovdb::tools::morphology::NN_FACE_EDGE_VERTEX);
dilator.setChecksum(nanovdb::CheckMode::Default);
dilator.setVerbose(0);
auto dilatedHandle = dilator.getHandle();
auto dilatedGrid = dilatedHandle.deviceGrid<BuildT>();
EXPECT_TRUE(dilatedGrid);
auto dilatedTreeData = nanovdb::util::cuda::DeviceGridTraits<BuildT>::getTreeData(dilatedGrid);
EXPECT_EQ(dilatedTreeData.mNodeCount[0], 13);
EXPECT_EQ(dilatedTreeData.mNodeCount[1], 12);
EXPECT_EQ(dilatedTreeData.mNodeCount[2], 5);
EXPECT_EQ(dilatedTreeData.mVoxelCount, 73);
// Create a prune mask (set bits correspond to retained voxels) from the occupancy of the original grid
auto maskBuffer = nanovdb::cuda::DeviceBuffer::create( dilatedTreeData.mNodeCount[0] * sizeof(nanovdb::Mask<3>), nullptr, false);
EXPECT_TRUE(maskBuffer.deviceData());
auto leafMasks = static_cast<nanovdb::Mask<3>*>(maskBuffer.deviceData());
constexpr unsigned int num_threads = 128;
unsigned int num_blocks = (static_cast<unsigned int>(dilatedTreeData.mNodeCount[0]) + num_threads - 1) / num_threads;
nanovdb::util::cuda::lambdaKernel<<<num_blocks, num_threads>>>(dilatedTreeData.mNodeCount[0],
nanovdb::util::cuda::InjectGridMaskFunctor<BuildT>(),
inputGrid, dilatedGrid, leafMasks);
// Prune with computed mask
nanovdb::tools::cuda::PruneGrid<BuildT> pruner( dilatedGrid, leafMasks );
pruner.setChecksum(nanovdb::CheckMode::Default);
pruner.setVerbose(0);
auto prunedHandle = pruner.getHandle();
auto prunedGrid = prunedHandle.deviceGrid<BuildT>();
EXPECT_TRUE(prunedGrid);
// The pruned grid should be identical to the original input
EXPECT_FALSE(prunedHandle.grid<BuildT>());
inputHandle.deviceDownload();
prunedHandle.deviceDownload();
EXPECT_TRUE(inputHandle.grid<BuildT>());
EXPECT_TRUE(prunedHandle.grid<BuildT>());
EXPECT_EQ(inputHandle.grid<BuildT>()->mChecksum.full(), prunedHandle.grid<BuildT>()->mChecksum.full());
}// DilateInjectPrune_ValueOnIndex
TEST(TestNanoVDBCUDA, RefineCoarsen_ValueOnIndex)
{
using BuildT = nanovdb::ValueOnIndex;
// Create an input (original) grid to use as input to refinement op
std::vector<nanovdb::Coord> inputPoints;
inputPoints.emplace_back(0,0,0); // Refinement doesn't introduce extra nodes
inputPoints.emplace_back(4,0,0); // Refinement will introduce extra leaf
inputPoints.emplace_back(0,64,0); // Refinement will introduce extra lower node
inputPoints.emplace_back(0,0,2048); // Refinement will introduce extra upper node (and root tile)
auto inputBuffer = nanovdb::cuda::DeviceBuffer::create( inputPoints.size() * sizeof(nanovdb::Coord), nullptr, false);
EXPECT_FALSE(inputBuffer.data());
EXPECT_TRUE(inputBuffer.deviceData());
cudaCheck(cudaMemcpy(inputBuffer.deviceData(), inputPoints.data(), inputPoints.size() * sizeof(nanovdb::Coord), cudaMemcpyHostToDevice));
nanovdb::tools::cuda::PointsToGrid<BuildT> converter;
converter.setChecksum(nanovdb::CheckMode::Default);
auto inputHandle = converter.getHandle(static_cast<nanovdb::Coord*>(inputBuffer.deviceData()), inputPoints.size());
EXPECT_TRUE(inputHandle.deviceGrid<BuildT>());
auto inputGrid = inputHandle.deviceGrid<BuildT>();
EXPECT_FALSE(inputHandle.grid<BuildT>());
// Perform refinement
nanovdb::tools::cuda::RefineGrid<BuildT> refiner( inputGrid );
refiner.setChecksum(nanovdb::CheckMode::Default);
refiner.setVerbose(0);
auto refinedHandle = refiner.getHandle();
auto refinedGrid = refinedHandle.deviceGrid<BuildT>();
EXPECT_TRUE(refinedGrid);
auto refinedTreeData = nanovdb::util::cuda::DeviceGridTraits<BuildT>::getTreeData(refinedGrid);
EXPECT_EQ(refinedTreeData.mNodeCount[0], 4);
EXPECT_EQ(refinedTreeData.mNodeCount[1], 3);
EXPECT_EQ(refinedTreeData.mNodeCount[2], 2);
EXPECT_EQ(refinedTreeData.mVoxelCount, 32);
// Coarsen back to original
nanovdb::tools::cuda::CoarsenGrid<BuildT> coarsener( refinedGrid );
coarsener.setChecksum(nanovdb::CheckMode::Default);
coarsener.setVerbose(0);
auto coarsenedHandle = coarsener.getHandle();
auto coarsenedGrid = coarsenedHandle.deviceGrid<BuildT>();
EXPECT_TRUE(coarsenedGrid);
// The coarsened grid should be identical to the original input
EXPECT_FALSE(coarsenedHandle.grid<BuildT>());
inputHandle.deviceDownload();
coarsenedHandle.deviceDownload();
EXPECT_TRUE(inputHandle.grid<BuildT>());
EXPECT_TRUE(coarsenedHandle.grid<BuildT>());
EXPECT_EQ(inputHandle.grid<BuildT>()->mChecksum.full(), coarsenedHandle.grid<BuildT>()->mChecksum.full());
}// RefineCoarsen_ValueOnIndex
TEST(TestNanoVDBCUDA, MergeGrids_ValueOnIndex)
{
using BuildT = nanovdb::ValueOnIndex;
// Create the first input grid for the merge op
std::vector<nanovdb::Coord> inputPointsA;
for (int i = 0; i <= 2; i++)
for (int j = 0; j <= 2; j++)
for (int k = 0; k <= 2; k++)
inputPointsA.emplace_back(i-1, j*8-1, k*128); // 4 upper, 12 lower, 18 leaf nodes
auto inputBufferA = nanovdb::cuda::DeviceBuffer::create( inputPointsA.size() * sizeof(nanovdb::Coord), nullptr, false);
EXPECT_FALSE(inputBufferA.data());
EXPECT_TRUE(inputBufferA.deviceData());
cudaCheck(cudaMemcpy(inputBufferA.deviceData(), inputPointsA.data(), inputPointsA.size() * sizeof(nanovdb::Coord), cudaMemcpyHostToDevice));
auto inputHandleA = nanovdb::tools::cuda::voxelsToGrid<BuildT>(static_cast<nanovdb::Coord*>(inputBufferA.deviceData()), inputPointsA.size());
EXPECT_TRUE(inputHandleA.deviceGrid<BuildT>());
auto inputGridA = inputHandleA.deviceGrid<BuildT>();
EXPECT_FALSE(inputHandleA.grid<BuildT>());
// Create the second input grid for the merge op
std::vector<nanovdb::Coord> inputPointsB;
for (int i = 0; i <= 2; i++)
for (int j = 0; j <= 2; j++)
for (int k = 0; k <= 2; k++)
inputPointsB.emplace_back(i, j*8-1, (k-1)*128); // 4 upper, 6 lower, 9 leaf nodes
auto inputBufferB = nanovdb::cuda::DeviceBuffer::create( inputPointsB.size() * sizeof(nanovdb::Coord), nullptr, false);
EXPECT_FALSE(inputBufferB.data());
EXPECT_TRUE(inputBufferB.deviceData());
cudaCheck(cudaMemcpy(inputBufferB.deviceData(), inputPointsB.data(), inputPointsB.size() * sizeof(nanovdb::Coord), cudaMemcpyHostToDevice));
auto inputHandleB = nanovdb::tools::cuda::voxelsToGrid<BuildT>(static_cast<nanovdb::Coord*>(inputBufferB.deviceData()), inputPointsB.size());
EXPECT_TRUE(inputHandleB.deviceGrid<BuildT>());
auto inputGridB = inputHandleB.deviceGrid<BuildT>();
EXPECT_FALSE(inputHandleB.grid<BuildT>());
// Perform the merge operation
nanovdb::tools::cuda::MergeGrids<BuildT> merger( inputGridA, inputGridB );
merger.setChecksum(nanovdb::CheckMode::Disable);
merger.setVerbose(0);
auto mergedHandle = merger.getHandle();
auto mergedGrid = mergedHandle.deviceGrid<BuildT>();
EXPECT_TRUE(mergedGrid);
auto mergedTreeData = nanovdb::util::cuda::DeviceGridTraits<BuildT>::getTreeData(mergedGrid);
EXPECT_EQ(mergedTreeData.mNodeCount[0], 21);
EXPECT_EQ(mergedTreeData.mNodeCount[1], 14);
EXPECT_EQ(mergedTreeData.mNodeCount[2], 6);
EXPECT_EQ(mergedTreeData.mVoxelCount, 42); // Each input grid has 27 active voxels, 12 shared between the two
}// DilateInjectPrune_ValueOnIndex
TEST(TestNanoVDBCUDA, GridHandle_from_HostBuffer)
{
using namespace nanovdb;
using BufferT = nanovdb::cuda::DeviceBuffer;
auto hostHandle = tools::createLevelSetSphere<float>(100, Vec3d(0),1,3, Vec3d(0), "test");
int dev;
cudaError_t err = cudaGetDevice(&dev);
EXPECT_EQ(err, cudaSuccess);
cudaStream_t stream;
cudaCheck(cudaStreamCreate(&stream));
{// longer version
auto devBuffer = BufferT::create(hostHandle.buffer(), dev, stream);
EXPECT_EQ(hostHandle.bufferSize(), devBuffer.size());
auto devHandle = GridHandle<BufferT>(std::move(devBuffer));
// testing
EXPECT_EQ(hostHandle.bufferSize(), devHandle.bufferSize());
EXPECT_EQ(devBuffer.size(), 0);
devHandle.deviceDownload(stream);
for (uint64_t i=0; i<hostHandle.bufferSize(); ++i) {
EXPECT_EQ(*hostHandle.buffer().data<char>(i), *devHandle.buffer().data<char>(i));
}
}
{// compact version
auto devHandle = GridHandle<BufferT>(BufferT::create(hostHandle.buffer(), dev, stream));
// testing
EXPECT_EQ(hostHandle.bufferSize(), devHandle.bufferSize());
devHandle.deviceDownload(stream);
for (uint64_t i=0; i<hostHandle.bufferSize(); ++i) {
EXPECT_EQ(*hostHandle.buffer().data<char>(i), *devHandle.buffer().data<char>(i));
}
}
}

Some files were not shown because too many files have changed in this diff Show More